summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--INPUT_OWNERS2
-rw-r--r--OWNERS1
-rw-r--r--Ravenwood.bp11
-rw-r--r--apex/jobscheduler/service/aconfig/device_idle.aconfig2
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java23
-rw-r--r--api/StubLibraries.bp26
-rw-r--r--boot/Android.bp14
-rw-r--r--core/api/current.txt109
-rw-r--r--core/api/system-current.txt26
-rw-r--r--core/api/test-current.txt40
-rw-r--r--core/api/test-lint-baseline.txt10
-rw-r--r--core/java/Android.bp7
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java38
-rw-r--r--core/java/android/app/Activity.java6
-rw-r--r--core/java/android/app/ActivityOptions.java33
-rw-r--r--core/java/android/app/ActivityThread.java54
-rw-r--r--core/java/android/app/AppOpsManager.java30
-rw-r--r--core/java/android/app/AutomaticZenRule.java24
-rw-r--r--core/java/android/app/BroadcastOptions.java5
-rw-r--r--core/java/android/app/ClientTransactionHandler.java17
-rw-r--r--core/java/android/app/ComponentOptions.java35
-rw-r--r--core/java/android/app/ForegroundServiceTypePolicy.java7
-rw-r--r--core/java/android/app/Notification.java1
-rw-r--r--core/java/android/app/SystemServiceRegistry.java10
-rw-r--r--core/java/android/app/WallpaperManager.java40
-rw-r--r--core/java/android/app/ambient_context.aconfig8
-rw-r--r--core/java/android/app/ambientcontext/AmbientContextEvent.java86
-rw-r--r--core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java7
-rw-r--r--core/java/android/app/servertransaction/ActivityRelaunchItem.java18
-rw-r--r--core/java/android/app/servertransaction/MoveToDisplayItem.java18
-rw-r--r--core/java/android/content/ClipData.java34
-rw-r--r--core/java/android/content/ClipboardManager.java3
-rw-r--r--core/java/android/content/Context.java9
-rw-r--r--core/java/android/content/pm/PackageManager.java4
-rw-r--r--core/java/android/content/pm/ServiceInfo.java15
-rw-r--r--core/java/android/content/pm/multiuser.aconfig7
-rw-r--r--core/java/android/credentials/GetCandidateCredentialsResponse.java32
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java107
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java15
-rw-r--r--core/java/android/hardware/camera2/params/OutputConfiguration.java1
-rw-r--r--core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig7
-rw-r--r--core/java/android/inputmethodservice/navigationbar/NavigationBarView.java6
-rw-r--r--core/java/android/net/flags.aconfig6
-rw-r--r--core/java/android/os/HandlerThread.java17
-rw-r--r--core/java/android/os/UserManager.java4
-rw-r--r--core/java/android/os/storage/OWNERS4
-rw-r--r--core/java/android/permission/PermissionUsageHelper.java3
-rw-r--r--core/java/android/provider/CallLog.java27
-rw-r--r--core/java/android/provider/E2eeContactKeysManager.java (renamed from core/java/android/provider/ContactKeysManager.java)392
-rw-r--r--core/java/android/security/ConfirmationPrompt.java1
-rw-r--r--core/java/android/security/flags.aconfig7
-rw-r--r--core/java/android/security/keystore/recovery/RecoveryController.java7
-rw-r--r--core/java/android/service/chooser/flags.aconfig11
-rw-r--r--core/java/android/view/HandwritingInitiator.java33
-rw-r--r--core/java/android/view/OWNERS1
-rw-r--r--core/java/android/view/PointerIcon.java19
-rw-r--r--core/java/android/view/RoundedCorners.java6
-rw-r--r--core/java/android/view/View.java4
-rw-r--r--core/java/android/view/ViewRootImpl.java9
-rw-r--r--core/java/android/view/flags/refresh_rate_flags.aconfig8
-rw-r--r--core/java/android/view/inputmethod/ImeTracker.java18
-rw-r--r--core/java/android/view/inputmethod/InputMethodInfo.java19
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java9
-rw-r--r--core/java/android/widget/RemoteViews.java21
-rw-r--r--core/java/android/widget/flags/notification_widget_flags.aconfig10
-rw-r--r--core/java/android/window/flags/OWNERS1
-rw-r--r--core/java/android/window/flags/large_screen_experiences_app_compat.aconfig7
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig7
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java8
-rw-r--r--core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java15
-rw-r--r--core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java3
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java3
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java11
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl8
-rw-r--r--core/java/com/android/internal/widget/ConversationAvatarData.java42
-rw-r--r--core/java/com/android/internal/widget/ConversationHeaderData.java44
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java308
-rw-r--r--core/java/com/android/internal/widget/MessagingData.java14
-rw-r--r--core/java/com/android/internal/widget/NotificationRunnables.java (renamed from packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt)19
-rw-r--r--core/jni/LayoutlibLoader.cpp18
-rw-r--r--core/jni/android_media_AudioSystem.cpp38
-rw-r--r--core/jni/android_view_MotionEvent.cpp19
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp2
-rw-r--r--core/res/Android.bp2
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--core/res/res/drawable/pointer_wait_vector.xml91
-rw-r--r--core/res/res/drawable/pointer_wait_vector_0.xml27
-rw-r--r--core/res/res/drawable/pointer_wait_vector_1.xml21
-rw-r--r--core/res/res/drawable/pointer_wait_vector_10.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_11.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_12.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_13.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_14.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_15.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_16.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_17.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_18.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_19.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_2.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_20.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_21.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_22.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_23.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_24.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_25.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_26.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_27.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_28.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_29.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_3.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_30.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_31.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_32.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_33.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_34.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_35.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_36.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_37.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_38.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_39.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_4.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_40.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_41.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_42.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_43.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_44.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_45.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_46.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_47.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_48.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_49.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_5.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_50.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_51.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_52.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_53.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_54.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_55.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_56.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_57.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_58.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_59.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_6.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_60.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_61.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_62.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_63.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_64.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_65.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_66.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_67.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_68.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_69.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_7.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_70.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_71.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_72.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_73.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_74.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_75.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_76.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_77.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_78.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_79.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_8.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_80.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_81.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_82.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_83.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_84.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_85.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_86.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_87.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_9.xml30
-rw-r--r--core/res/res/drawable/pointer_wait_vector_icon.xml5
-rw-r--r--core/res/res/values/attrs.xml4
-rw-r--r--core/res/res/values/attrs_manifest.xml16
-rw-r--r--core/res/res/values/config.xml12
-rw-r--r--core/res/res/values/public-staging.xml2
-rw-r--r--core/res/res/values/styles.xml2
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--core/tests/coretests/Android.bp38
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java25
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java6
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java6
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java10
-rw-r--r--core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java37
-rw-r--r--core/tests/coretests/src/android/view/RoundedCornersTest.java86
-rw-r--r--core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java20
-rw-r--r--keystore/java/android/security/KeyStore.java63
-rw-r--r--keystore/java/android/security/keystore/AndroidKeyStoreProvider.java22
-rw-r--r--keystore/java/android/security/keystore/KeyGenParameterSpec.java2
-rw-r--r--keystore/java/android/security/keystore/KeyInfo.java18
-rw-r--r--keystore/java/android/security/keystore/KeyProtection.java7
-rw-r--r--keystore/java/android/security/keystore/KeyStoreCryptoOperation.java8
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java3
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java7
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java8
-rw-r--r--keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java5
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig8
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml12
-rw-r--r--libs/WindowManager/Shell/res/layout/maximize_menu_button.xml8
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml4
-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/bar/BubbleBarLayerView.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt218
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java59
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt169
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java113
-rw-r--r--libs/hwui/DisplayListOps.in1
-rw-r--r--libs/hwui/RecordingCanvas.cpp15
-rw-r--r--libs/hwui/RecordingCanvas.h2
-rw-r--r--libs/input/PointerController.cpp4
-rw-r--r--media/java/android/media/AudioFormat.java40
-rw-r--r--media/java/android/media/RingtoneSelection.java742
-rw-r--r--media/java/android/media/audiopolicy/AudioMix.java31
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicy.java16
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicyConfig.java6
-rw-r--r--media/java/android/media/tv/ITvInputSessionWrapper.java2
-rw-r--r--nfc/api/current.txt2
-rw-r--r--nfc/java/android/nfc/INfcCardEmulation.aidl2
-rw-r--r--nfc/java/android/nfc/cardemulation/ApduServiceInfo.java28
-rw-r--r--nfc/java/android/nfc/cardemulation/CardEmulation.java6
-rw-r--r--packages/CrashRecovery/framework/Android.bp22
-rw-r--r--packages/CrashRecovery/services/Android.bp24
-rw-r--r--packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java2
-rw-r--r--packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java11
-rw-r--r--packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java103
-rw-r--r--packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java46
-rw-r--r--packages/CredentialManager/res/drawable/ic_passkey_24.xml22
-rw-r--r--packages/CredentialManager/res/values/strings.xml4
-rw-r--r--packages/CredentialManager/shared/Android.bp1
-rw-r--r--packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml26
-rw-r--r--packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt17
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt10
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt75
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt100
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt6
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt60
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt10
-rw-r--r--packages/CredentialManager/tests/robotests/Android.bp3
-rw-r--r--packages/CredentialManager/tests/robotests/customization/assets/phone/dark_landscape_singleCredentialScreen_newM3BottomSheet.pngbin0 -> 79574 bytes
-rw-r--r--packages/CredentialManager/tests/robotests/customization/assets/phone/dark_portrait_singleCredentialScreen_newM3BottomSheet.pngbin0 -> 82294 bytes
-rw-r--r--packages/CredentialManager/tests/robotests/customization/assets/phone/light_landscape_singleCredentialScreen_newM3BottomSheet.pngbin0 -> 78668 bytes
-rw-r--r--packages/CredentialManager/tests/robotests/customization/assets/phone/light_portrait_singleCredentialScreen_newM3BottomSheet.pngbin0 -> 81211 bytes
-rw-r--r--packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_landscape_singleCredentialScreen_newM3BottomSheet.pngbin0 -> 53722 bytes
-rw-r--r--packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_portrait_singleCredentialScreen_newM3BottomSheet.pngbin0 -> 55706 bytes
-rw-r--r--packages/CredentialManager/tests/robotests/customization/assets/tablet/light_landscape_singleCredentialScreen_newM3BottomSheet.pngbin0 -> 53484 bytes
-rw-r--r--packages/CredentialManager/tests/robotests/customization/assets/tablet/light_portrait_singleCredentialScreen_newM3BottomSheet.pngbin0 -> 55341 bytes
-rw-r--r--packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt41
-rw-r--r--packages/PackageInstaller/res/layout/install_content_view.xml192
-rw-r--r--packages/PackageInstaller/res/layout/uninstall_content_view.xml53
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java2
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java2
-rw-r--r--packages/SettingsLib/Spa/build.gradle.kts2
-rw-r--r--packages/SettingsLib/Spa/gradle/libs.versions.toml2
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.pngbin57340 -> 58496 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.pngbin48422 -> 49467 bytes
-rw-r--r--packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.pngbin28871 -> 29537 bytes
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/RadioPreferences.kt73
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt13
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/RadioPreferencesTest.kt150
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt30
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt16
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig9
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml37
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml28
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml41
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml32
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml36
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml27
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml40
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml31
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml35
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml26
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml39
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml30
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml34
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml25
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml38
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml29
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml33
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml24
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml37
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml28
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml36
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml27
-rw-r--r--packages/SettingsLib/res/drawable/ic_mobile_level_list.xml46
-rw-r--r--packages/SettingsLib/res/drawable/ic_wifi_0.xml37
-rw-r--r--packages/SettingsLib/res/drawable/ic_wifi_0_error.xml28
-rw-r--r--packages/SettingsLib/res/drawable/ic_wifi_1.xml36
-rw-r--r--packages/SettingsLib/res/drawable/ic_wifi_1_error.xml27
-rw-r--r--packages/SettingsLib/res/drawable/ic_wifi_2.xml35
-rw-r--r--packages/SettingsLib/res/drawable/ic_wifi_2_error.xml26
-rw-r--r--packages/SettingsLib/res/drawable/ic_wifi_3.xml34
-rw-r--r--packages/SettingsLib/res/drawable/ic_wifi_3_error.xml25
-rw-r--r--packages/SettingsLib/res/drawable/ic_wifi_4.xml33
-rw-r--r--packages/SettingsLib/res/drawable/ic_wifi_4_error.xml24
-rw-r--r--packages/SettingsLib/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java35
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java27
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt76
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt34
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java60
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt45
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt56
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java15
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java12
-rw-r--r--packages/SettingsProvider/Android.bp1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java31
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig11
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java210
-rw-r--r--packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java2
-rw-r--r--packages/SystemUI/Android.bp1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig17
-rw-r--r--packages/SystemUI/animation/Android.bp1
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java (renamed from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java)6
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationTargetCompat.java (renamed from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java)4
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt5
-rw-r--r--packages/SystemUI/compose/features/Android.bp1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt12
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt116
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt8
-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.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt20
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/WeatherClockBlueprint.kt10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt72
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt19
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt19
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt17
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VerticalVolumePanelContent.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt78
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt (renamed from packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt)365
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt66
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt14
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt98
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt115
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt46
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt58
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt36
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt43
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt115
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt (renamed from packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt)219
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt163
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt15
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt116
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt117
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt102
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt76
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt)2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt)2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt36
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/domain/interactor/SpatializerInteractorTest.kt92
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractorTest.kt191
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt65
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java219
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java)31
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java135
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt130
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt138
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt119
-rw-r--r--packages/SystemUI/res/drawable/battery_unified_attr_charging.xml26
-rw-r--r--packages/SystemUI/res/drawable/battery_unified_attr_defend.xml26
-rw-r--r--packages/SystemUI/res/drawable/battery_unified_attr_powersave.xml32
-rw-r--r--packages/SystemUI/res/drawable/battery_unified_frame.xml34
-rw-r--r--packages/SystemUI/res/drawable/battery_unified_frame_bg.xml27
-rw-r--r--packages/SystemUI/res/drawable/bg_shutdown_finder_message.xml5
-rw-r--r--packages/SystemUI/res/drawable/ic_finder_active.xml14
-rw-r--r--packages/SystemUI/res/layout/internet_connectivity_dialog.xml5
-rw-r--r--packages/SystemUI/res/layout/internet_list_item.xml5
-rw-r--r--packages/SystemUI/res/layout/shutdown_dialog_finder_active.xml72
-rw-r--r--packages/SystemUI/res/values/dimens.xml25
-rw-r--r--packages/SystemUI/res/values/strings.xml11
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt14
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java26
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/DualToneHandler.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java287
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt102
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt137
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt171
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt280
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt119
-rw-r--r--packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt136
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt164
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt)212
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractor.kt104
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java179
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java117
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java3
-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.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt170
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioAvailabilityModel.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt42
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt103
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java61
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt141
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt)231
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt238
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java93
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt151
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java13
-rw-r--r--packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/SpatializerKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt83
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt2
-rw-r--r--ravenwood/framework-minus-apex-ravenwood-policies.txt2
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java83
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java7
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java6
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java11
-rw-r--r--ravenwood/ravenwood-annotation-allowed-classes.txt1
-rw-r--r--rs/java/android/renderscript/ScriptC.java10
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java10
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java10
-rw-r--r--services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java20
-rw-r--r--services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java7
-rw-r--r--services/companion/java/com/android/server/companion/CompanionApplicationController.java31
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java28
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java3
-rw-r--r--services/companion/java/com/android/server/companion/PersistentDataStore.java11
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java6
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java10
-rw-r--r--services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java2
-rw-r--r--services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java4
-rw-r--r--services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java2
-rw-r--r--services/companion/java/com/android/server/companion/presence/ObservableUuid.java (renamed from services/companion/java/com/android/server/companion/ObservableUuid.java)2
-rw-r--r--services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java (renamed from services/companion/java/com/android/server/companion/ObservableUuidStore.java)22
-rw-r--r--services/companion/java/com/android/server/companion/presence/Utils.java49
-rw-r--r--services/companion/java/com/android/server/companion/utils/DataStoreUtils.java (renamed from services/companion/java/com/android/server/companion/DataStoreUtils.java)4
-rw-r--r--services/companion/java/com/android/server/companion/utils/MetricUtils.java (renamed from services/companion/java/com/android/server/companion/MetricUtils.java)14
-rw-r--r--services/companion/java/com/android/server/companion/utils/PackageUtils.java (renamed from services/companion/java/com/android/server/companion/PackageUtils.java)48
-rw-r--r--services/companion/java/com/android/server/companion/utils/PermissionsUtils.java (renamed from services/companion/java/com/android/server/companion/PermissionsUtils.java)61
-rw-r--r--services/companion/java/com/android/server/companion/utils/RolesUtils.java (renamed from services/companion/java/com/android/server/companion/RolesUtils.java)54
-rw-r--r--services/companion/java/com/android/server/companion/utils/Utils.java (renamed from services/companion/java/com/android/server/companion/Utils.java)26
-rw-r--r--services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java54
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java101
-rw-r--r--services/core/java/com/android/server/am/IntentBindRecord.java8
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java5
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java1
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java3
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java12
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java29
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java53
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java27
-rw-r--r--services/core/java/com/android/server/display/brightness/BrightnessEvent.java36
-rw-r--r--services/core/java/com/android/server/display/brightness/BrightnessReason.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java13
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java17
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java8
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java4
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java1
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java1
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java5
-rw-r--r--services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java1
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java11
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java8
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java140
-rw-r--r--services/core/java/com/android/server/input/KeyboardMetricsCollector.java5
-rw-r--r--services/core/java/com/android/server/input/PointerIconCache.java207
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java42
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java20
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java14
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java4
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java17
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java56
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java34
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java13
-rw-r--r--services/core/java/com/android/server/policy/PermissionPolicyService.java2
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java4
-rw-r--r--services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java5
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java4
-rw-r--r--services/core/java/com/android/server/utils/AnrTimer.java2
-rw-r--r--services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java1
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperCropper.java21
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityAssistInfo.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java15
-rw-r--r--services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java9
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java17
-rw-r--r--services/core/java/com/android/server/wm/AppTaskImpl.java4
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java359
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java23
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java15
-rw-r--r--services/core/java/com/android/server/wm/DragDropController.java4
-rw-r--r--services/core/java/com/android/server/wm/DragState.java14
-rw-r--r--services/core/java/com/android/server/wm/LaunchParamsController.java5
-rw-r--r--services/core/java/com/android/server/wm/OWNERS2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java37
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerUi.java50
-rw-r--r--services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java6
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderGetSession.java18
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java12
-rw-r--r--services/fakes/Android.bp20
-rw-r--r--services/fakes/java/com/android/server/FakeClipboardService.java165
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java31
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java69
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java23
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java6
-rw-r--r--services/tests/dreamservicetests/Android.bp5
-rw-r--r--services/tests/dreamservicetests/AndroidManifest.xml1
-rw-r--r--services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java30
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java102
-rw-r--r--services/tests/servicestests/Android.bp1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterInputTest.kt25
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java1247
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java49
-rw-r--r--services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java10
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java14
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java21
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java224
-rw-r--r--services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java30
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java46
-rw-r--r--services/usb/java/com/android/server/usb/UsbService.java64
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java144
-rw-r--r--telephony/java/android/telephony/SubscriptionInfo.java6
-rw-r--r--telephony/java/android/telephony/ims/feature/MmTelFeature.java10
-rw-r--r--tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java4
-rw-r--r--tools/hoststubgen/TEST_MAPPING3
-rwxr-xr-xtools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py1
694 files changed, 18366 insertions, 5554 deletions
diff --git a/INPUT_OWNERS b/INPUT_OWNERS
index 44b2f3805495..06ead06fc13a 100644
--- a/INPUT_OWNERS
+++ b/INPUT_OWNERS
@@ -1,4 +1,6 @@
# Bug component: 136048
+arpitks@google.com
+asmitapoddar@google.com
hcutts@google.com
joseprio@google.com
michaelwr@google.com
diff --git a/OWNERS b/OWNERS
index 6bab92be1582..7ceca321283b 100644
--- a/OWNERS
+++ b/OWNERS
@@ -32,6 +32,7 @@ per-file **.bp,**.mk = hansson@google.com, joeo@google.com, lamontjones@google.c
per-file TestProtoLibraries.bp = file:platform/platform_testing:/libraries/health/OWNERS
per-file TestProtoLibraries.bp = file:platform/tools/tradefederation:/OWNERS
+per-file INPUT_OWNERS = file:/INPUT_OWNERS
per-file ZYGOTE_OWNERS = file:/ZYGOTE_OWNERS
per-file SQLITE_OWNERS = file:/SQLITE_OWNERS
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 4e360759c137..c73e04896173 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -145,6 +145,16 @@ java_library {
}
java_library {
+ name: "services.fakes.ravenwood-jarjar",
+ installable: false,
+ srcs: [":services.fakes-sources"],
+ libs: [
+ "services.core.ravenwood",
+ ],
+ jarjar_rules: ":ravenwood-services-jarjar-rules",
+}
+
+java_library {
name: "mockito-ravenwood-prebuilt",
installable: false,
static_libs: [
@@ -189,6 +199,7 @@ android_ravenwood_libgroup {
"ravenwood-helper-runtime",
"hoststubgen-helper-runtime.ravenwood",
"services.core.ravenwood-jarjar",
+ "services.fakes.ravenwood-jarjar",
// Provide runtime versions of utils linked in below
"junit",
diff --git a/apex/jobscheduler/service/aconfig/device_idle.aconfig b/apex/jobscheduler/service/aconfig/device_idle.aconfig
index fc24b3075f14..e4cb5ad81ba0 100644
--- a/apex/jobscheduler/service/aconfig/device_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/device_idle.aconfig
@@ -4,5 +4,5 @@ flag {
name: "disable_wakelocks_in_light_idle"
namespace: "backstage_power"
description: "Disable wakelocks for background apps while Light Device Idle is active"
- bug: "299329948"
+ bug: "326607666"
}
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index ef9ac73d6f8e..5e6d3775f6a2 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -4,7 +4,7 @@ flag {
name: "batch_active_bucket_jobs"
namespace: "backstage_power"
description: "Include jobs in the ACTIVE bucket in the job batching effort. Don't let them run as freely as they're ready."
- bug: "299329948"
+ bug: "326607666"
}
flag {
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index e728a2c55765..d0a1b027ec48 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -183,6 +183,9 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.zone.ZoneOffsetTransition;
+import java.time.zone.ZoneRules;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -194,6 +197,7 @@ import java.util.Set;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/**
@@ -234,8 +238,12 @@ public class AlarmManagerService extends SystemService {
private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY;
- // System property read on some device configurations to initialize time properly.
+ // System properties read on some device configurations to initialize time properly and
+ // perform DST transitions at the bootloader level.
private static final String TIMEOFFSET_PROPERTY = "persist.sys.time.offset";
+ private static final String DST_TRANSITION_PROPERTY = "persist.sys.time.dst_transition";
+ private static final String DST_OFFSET_PROPERTY = "persist.sys.time.dst_offset";
+
private final Intent mBackgroundIntent
= new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
@@ -2200,6 +2208,19 @@ public class AlarmManagerService extends SystemService {
final int gmtOffset = newZone.getOffset(mInjector.getCurrentTimeMillis());
SystemProperties.set(TIMEOFFSET_PROPERTY, String.valueOf(gmtOffset));
+
+ final ZoneRules rules = newZone.toZoneId().getRules();
+ final ZoneOffsetTransition transition = rules.nextTransition(Instant.now());
+ if (null != transition) {
+ // Get the offset between the time after the DST transition and before.
+ final long transitionOffset = TimeUnit.SECONDS.toMillis((
+ transition.getOffsetAfter().getTotalSeconds()
+ - transition.getOffsetBefore().getTotalSeconds()));
+ // Time when the next DST transition is programmed.
+ final long nextTransition = TimeUnit.SECONDS.toMillis(transition.toEpochSecond());
+ SystemProperties.set(DST_TRANSITION_PROPERTY, String.valueOf(nextTransition));
+ SystemProperties.set(DST_OFFSET_PROPERTY, String.valueOf(transitionOffset));
+ }
}
// Clear the default time zone in the system server process. This forces the next call
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 1f5b83ca8ae8..c1add03fa31a 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -458,13 +458,21 @@ java_defaults {
libs: ["stub-annotations"],
}
+java_defaults {
+ name: "android-non-updatable_everything_from_text_defaults",
+ defaults: [
+ "android-non-updatable_from_text_defaults",
+ ],
+ stubs_type: "everything",
+}
+
java_api_library {
name: "android-non-updatable.stubs.from-text",
api_surface: "public",
api_contributions: [
"api-stubs-docs-non-updatable.api.contribution",
],
- defaults: ["android-non-updatable_from_text_defaults"],
+ defaults: ["android-non-updatable_everything_from_text_defaults"],
full_api_surface_stub: "android_stubs_current.from-text",
}
@@ -475,7 +483,7 @@ java_api_library {
"api-stubs-docs-non-updatable.api.contribution",
"system-api-stubs-docs-non-updatable.api.contribution",
],
- defaults: ["android-non-updatable_from_text_defaults"],
+ defaults: ["android-non-updatable_everything_from_text_defaults"],
full_api_surface_stub: "android_system_stubs_current.from-text",
}
@@ -487,7 +495,7 @@ java_api_library {
"system-api-stubs-docs-non-updatable.api.contribution",
"test-api-stubs-docs-non-updatable.api.contribution",
],
- defaults: ["android-non-updatable_from_text_defaults"],
+ defaults: ["android-non-updatable_everything_from_text_defaults"],
full_api_surface_stub: "android_test_stubs_current.from-text",
}
@@ -499,7 +507,7 @@ java_api_library {
"system-api-stubs-docs-non-updatable.api.contribution",
"module-lib-api-stubs-docs-non-updatable.api.contribution",
],
- defaults: ["android-non-updatable_from_text_defaults"],
+ defaults: ["android-non-updatable_everything_from_text_defaults"],
full_api_surface_stub: "android_module_lib_stubs_current_full.from-text",
}
@@ -515,7 +523,7 @@ java_api_library {
"test-api-stubs-docs-non-updatable.api.contribution",
"module-lib-api-stubs-docs-non-updatable.api.contribution",
],
- defaults: ["android-non-updatable_from_text_defaults"],
+ defaults: ["android-non-updatable_everything_from_text_defaults"],
full_api_surface_stub: "android_test_module_lib_stubs_current.from-text",
// This module is only used for hiddenapi, and other modules should not
@@ -836,6 +844,7 @@ java_api_library {
],
visibility: ["//visibility:public"],
enable_validation: false,
+ stubs_type: "everything",
}
java_api_library {
@@ -852,6 +861,7 @@ java_api_library {
],
visibility: ["//visibility:public"],
enable_validation: false,
+ stubs_type: "everything",
}
java_api_library {
@@ -870,6 +880,7 @@ java_api_library {
],
visibility: ["//visibility:public"],
enable_validation: false,
+ stubs_type: "everything",
}
java_api_library {
@@ -888,6 +899,7 @@ java_api_library {
"system-api-stubs-docs-non-updatable.api.contribution",
],
enable_validation: false,
+ stubs_type: "everything",
}
java_api_library {
@@ -908,6 +920,7 @@ java_api_library {
],
visibility: ["//visibility:public"],
enable_validation: false,
+ stubs_type: "everything",
}
java_api_library {
@@ -922,6 +935,7 @@ java_api_library {
],
visibility: ["//visibility:public"],
enable_validation: false,
+ stubs_type: "everything",
}
java_api_library {
@@ -947,6 +961,7 @@ java_api_library {
"//visibility:private",
],
enable_validation: false,
+ stubs_type: "everything",
}
java_api_library {
@@ -964,6 +979,7 @@ java_api_library {
],
visibility: ["//visibility:public"],
enable_validation: false,
+ stubs_type: "everything",
}
////////////////////////////////////////////////////////////////////////
diff --git a/boot/Android.bp b/boot/Android.bp
index cdfa7c80bc93..f60bb9e7149a 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -31,6 +31,7 @@ soong_config_module_type {
"car_bootclasspath_fragment",
"nfc_apex_bootclasspath_fragment",
"release_crashrecovery_module",
+ "release_package_profiling_module",
],
properties: [
"fragments",
@@ -122,10 +123,6 @@ custom_platform_bootclasspath {
module: "com.android.permission-bootclasspath-fragment",
},
{
- apex: "com.android.profiling",
- module: "com.android.profiling-bootclasspath-fragment",
- },
- {
apex: "com.android.scheduling",
module: "com.android.scheduling-bootclasspath-fragment",
},
@@ -179,6 +176,15 @@ custom_platform_bootclasspath {
},
],
},
+ release_package_profiling_module: {
+ fragments: [
+ // only used if profiling is enabled.
+ {
+ apex: "com.android.profiling",
+ module: "com.android.profiling-bootclasspath-fragment",
+ },
+ ],
+ },
},
// Additional information needed by hidden api processing.
diff --git a/core/api/current.txt b/core/api/current.txt
index 386396c67c71..b4c3f44b4ec4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -203,7 +203,7 @@ package android {
field public static final String MANAGE_DEVICE_POLICY_SYSTEM_APPS = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_APPS";
field public static final String MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS";
field public static final String MANAGE_DEVICE_POLICY_SYSTEM_UPDATES = "android.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES";
- field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String MANAGE_DEVICE_POLICY_THREAD_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK";
+ field @FlaggedApi("com.android.net.thread.platform.flags.thread_user_restriction_enabled") public static final String MANAGE_DEVICE_POLICY_THREAD_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK";
field public static final String MANAGE_DEVICE_POLICY_TIME = "android.permission.MANAGE_DEVICE_POLICY_TIME";
field public static final String MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING = "android.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING";
field public static final String MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER = "android.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER";
@@ -691,7 +691,6 @@ package android {
field public static final int defaultHeight = 16844021; // 0x10104f5
field @FlaggedApi("android.content.res.default_locale") public static final int defaultLocale;
field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504
- field @FlaggedApi("android.nfc.Flags.FLAG_OBSERVE_MODE") public static final int defaultToObserveMode;
field public static final int defaultValue = 16843245; // 0x10101ed
field public static final int defaultWidth = 16844020; // 0x10104f4
field public static final int delay = 16843212; // 0x10101cc
@@ -1501,6 +1500,7 @@ package android {
field public static final int shortcutId = 16844072; // 0x1010528
field public static final int shortcutLongLabel = 16844074; // 0x101052a
field public static final int shortcutShortLabel = 16844073; // 0x1010529
+ field @FlaggedApi("android.nfc.Flags.FLAG_OBSERVE_MODE") public static final int shouldDefaultToObserveMode;
field public static final int shouldDisableView = 16843246; // 0x10101ee
field public static final int shouldUseDefaultUnfoldTransition = 16844364; // 0x101064c
field public static final int showAsAction = 16843481; // 0x10102d9
@@ -3350,7 +3350,6 @@ package android.accessibilityservice {
method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method public boolean clearCache();
method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
- method @FlaggedApi("android.view.accessibility.braille_display_hid") public void clearTestBrailleDisplayController();
method public final void disableSelf();
method public final boolean dispatchGesture(@NonNull android.accessibilityservice.GestureDescription, @Nullable android.accessibilityservice.AccessibilityService.GestureResultCallback, @Nullable android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
@@ -3386,7 +3385,6 @@ package android.accessibilityservice {
method public boolean setCacheEnabled(boolean);
method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region);
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
- method @FlaggedApi("android.view.accessibility.braille_display_hid") public void setTestBrailleDisplayController(@NonNull android.accessibilityservice.BrailleDisplayController);
method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);
method public void takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
method public void takeScreenshotOfWindow(int, @NonNull java.util.concurrent.Executor, @NonNull android.accessibilityservice.AccessibilityService.TakeScreenshotCallback);
@@ -13218,7 +13216,7 @@ package android.content.pm {
field public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio.access";
field public static final String FEATURE_TELEPHONY_SUBSCRIPTION = "android.hardware.telephony.subscription";
field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
- field @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
+ field @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct";
@@ -13510,7 +13508,7 @@ package android.content.pm {
field public static final int FLAG_USE_APP_ZYGOTE = 8; // 0x8
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CAMERA}, anyOf={android.Manifest.permission.CAMERA}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CAMERA = 64; // 0x40
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE}, anyOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.CHANGE_NETWORK_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, android.Manifest.permission.NFC, android.Manifest.permission.TRANSMIT_IR, android.Manifest.permission.UWB_RANGING}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16; // 0x10
- field @Deprecated @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
+ field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1; // 0x1
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_HEALTH}, anyOf={android.Manifest.permission.ACTIVITY_RECOGNITION, android.Manifest.permission.BODY_SENSORS, android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS}) public static final int FOREGROUND_SERVICE_TYPE_HEALTH = 256; // 0x100
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
@@ -34086,7 +34084,7 @@ package android.os {
field @FlaggedApi("android.app.admin.flags.esim_management_enabled") public static final String DISALLOW_SIM_GLOBALLY = "no_sim_globally";
field public static final String DISALLOW_SMS = "no_sms";
field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
- field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
+ field @FlaggedApi("com.android.net.thread.platform.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
field public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password";
field public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
@@ -35817,54 +35815,6 @@ package android.provider {
field public static final String LONGITUDE = "longitude";
}
- @FlaggedApi("android.provider.user_keys") public final class ContactKeysManager {
- method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.ContactKeysManager.ContactKey> getAllContactKeys(@NonNull String);
- method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.ContactKeysManager.SelfKey> getAllSelfKeys();
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public android.provider.ContactKeysManager.ContactKey getContactKey(@NonNull String, @NonNull String, @NonNull String);
- method public static int getMaxKeySizeBytes();
- method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.ContactKeysManager.ContactKey> getOwnerContactKeys(@NonNull String);
- method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.ContactKeysManager.SelfKey> getOwnerSelfKeys();
- method @Nullable @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public android.provider.ContactKeysManager.SelfKey getSelfKey(@NonNull String, @NonNull String);
- method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean removeContactKey(@NonNull String, @NonNull String, @NonNull String);
- method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean removeSelfKey(@NonNull String, @NonNull String);
- method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
- method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
- method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public void updateOrInsertContactKey(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
- method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateOrInsertSelfKey(@NonNull String, @NonNull String, @NonNull byte[]);
- method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, int);
- field public static final int VERIFICATION_STATE_UNVERIFIED = 0; // 0x0
- field public static final int VERIFICATION_STATE_VERIFICATION_FAILED = 1; // 0x1
- field public static final int VERIFICATION_STATE_VERIFIED = 2; // 0x2
- }
-
- public static final class ContactKeysManager.ContactKey implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public String getAccountId();
- method @Nullable public String getDeviceId();
- method @Nullable public String getDisplayName();
- method @Nullable public String getEmailAddress();
- method @Nullable public byte[] getKeyValue();
- method public int getLocalVerificationState();
- method @NonNull public String getOwnerPackageName();
- method @Nullable public String getPhoneNumber();
- method public int getRemoteVerificationState();
- method public long getTimeUpdated();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.provider.ContactKeysManager.ContactKey> CREATOR;
- }
-
- public static final class ContactKeysManager.SelfKey implements android.os.Parcelable {
- method public int describeContents();
- method @NonNull public String getAccountId();
- method @Nullable public String getDeviceId();
- method @Nullable public byte[] getKeyValue();
- method @NonNull public String getOwnerPackageName();
- method public int getRemoteVerificationState();
- method public long getTimeUpdated();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.provider.ContactKeysManager.SelfKey> CREATOR;
- }
-
@Deprecated public class Contacts {
field @Deprecated public static final String AUTHORITY = "contacts";
field @Deprecated public static final android.net.Uri CONTENT_URI;
@@ -37165,6 +37115,54 @@ package android.provider {
method public final int update(android.net.Uri, android.content.ContentValues, String, String[]);
}
+ @FlaggedApi("android.provider.user_keys") public final class E2eeContactKeysManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.E2eeContactKeysManager.E2eeContactKey> getAllE2eeContactKeys(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.E2eeContactKeysManager.E2eeSelfKey> getAllE2eeSelfKeys();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public android.provider.E2eeContactKeysManager.E2eeContactKey getE2eeContactKey(@NonNull String, @NonNull String, @NonNull String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public android.provider.E2eeContactKeysManager.E2eeSelfKey getE2eeSelfKey(@NonNull String, @NonNull String);
+ method public static int getMaxKeySizeBytes();
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.E2eeContactKeysManager.E2eeContactKey> getOwnerE2eeContactKeys(@NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_CONTACTS) public java.util.List<android.provider.E2eeContactKeysManager.E2eeSelfKey> getOwnerE2eeSelfKeys();
+ method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean removeE2eeContactKey(@NonNull String, @NonNull String, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean removeE2eeSelfKey(@NonNull String, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateE2eeContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateE2eeContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateE2eeSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public void updateOrInsertE2eeContactKey(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+ method @RequiresPermission(android.Manifest.permission.WRITE_CONTACTS) public boolean updateOrInsertE2eeSelfKey(@NonNull String, @NonNull String, @NonNull byte[]);
+ field public static final int VERIFICATION_STATE_UNVERIFIED = 0; // 0x0
+ field public static final int VERIFICATION_STATE_VERIFICATION_FAILED = 1; // 0x1
+ field public static final int VERIFICATION_STATE_VERIFIED = 2; // 0x2
+ }
+
+ public static final class E2eeContactKeysManager.E2eeContactKey implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getAccountId();
+ method @Nullable public String getDeviceId();
+ method @Nullable public String getDisplayName();
+ method @Nullable public String getEmailAddress();
+ method @Nullable public byte[] getKeyValue();
+ method public int getLocalVerificationState();
+ method @NonNull public String getOwnerPackageName();
+ method @Nullable public String getPhoneNumber();
+ method public int getRemoteVerificationState();
+ method public long getTimeUpdated();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.provider.E2eeContactKeysManager.E2eeContactKey> CREATOR;
+ }
+
+ public static final class E2eeContactKeysManager.E2eeSelfKey implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getAccountId();
+ method @Nullable public String getDeviceId();
+ method @Nullable public byte[] getKeyValue();
+ method @NonNull public String getOwnerPackageName();
+ method public int getRemoteVerificationState();
+ method public long getTimeUpdated();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.provider.E2eeContactKeysManager.E2eeSelfKey> CREATOR;
+ }
+
@Deprecated public final class FontRequest {
ctor @Deprecated public FontRequest(@NonNull String, @NonNull String, @NonNull String);
ctor @Deprecated public FontRequest(@NonNull String, @NonNull String, @NonNull String, @NonNull java.util.List<java.util.List<byte[]>>);
@@ -39801,6 +39799,7 @@ package android.security.keystore {
method @Deprecated public boolean isInsideSecureHardware();
method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isTrustedUserPresenceRequired();
+ method @FlaggedApi("android.security.keyinfo_unlocked_device_required") public boolean isUnlockedDeviceRequired();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
method public boolean isUserAuthenticationValidWhileOnBody();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d9b5d56544d6..1718452548b3 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -327,7 +327,7 @@ package android {
field public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
- field @FlaggedApi("com.android.net.flags.register_nsd_offload_engine") public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE";
+ field @FlaggedApi("android.net.platform.flags.register_nsd_offload_engine") public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE";
field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM";
field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER";
@@ -388,7 +388,7 @@ package android {
field public static final String SYSTEM_APPLICATION_OVERLAY = "android.permission.SYSTEM_APPLICATION_OVERLAY";
field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
- field @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") public static final String THREAD_NETWORK_PRIVILEGED = "android.permission.THREAD_NETWORK_PRIVILEGED";
+ field @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") public static final String THREAD_NETWORK_PRIVILEGED = "android.permission.THREAD_NETWORK_PRIVILEGED";
field public static final String TIS_EXTENSION_INTERFACE = "android.permission.TIS_EXTENSION_INTERFACE";
field public static final String TOGGLE_AUTOMOTIVE_PROJECTION = "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION";
field public static final String TRIGGER_LOST_MODE = "android.permission.TRIGGER_LOST_MODE";
@@ -1647,14 +1647,12 @@ package android.app.ambientcontext {
method public int getDensityLevel();
method @NonNull public java.time.Instant getEndTime();
method public int getEventType();
- method @FlaggedApi("android.app.ambient_heart_rate") @IntRange(from=0xffffffff) public int getRatePerMinute();
method @NonNull public java.time.Instant getStartTime();
method @NonNull public android.os.PersistableBundle getVendorData();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEvent> CREATOR;
field public static final int EVENT_BACK_DOUBLE_TAP = 3; // 0x3
field public static final int EVENT_COUGH = 1; // 0x1
- field @FlaggedApi("android.app.ambient_heart_rate") public static final int EVENT_HEART_RATE = 4; // 0x4
field public static final int EVENT_SNORE = 2; // 0x2
field public static final int EVENT_UNKNOWN = 0; // 0x0
field public static final int EVENT_VENDOR_WEARABLE_START = 100000; // 0x186a0
@@ -1665,7 +1663,6 @@ package android.app.ambientcontext {
field public static final int LEVEL_MEDIUM_HIGH = 4; // 0x4
field public static final int LEVEL_MEDIUM_LOW = 2; // 0x2
field public static final int LEVEL_UNKNOWN = 0; // 0x0
- field @FlaggedApi("android.app.ambient_heart_rate") public static final int RATE_PER_MINUTE_UNKNOWN = -1; // 0xffffffff
}
public static final class AmbientContextEvent.Builder {
@@ -1675,7 +1672,6 @@ package android.app.ambientcontext {
method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setDensityLevel(int);
method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEndTime(@NonNull java.time.Instant);
method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEventType(int);
- method @FlaggedApi("android.app.ambient_heart_rate") @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setRatePerMinute(@IntRange(from=0xffffffff) int);
method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setStartTime(@NonNull java.time.Instant);
method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setVendorData(@NonNull android.os.PersistableBundle);
}
@@ -3787,7 +3783,7 @@ package android.content {
field public static final String SYSTEM_CONFIG_SERVICE = "system_config";
field public static final String SYSTEM_UPDATE_SERVICE = "system_update";
field public static final String TETHERING_SERVICE = "tethering";
- field @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") public static final String THREAD_NETWORK_SERVICE = "thread_network";
+ field @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") public static final String THREAD_NETWORK_SERVICE = "thread_network";
field public static final String TIME_MANAGER_SERVICE = "time_manager";
field public static final String TRANSLATION_MANAGER_SERVICE = "translation";
field public static final String UI_TRANSLATION_SERVICE = "ui_translation";
@@ -10422,7 +10418,6 @@ package android.nfc.cardemulation {
@FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String, boolean);
- method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean defaultToObserveMode();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
@@ -10452,9 +10447,10 @@ package android.nfc.cardemulation {
method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setCategoryOtherServiceEnabled(boolean);
- method @FlaggedApi("android.nfc.nfc_observe_mode") public void setDefaultToObserveMode(boolean);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setDynamicAidGroup(@NonNull android.nfc.cardemulation.AidGroup);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void setOffHostSecureElement(@NonNull String);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public void setShouldDefaultToObserveMode(boolean);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean shouldDefaultToObserveMode();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void writeToParcel(@NonNull android.os.Parcel, int);
field @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.ApduServiceInfo> CREATOR;
}
@@ -11708,12 +11704,6 @@ package android.provider {
field public static final int ERROR_UNKNOWN = 0; // 0x0
}
- @FlaggedApi("android.provider.user_keys") public final class ContactKeysManager {
- method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
- method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
- method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
- }
-
@Deprecated public static final class ContactsContract.MetadataSync implements android.provider.BaseColumns android.provider.ContactsContract.MetadataSyncColumns {
field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_metadata";
field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/contact_metadata";
@@ -11771,6 +11761,12 @@ package android.provider {
field public static final int FLAG_REMOVABLE_USB = 524288; // 0x80000
}
+ @FlaggedApi("android.provider.user_keys") public final class E2eeContactKeysManager {
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateE2eeContactKeyLocalVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateE2eeContactKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, @NonNull String, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS, android.Manifest.permission.WRITE_CONTACTS}) public boolean updateE2eeSelfKeyRemoteVerificationState(@NonNull String, @NonNull String, @NonNull String, int);
+ }
+
public abstract class SearchIndexableData {
ctor public SearchIndexableData();
ctor public SearchIndexableData(android.content.Context);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index cdf232cd71ee..af40c3d658a7 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -251,6 +251,7 @@ package android.app {
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void getHistoricalOpsFromDiskRaw(@NonNull android.app.AppOpsManager.HistoricalOpsRequest, @Nullable java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.AppOpsManager.HistoricalOps>);
method public static int getNumOps();
method public boolean isOperationActive(int, int, String);
+ method public int noteOpNoThrow(int, @NonNull android.content.AttributionSource, @Nullable String);
method @RequiresPermission("android.permission.MANAGE_APPOPS") public void offsetHistory(long);
method public static String opToPermission(int);
method public static int permissionToOpCode(String);
@@ -316,9 +317,6 @@ package android.app {
}
public class ComponentOptions {
- field public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1; // 0x1
- field public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; // 0x2
- field public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0; // 0x0
}
public class DownloadManager {
@@ -2033,41 +2031,6 @@ package android.media {
method public android.media.PlaybackParams setAudioStretchMode(int);
}
- @FlaggedApi("android.os.vibrator.haptics_customization_enabled") public final class RingtoneSelection {
- method @NonNull public static android.media.RingtoneSelection fromUri(@Nullable android.net.Uri, int);
- method public int getSoundSource();
- method @Nullable public android.net.Uri getSoundUri();
- method public int getVibrationSource();
- method @Nullable public android.net.Uri getVibrationUri();
- method public static boolean isRingtoneSelectionUri(@Nullable android.net.Uri);
- method @NonNull public android.net.Uri toUri();
- field public static final String DEFAULT_SELECTION_URI_STRING = "content://media/ringtone";
- field public static final int FROM_URI_RINGTONE_SELECTION_ONLY = 3; // 0x3
- field public static final int FROM_URI_RINGTONE_SELECTION_OR_SOUND = 1; // 0x1
- field public static final int FROM_URI_RINGTONE_SELECTION_OR_VIBRATION = 2; // 0x2
- field public static final int SOUND_SOURCE_OFF = 1; // 0x1
- field public static final int SOUND_SOURCE_SYSTEM_DEFAULT = 3; // 0x3
- field public static final int SOUND_SOURCE_UNSPECIFIED = 0; // 0x0
- field public static final int SOUND_SOURCE_URI = 2; // 0x2
- field public static final int VIBRATION_SOURCE_APPLICATION_DEFAULT = 4; // 0x4
- field public static final int VIBRATION_SOURCE_AUDIO_CHANNEL = 10; // 0xa
- field public static final int VIBRATION_SOURCE_HAPTIC_GENERATOR = 11; // 0xb
- field public static final int VIBRATION_SOURCE_OFF = 1; // 0x1
- field public static final int VIBRATION_SOURCE_SYSTEM_DEFAULT = 3; // 0x3
- field public static final int VIBRATION_SOURCE_UNSPECIFIED = 0; // 0x0
- field public static final int VIBRATION_SOURCE_URI = 2; // 0x2
- }
-
- @FlaggedApi("android.os.vibrator.haptics_customization_enabled") public static final class RingtoneSelection.Builder {
- ctor public RingtoneSelection.Builder();
- ctor public RingtoneSelection.Builder(@NonNull android.media.RingtoneSelection);
- method @NonNull public android.media.RingtoneSelection build();
- method @NonNull public android.media.RingtoneSelection.Builder setSoundSource(int);
- method @NonNull public android.media.RingtoneSelection.Builder setSoundSource(@NonNull android.net.Uri);
- method @NonNull public android.media.RingtoneSelection.Builder setVibrationSource(int);
- method @NonNull public android.media.RingtoneSelection.Builder setVibrationSource(@NonNull android.net.Uri);
- }
-
public static final class VolumeShaper.Configuration.Builder {
method @NonNull public android.media.VolumeShaper.Configuration.Builder setOptionFlags(int);
}
@@ -3966,6 +3929,7 @@ package android.view.inputmethod {
}
public final class InputMethodInfo implements android.os.Parcelable {
+ ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, @NonNull String);
ctor @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, boolean, @NonNull String);
ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
field public static final int COMPONENT_NAME_MAX_LENGTH = 1000; // 0x3e8
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index 658ddbf0abfd..685ea63cb432 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -2055,14 +2055,8 @@ UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_DEFAULT:
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_DEFAULT
UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_OFF:
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_OFF
-UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_SYSTEM_DEFAULT:
- New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_SYSTEM_DEFAULT
-UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_UNSPECIFIED:
- New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_UNSPECIFIED
UnflaggedApi: android.media.RingtoneSelection#SOUND_SOURCE_URI:
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.SOUND_SOURCE_URI
-UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_APPLICATION_DEFAULT:
- New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_APPLICATION_DEFAULT
UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_APPLICATION_PROVIDED:
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_APPLICATION_PROVIDED
UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_AUDIO_CHANNEL:
@@ -2073,10 +2067,6 @@ UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_HAPTIC_GENERATOR:
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_HAPTIC_GENERATOR
UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_OFF:
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_OFF
-UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_SYSTEM_DEFAULT:
- New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_SYSTEM_DEFAULT
-UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_UNSPECIFIED:
- New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_UNSPECIFIED
UnflaggedApi: android.media.RingtoneSelection#VIBRATION_SOURCE_URI:
New API must be flagged with @FlaggedApi: field android.media.RingtoneSelection.VIBRATION_SOURCE_URI
UnflaggedApi: android.media.RingtoneSelection#fromUri(android.net.Uri, int):
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 184421518d32..ab1c9a4ef48d 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -528,6 +528,13 @@ filegroup {
],
}
+java_library {
+ name: "protolog-group",
+ srcs: [
+ "com/android/internal/protolog/common/IProtoLogGroup.java",
+ ],
+}
+
// PackageManager common
filegroup {
name: "framework-pm-common-shared-srcs",
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 42c32723fd72..d70fa19a4468 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -81,7 +81,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -853,7 +852,6 @@ public abstract class AccessibilityService extends Service {
private final SparseArray<AccessibilityButtonController> mAccessibilityButtonControllers =
new SparseArray<>(0);
private BrailleDisplayController mBrailleDisplayController;
- private BrailleDisplayController mTestBrailleDisplayController;
private int mGestureStatusCallbackSequence;
@@ -3650,46 +3648,10 @@ public abstract class AccessibilityService extends Service {
public BrailleDisplayController getBrailleDisplayController() {
BrailleDisplayController.checkApiFlagIsEnabled();
synchronized (mLock) {
- if (mTestBrailleDisplayController != null) {
- return mTestBrailleDisplayController;
- }
-
if (mBrailleDisplayController == null) {
mBrailleDisplayController = new BrailleDisplayControllerImpl(this, mLock);
}
return mBrailleDisplayController;
}
}
-
- /**
- * Set the {@link BrailleDisplayController} implementation that will be returned by
- * {@link #getBrailleDisplayController}, to allow this accessibility service to test its
- * interaction with BrailleDisplayController without requiring a real Braille display.
- *
- * <p>For full test fidelity, ensure that this test-only implementation follows the same
- * behavior specified in the documentation for {@link BrailleDisplayController}, including
- * thrown exceptions.
- *
- * @param controller A test-only implementation of {@link BrailleDisplayController}.
- */
- @FlaggedApi(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
- public void setTestBrailleDisplayController(@NonNull BrailleDisplayController controller) {
- BrailleDisplayController.checkApiFlagIsEnabled();
- Objects.requireNonNull(controller);
- synchronized (mLock) {
- mTestBrailleDisplayController = controller;
- }
- }
-
- /**
- * Clears the {@link BrailleDisplayController} previously set by
- * {@link #setTestBrailleDisplayController}.
- */
- @FlaggedApi(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID)
- public void clearTestBrailleDisplayController() {
- BrailleDisplayController.checkApiFlagIsEnabled();
- synchronized (mLock) {
- mTestBrailleDisplayController = null;
- }
- }
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 57c67be7e625..63cafdc6c855 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -9601,9 +9601,9 @@ public class Activity extends ContextThemeWrapper
* Specifies whether the activities below this one in the task can also start other activities
* or finish the task.
* <p>
- * Starting from Target SDK Level {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, apps
- * are blocked from starting new activities or finishing their task unless the top activity of
- * such task belong to the same UID for security reasons.
+ * Starting from Target SDK Level {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, apps
+ * may be blocked from starting new activities or finishing their task unless the top activity
+ * of such task belong to the same UID for security reasons.
* <p>
* Setting this flag to {@code true} will allow the launching app to ignore the restriction if
* this activity is on top. Apps matching the UID of this activity are always exempt.
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 2a2c5f05f122..e094ac61b500 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -93,15 +93,30 @@ public class ActivityOptions extends ComponentOptions {
*/
public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
- /** No explicit value chosen. The system will decide whether to grant privileges. */
- public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED =
- ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
- /** Allow the {@link PendingIntent} to use the background activity start privileges. */
- public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED =
- ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
- /** Deny the {@link PendingIntent} to use the background activity start privileges. */
- public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED =
- ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ /** Enumeration of background activity start modes.
+ *
+ * These define if an app wants to grant it's background activity start privileges to a
+ * {@link PendingIntent}.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = {
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
+ MODE_BACKGROUND_ACTIVITY_START_DENIED})
+ public @interface BackgroundActivityStartMode {}
+ /**
+ * No explicit value chosen. The system will decide whether to grant privileges.
+ */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0;
+ /**
+ * Allow the {@link PendingIntent} to use the background activity start privileges.
+ */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1;
+ /**
+ * Deny the {@link PendingIntent} to use the background activity start privileges.
+ */
+ public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
/**
* The package name that created the options.
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 22b8d92b507c..41151c0dc647 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -717,7 +717,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
activity.mMainThread.handleActivityConfigurationChanged(
ActivityClientRecord.this, overrideConfig, newDisplayId,
- false /* alwaysReportChange */);
+ mActivityWindowInfo, false /* alwaysReportChange */);
}
@Override
@@ -5998,9 +5998,11 @@ public final class ActivityThread extends ClientTransactionHandler
}
@Override
- public ActivityClientRecord prepareRelaunchActivity(IBinder token,
- List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- int configChanges, MergedConfiguration config, boolean preserveWindow) {
+ public ActivityClientRecord prepareRelaunchActivity(@NonNull IBinder token,
+ @Nullable List<ResultInfo> pendingResults,
+ @Nullable List<ReferrerIntent> pendingNewIntents, int configChanges,
+ @NonNull MergedConfiguration config, boolean preserveWindow,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
ActivityClientRecord target = null;
boolean scheduleRelaunch = false;
@@ -6041,14 +6043,15 @@ public final class ActivityThread extends ClientTransactionHandler
target.createdConfig = config.getGlobalConfiguration();
target.overrideConfig = config.getOverrideConfiguration();
target.pendingConfigChanges |= configChanges;
+ target.mActivityWindowInfo = activityWindowInfo;
}
return scheduleRelaunch ? target : null;
}
@Override
- public void handleRelaunchActivity(ActivityClientRecord tmp,
- PendingTransactionActions pendingActions) {
+ public void handleRelaunchActivity(@NonNull ActivityClientRecord tmp,
+ @NonNull PendingTransactionActions pendingActions) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
@@ -6128,7 +6131,8 @@ public final class ActivityThread extends ClientTransactionHandler
r.activity.mChangingConfigurations = true;
handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
- pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
+ pendingActions, tmp.startsNotResumed, tmp.overrideConfig, tmp.mActivityWindowInfo,
+ "handleRelaunchActivity");
}
void scheduleRelaunchActivity(IBinder token) {
@@ -6183,7 +6187,8 @@ public final class ActivityThread extends ClientTransactionHandler
r.overrideConfig);
final ActivityRelaunchItem activityRelaunchItem = ActivityRelaunchItem.obtain(
r.token, null /* pendingResults */, null /* pendingIntents */,
- 0 /* configChanges */, mergedConfiguration, r.mPreserveWindow);
+ 0 /* configChanges */, mergedConfiguration, r.mPreserveWindow,
+ r.getActivityWindowInfo());
// Make sure to match the existing lifecycle state in the end of the transaction.
final ActivityLifecycleItem lifecycleRequest =
TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
@@ -6194,10 +6199,12 @@ public final class ActivityThread extends ClientTransactionHandler
executeTransaction(transaction);
}
- private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
- List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
- PendingTransactionActions pendingActions, boolean startsNotResumed,
- Configuration overrideConfig, String reason) {
+ private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r, int configChanges,
+ @Nullable List<ResultInfo> pendingResults,
+ @Nullable List<ReferrerIntent> pendingIntents,
+ @NonNull PendingTransactionActions pendingActions, boolean startsNotResumed,
+ @NonNull Configuration overrideConfig, @NonNull ActivityWindowInfo activityWindowInfo,
+ @NonNull String reason) {
// Preserve last used intent, it may be set from Activity#setIntent().
final Intent customIntent = r.activity.mIntent;
// Need to ensure state is saved.
@@ -6230,6 +6237,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
r.startsNotResumed = startsNotResumed;
r.overrideConfig = overrideConfig;
+ r.mActivityWindowInfo = activityWindowInfo;
handleLaunchActivity(r, pendingActions, mLastReportedDeviceId, customIntent);
}
@@ -6651,11 +6659,12 @@ public final class ActivityThread extends ClientTransactionHandler
/**
* Sets the supplied {@code overrideConfig} as pending for the {@code token}. Calling
* this method prevents any calls to
- * {@link #handleActivityConfigurationChanged(ActivityClientRecord, Configuration, int)} from
- * processing any configurations older than {@code overrideConfig}.
+ * {@link #handleActivityConfigurationChanged(ActivityClientRecord, Configuration, int,
+ * ActivityWindowInfo)} from processing any configurations older than {@code overrideConfig}.
*/
@Override
- public void updatePendingActivityConfiguration(IBinder token, Configuration overrideConfig) {
+ public void updatePendingActivityConfiguration(@NonNull IBinder token,
+ @NonNull Configuration overrideConfig) {
synchronized (mPendingOverrideConfigs) {
final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(token);
if (pendingOverrideConfig != null
@@ -6672,9 +6681,10 @@ public final class ActivityThread extends ClientTransactionHandler
}
@Override
- public void handleActivityConfigurationChanged(ActivityClientRecord r,
- @NonNull Configuration overrideConfig, int displayId) {
- handleActivityConfigurationChanged(r, overrideConfig, displayId,
+ public void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
+ @NonNull Configuration overrideConfig, int displayId,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
+ handleActivityConfigurationChanged(r, overrideConfig, displayId, activityWindowInfo,
// This is the only place that uses alwaysReportChange=true. The entry point should
// be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side
// has confirmed the activity should handle the configuration instead of relaunch.
@@ -6692,9 +6702,11 @@ public final class ActivityThread extends ClientTransactionHandler
* @param overrideConfig Activity override config.
* @param displayId Id of the display where activity was moved to, -1 if there was no move and
* value didn't change.
+ * @param activityWindowInfo the window info of the given activity.
*/
- void handleActivityConfigurationChanged(ActivityClientRecord r,
- @NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {
+ void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
+ @NonNull Configuration overrideConfig, int displayId,
+ @NonNull ActivityWindowInfo activityWindowInfo, boolean alwaysReportChange) {
synchronized (mPendingOverrideConfigs) {
final Configuration pendingOverrideConfig = mPendingOverrideConfigs.get(r.token);
if (overrideConfig.isOtherSeqNewer(pendingOverrideConfig)) {
@@ -6727,6 +6739,8 @@ public final class ActivityThread extends ClientTransactionHandler
// Perform updates.
r.overrideConfig = overrideConfig;
+ r.mActivityWindowInfo = activityWindowInfo;
+ // TODO(b/287582673): notify on ActivityWindowInfo change
final ViewRootImpl viewRoot = r.activity.mDecor != null
? r.activity.mDecor.getViewRootImpl() : null;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 39900a03e560..2afc78cb90e6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7800,7 +7800,7 @@ public class AppOpsManager {
}
final List<AppOpsManager.PackageOps> result;
try {
- result = mService.getPackagesForOpsForDevice(opCodes, persistentDeviceId);
+ result = mService.getPackagesForOpsForDevice(opCodes, persistentDeviceId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -8272,14 +8272,24 @@ public class AppOpsManager {
cb = new IAppOpsCallback.Stub() {
public void opChanged(int op, int uid, String packageName,
String persistentDeviceId) {
- if (callback instanceof OnOpChangedInternalListener) {
- ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName,
- persistentDeviceId);
- }
- if (sAppOpInfos[op].name != null) {
-
- callback.onOpChanged(sAppOpInfos[op].name, packageName,
- UserHandle.getUserId(uid), persistentDeviceId);
+ if (Flags.deviceAwarePermissionApisEnabled()) {
+ if (callback instanceof OnOpChangedInternalListener) {
+ ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName,
+ persistentDeviceId);
+ }
+ if (sAppOpInfos[op].name != null) {
+ callback.onOpChanged(sAppOpInfos[op].name, packageName,
+ UserHandle.getUserId(uid), persistentDeviceId);
+ }
+ } else {
+ if (callback instanceof OnOpChangedInternalListener) {
+ ((OnOpChangedInternalListener) callback).onOpChanged(op,
+ packageName);
+ }
+ if (sAppOpInfos[op].name != null) {
+ callback.onOpChanged(sAppOpInfos[op].name, packageName,
+ UserHandle.getUserId(uid));
+ }
}
}
};
@@ -8940,6 +8950,8 @@ public class AppOpsManager {
*
* @hide
*/
+ @TestApi
+ @SuppressLint("UnflaggedApi")
public int noteOpNoThrow(int op, @NonNull AttributionSource attributionSource,
@Nullable String message) {
return noteOpNoThrow(op, attributionSource.getUid(), attributionSource.getPackageName(),
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index f6373d690793..f6ec370478a9 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -377,6 +377,9 @@ public final class AutomaticZenRule implements Parcelable {
* Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes to
* the device behavior that should apply while the rule is active, but are not directly related
* to suppressing notifications (for example: disabling always-on display).
+ *
+ * <p>When updating an existing rule via {@link NotificationManager#updateAutomaticZenRule},
+ * a {@code null} value here means the previous set of effects is retained.
*/
@FlaggedApi(Flags.FLAG_MODES_API)
public void setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
@@ -702,7 +705,15 @@ public final class AutomaticZenRule implements Parcelable {
}
/**
- * Sets the component (service or activity) that owns this rule.
+ * Sets the component name of the
+ * {@link android.service.notification.ConditionProviderService} that manages this rule
+ * (but note that {@link android.service.notification.ConditionProviderService} is
+ * deprecated in favor of using {@link NotificationManager#setAutomaticZenRuleState} to
+ * notify the system about the state of your rule).
+ *
+ * <p>This is exclusive with {@link #setConfigurationActivity}; rules where a configuration
+ * activity is set will not use the component set here to determine whether the rule
+ * should be active.
*/
public @NonNull Builder setOwner(@Nullable ComponentName owner) {
mOwner = owner;
@@ -740,6 +751,11 @@ public final class AutomaticZenRule implements Parcelable {
* information about this rule and/or allows them to configure it. This is required to be
* non-null for rules that are not backed by a
* {@link android.service.notification.ConditionProviderService}.
+ *
+ * <p>This is exclusive with {@link #setOwner}; rules where a configuration
+ * activity is set will not use the
+ * {@link android.service.notification.ConditionProviderService} supplied there to determine
+ * whether the rule should be active.
*/
public @NonNull Builder setConfigurationActivity(
@Nullable ComponentName configurationActivity) {
@@ -749,6 +765,9 @@ public final class AutomaticZenRule implements Parcelable {
/**
* Sets the zen policy.
+ *
+ * <p>When updating an existing rule via {@link NotificationManager#updateAutomaticZenRule},
+ * a {@code null} value here means the previous policy is retained.
*/
public @NonNull Builder setZenPolicy(@Nullable ZenPolicy policy) {
mPolicy = policy;
@@ -759,6 +778,9 @@ public final class AutomaticZenRule implements Parcelable {
* Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes
* to the device behavior that should apply while the rule is active, but are not directly
* related to suppressing notifications (for example: disabling always-on display).
+ *
+ * <p>When updating an existing rule via {@link NotificationManager#updateAutomaticZenRule},
+ * a {@code null} value here means the previous set of effects is retained.
*/
@NonNull
public Builder setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 1b5b0fc5d917..60d622dd78c6 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.app.ActivityOptions.BackgroundActivityStartMode;
+
import android.annotation.CurrentTimeMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -1132,7 +1134,8 @@ public class BroadcastOptions extends ComponentOptions {
@SystemApi
@NonNull
@Override // to narrow down the return type
- public BroadcastOptions setPendingIntentBackgroundActivityStartMode(int state) {
+ public BroadcastOptions setPendingIntentBackgroundActivityStartMode(
+ @BackgroundActivityStartMode int state) {
super.setPendingIntentBackgroundActivityStartMode(state);
return this;
}
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index b30067491a84..b5b3669c1d80 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -30,6 +30,7 @@ import android.content.res.Configuration;
import android.os.IBinder;
import android.util.MergedConfiguration;
import android.view.SurfaceControl;
+import android.window.ActivityWindowInfo;
import android.window.SplashScreenView.SplashScreenViewParcelable;
import android.window.WindowContext;
import android.window.WindowContextInfo;
@@ -166,11 +167,12 @@ public abstract class ClientTransactionHandler {
/** Set pending activity configuration in case it will be updated by other transaction item. */
public abstract void updatePendingActivityConfiguration(@NonNull IBinder token,
- Configuration overrideConfig);
+ @NonNull Configuration overrideConfig);
/** Deliver activity (override) configuration change. */
public abstract void handleActivityConfigurationChanged(@NonNull ActivityClientRecord r,
- Configuration overrideConfig, int displayId);
+ @NonNull Configuration overrideConfig, int displayId,
+ @NonNull ActivityWindowInfo activityWindowInfo);
/** Deliver {@link android.window.WindowContextInfo} change. */
public abstract void handleWindowContextInfoChanged(@NonNull IBinder clientToken,
@@ -232,12 +234,15 @@ public abstract class ClientTransactionHandler {
* @param config New configuration applied to the activity.
* @param preserveWindow Whether the activity should try to reuse the window it created,
* including the decor view after the relaunch.
+ * @param activityWindowInfo Window information about the relaunched Activity.
* @return An initialized instance of {@link ActivityThread.ActivityClientRecord} to use during
* relaunch, or {@code null} if relaunch cancelled.
*/
- public abstract ActivityClientRecord prepareRelaunchActivity(IBinder token,
- List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- int configChanges, MergedConfiguration config, boolean preserveWindow);
+ public abstract ActivityClientRecord prepareRelaunchActivity(@NonNull IBinder token,
+ @Nullable List<ResultInfo> pendingResults,
+ @Nullable List<ReferrerIntent> pendingNewIntents, int configChanges,
+ @NonNull MergedConfiguration config, boolean preserveWindow,
+ @NonNull ActivityWindowInfo activityWindowInfo);
/**
* Perform activity relaunch.
@@ -245,7 +250,7 @@ public abstract class ClientTransactionHandler {
* @param pendingActions Pending actions to be used on later stages of activity transaction.
* */
public abstract void handleRelaunchActivity(@NonNull ActivityClientRecord r,
- PendingTransactionActions pendingActions);
+ @NonNull PendingTransactionActions pendingActions);
/**
* Report that relaunch request was handled.
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index ce16ddf5c05f..397477d72a9a 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -16,16 +16,17 @@
package android.app;
-import android.annotation.IntDef;
+import static android.app.ActivityOptions.BackgroundActivityStartMode;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.os.Bundle;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* Base class for {@link ActivityOptions} and {@link BroadcastOptions}.
* @hide
@@ -56,32 +57,6 @@ public class ComponentOptions {
private @Nullable Boolean mPendingIntentBalAllowed = null;
private boolean mPendingIntentBalAllowedByPermission = false;
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = {
- MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
- MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
- MODE_BACKGROUND_ACTIVITY_START_DENIED})
- public @interface BackgroundActivityStartMode {}
- /**
- * No explicit value chosen. The system will decide whether to grant privileges.
- * @hide
- */
- @TestApi
- public static final int MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED = 0;
- /**
- * Allow the {@link PendingIntent} to use the background activity start privileges.
- * @hide
- */
- @TestApi
- public static final int MODE_BACKGROUND_ACTIVITY_START_ALLOWED = 1;
- /**
- * Deny the {@link PendingIntent} to use the background activity start privileges.
- * @hide
- */
- @TestApi
- public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2;
-
ComponentOptions() {
}
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 7e06735791ff..d1e517bbd03c 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -62,7 +62,6 @@ import android.content.pm.ServiceInfo.ForegroundServiceType;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
-import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -128,14 +127,10 @@ public abstract class ForegroundServiceTypePolicy {
* The FGS type enforcement:
* deprecating the {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC}.
*
- * <p>Starting a FGS with this type from apps with targetSdkVersion
- * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or later will result in a warning
- * in the log.
- *
* @hide
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Disabled
@Overridable
public static final long FGS_TYPE_DATA_SYNC_DEPRECATION_CHANGE_ID = 255039210L;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 26f85f723bd9..1129f9dbdfeb 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7543,6 +7543,7 @@ public class Notification implements Parcelable
/**
* @hide
*/
+ @SuppressWarnings("HiddenAbstractMethod")
public abstract boolean areNotificationsVisiblyDifferent(Style other);
/**
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 08c193f06286..d01626e17f2d 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -212,7 +212,7 @@ import android.permission.PermissionControllerManager;
import android.permission.PermissionManager;
import android.print.IPrintManager;
import android.print.PrintManager;
-import android.provider.ContactKeysManager;
+import android.provider.E2eeContactKeysManager;
import android.safetycenter.SafetyCenterFrameworkInitializer;
import android.scheduling.SchedulingFrameworkInitializer;
import android.security.FileIntegrityManager;
@@ -1608,16 +1608,16 @@ public final class SystemServiceRegistry {
}
});
- registerService(Context.CONTACT_KEYS_SERVICE, ContactKeysManager.class,
- new CachedServiceFetcher<ContactKeysManager>() {
+ registerService(Context.CONTACT_KEYS_SERVICE, E2eeContactKeysManager.class,
+ new CachedServiceFetcher<E2eeContactKeysManager>() {
@Override
- public ContactKeysManager createService(ContextImpl ctx)
+ public E2eeContactKeysManager createService(ContextImpl ctx)
throws ServiceNotFoundException {
if (!android.provider.Flags.userKeys()) {
throw new ServiceNotFoundException(
"ContactKeysManager is not supported");
}
- return new ContactKeysManager(ctx);
+ return new E2eeContactKeysManager(ctx);
}});
// DO NOT do a flag check like this unless the flag is read-only.
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 83fb393da125..02d694436bfe 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -981,12 +981,12 @@ public class WallpaperManager {
/**
* <strong> Important note: </strong>
* <ul>
- * <li>Up to version S, this method requires the
+ * <li>Up to Android 12, this method requires the
* {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
- * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * <li>Starting in Android 13, directly accessing the wallpaper is not possible anymore,
* instead the default system wallpaper is returned
- * (some versions of T may throw a {@code SecurityException}).</li>
- * <li>From version U, this method should not be used
+ * (some versions of Android 13 may throw a {@code SecurityException}).</li>
+ * <li>From Android 14, this method should not be used
* and will always throw a {@code SecurityException}.</li>
* <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
* can still access the real wallpaper on all versions. </li>
@@ -1265,12 +1265,12 @@ public class WallpaperManager {
/**
* <strong> Important note: </strong>
* <ul>
- * <li>Up to version S, this method requires the
+ * <li>Up to Android 12, this method requires the
* {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
- * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * <li>Starting in Android 13, directly accessing the wallpaper is not possible anymore,
* instead the default system wallpaper is returned
- * (some versions of T may throw a {@code SecurityException}).</li>
- * <li>From version U, this method should not be used
+ * (some versions of Android 13 may throw a {@code SecurityException}).</li>
+ * <li>From Android 14, this method should not be used
* and will always throw a {@code SecurityException}.</li>
* <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
* can still access the real wallpaper on all versions. </li>
@@ -1318,12 +1318,12 @@ public class WallpaperManager {
/**
* <strong> Important note: </strong>
* <ul>
- * <li>Up to version S, this method requires the
+ * <li>Up to Android 12, this method requires the
* {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
- * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * <li>Starting in Android 13, directly accessing the wallpaper is not possible anymore,
* instead the default wallpaper is returned
- * (some versions of T may throw a {@code SecurityException}).</li>
- * <li>From version U, this method should not be used
+ * (some versions of Android 13 may throw a {@code SecurityException}).</li>
+ * <li>From Android 14, this method should not be used
* and will always throw a {@code SecurityException}.</li>
* <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
* can still access the real wallpaper on all versions. </li>
@@ -1677,12 +1677,12 @@ public class WallpaperManager {
/**
* <strong> Important note: </strong>
* <ul>
- * <li>Up to version S, this method requires the
+ * <li>Up to Android 12, this method requires the
* {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li>
- * <li>Starting in T, directly accessing the wallpaper is not possible anymore,
+ * <li>Starting in Android 13, directly accessing the wallpaper is not possible anymore,
* instead the default system wallpaper is returned
- * (some versions of T may throw a {@code SecurityException}).</li>
- * <li>From version U, this method should not be used
+ * (some versions of Android 13 may throw a {@code SecurityException}).</li>
+ * <li>From Android 14, this method should not be used
* and will always throw a {@code SecurityException}.</li>
* <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
* can still access the real wallpaper on all versions. </li>
@@ -1904,14 +1904,14 @@ public class WallpaperManager {
* caller doesn't have the appropriate permissions, this returns {@code null}.
*
* <p>
- * Before Android U, this method requires the
+ * For devices running Android 13 or earlier, this method requires the
* {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission.
* </p>
*
* <p>
- * Starting from Android U, in order to use this, apps should declare a {@code <queries>} tag
- * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
- * this method will return {@code null} if the caller doesn't otherwise have
+ * For devices running Android 14 or later, in order to use this, apps should declare a
+ * {@code <queries>} tag with the action {@code "android.service.wallpaper.WallpaperService"}.
+ * Otherwise, this method will return {@code null} if the caller doesn't otherwise have
* <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
* </p>
*/
diff --git a/core/java/android/app/ambient_context.aconfig b/core/java/android/app/ambient_context.aconfig
deleted file mode 100644
index 3f73da216b9f..000000000000
--- a/core/java/android/app/ambient_context.aconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-package: "android.app"
-
-flag {
- namespace: "biometrics_integration"
- name: "ambient_heart_rate"
- description: "Feature flag for adding heart rate api to ambient context."
- bug: "318309481"
-}
diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java
index 5ab7991c6326..13d959c79cd2 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEvent.java
+++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java
@@ -16,9 +16,7 @@
package android.app.ambientcontext;
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcelable;
@@ -70,14 +68,6 @@ public final class AmbientContextEvent implements Parcelable {
public static final int EVENT_BACK_DOUBLE_TAP = 3;
/**
- * The integer indicating a heart rate measurement was done.
- *
- * @see #getRatePerMinute
- */
- @Event @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
- public static final int EVENT_HEART_RATE = 4;
-
- /**
* Integer indicating the start of wearable vendor defined events that can be detected.
* These depend on the vendor implementation.
*/
@@ -89,19 +79,12 @@ public final class AmbientContextEvent implements Parcelable {
*/
public static final String KEY_VENDOR_WEARABLE_EVENT_NAME = "wearable_event_name";
- /**
- * Default value for {@link #getRatePerMinute}. Indicates that the rate of the event is unknown.
- */
- @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
- public static final int RATE_PER_MINUTE_UNKNOWN = -1;
-
/** @hide */
@IntDef(prefix = { "EVENT_" }, value = {
EVENT_UNKNOWN,
EVENT_COUGH,
EVENT_SNORE,
EVENT_BACK_DOUBLE_TAP,
- EVENT_HEART_RATE,
EVENT_VENDOR_WEARABLE_START,
})
@Retention(RetentionPolicy.SOURCE)
@@ -187,16 +170,6 @@ public final class AmbientContextEvent implements Parcelable {
return new PersistableBundle();
}
- /**
- * Rate per minute of the event during the start to end time.
- *
- * @return the rate per minute, or {@link #RATE_PER_MINUTE_UNKNOWN} if the rate is unknown.
- */
- private final @IntRange(from = -1) int mRatePerMinute;
- private static int defaultRatePerMinute() {
- return RATE_PER_MINUTE_UNKNOWN;
- }
-
// Code below generated by codegen v1.0.23.
@@ -206,8 +179,6 @@ public final class AmbientContextEvent implements Parcelable {
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java
- // then manually add @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE) back to flagged
- // APIs.
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -220,7 +191,6 @@ public final class AmbientContextEvent implements Parcelable {
EVENT_COUGH,
EVENT_SNORE,
EVENT_BACK_DOUBLE_TAP,
- EVENT_HEART_RATE,
EVENT_VENDOR_WEARABLE_START
})
@Retention(RetentionPolicy.SOURCE)
@@ -239,8 +209,6 @@ public final class AmbientContextEvent implements Parcelable {
return "EVENT_SNORE";
case EVENT_BACK_DOUBLE_TAP:
return "EVENT_BACK_DOUBLE_TAP";
- case EVENT_HEART_RATE:
- return "EVENT_HEART_RATE";
case EVENT_VENDOR_WEARABLE_START:
return "EVENT_VENDOR_WEARABLE_START";
default: return Integer.toHexString(value);
@@ -287,8 +255,7 @@ public final class AmbientContextEvent implements Parcelable {
@NonNull Instant endTime,
@LevelValue int confidenceLevel,
@LevelValue int densityLevel,
- @NonNull PersistableBundle vendorData,
- @IntRange(from = -1) int ratePerMinute) {
+ @NonNull PersistableBundle vendorData) {
this.mEventType = eventType;
com.android.internal.util.AnnotationValidations.validate(
EventCode.class, null, mEventType);
@@ -307,10 +274,6 @@ public final class AmbientContextEvent implements Parcelable {
this.mVendorData = vendorData;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mVendorData);
- this.mRatePerMinute = ratePerMinute;
- com.android.internal.util.AnnotationValidations.validate(
- IntRange.class, null, mRatePerMinute,
- "from", -1);
// onConstructed(); // You can define this method to get a callback
}
@@ -367,17 +330,6 @@ public final class AmbientContextEvent implements Parcelable {
return mVendorData;
}
- /**
- * Rate per minute of the event during the start to end time.
- *
- * @return the rate per minute, or {@link #RATE_PER_MINUTE_UNKNOWN} if the rate is unknown.
- */
- @DataClass.Generated.Member
- @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
- public @IntRange(from = -1) int getRatePerMinute() {
- return mRatePerMinute;
- }
-
@Override
@DataClass.Generated.Member
public String toString() {
@@ -390,8 +342,7 @@ public final class AmbientContextEvent implements Parcelable {
"endTime = " + mEndTime + ", " +
"confidenceLevel = " + mConfidenceLevel + ", " +
"densityLevel = " + mDensityLevel + ", " +
- "vendorData = " + mVendorData + ", " +
- "ratePerMinute = " + mRatePerMinute +
+ "vendorData = " + mVendorData +
" }";
}
@@ -429,7 +380,6 @@ public final class AmbientContextEvent implements Parcelable {
dest.writeInt(mConfidenceLevel);
dest.writeInt(mDensityLevel);
dest.writeTypedObject(mVendorData, flags);
- dest.writeInt(mRatePerMinute);
}
@Override
@@ -449,7 +399,6 @@ public final class AmbientContextEvent implements Parcelable {
int confidenceLevel = in.readInt();
int densityLevel = in.readInt();
PersistableBundle vendorData = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
- int ratePerMinute = in.readInt();
this.mEventType = eventType;
com.android.internal.util.AnnotationValidations.validate(
@@ -469,10 +418,6 @@ public final class AmbientContextEvent implements Parcelable {
this.mVendorData = vendorData;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mVendorData);
- this.mRatePerMinute = ratePerMinute;
- com.android.internal.util.AnnotationValidations.validate(
- IntRange.class, null, mRatePerMinute,
- "from", -1);
// onConstructed(); // You can define this method to get a callback
}
@@ -504,7 +449,6 @@ public final class AmbientContextEvent implements Parcelable {
private @LevelValue int mConfidenceLevel;
private @LevelValue int mDensityLevel;
private @NonNull PersistableBundle mVendorData;
- private @IntRange(from = -1) int mRatePerMinute;
private long mBuilderFieldsSet = 0L;
@@ -581,22 +525,10 @@ public final class AmbientContextEvent implements Parcelable {
return this;
}
- /**
- * Rate per minute of the event during the start to end time.
- */
- @DataClass.Generated.Member
- @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
- public @NonNull Builder setRatePerMinute(@IntRange(from = -1) int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x40;
- mRatePerMinute = value;
- return this;
- }
-
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull AmbientContextEvent build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x80; // Mark builder used
+ mBuilderFieldsSet |= 0x40; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mEventType = defaultEventType();
@@ -616,22 +548,18 @@ public final class AmbientContextEvent implements Parcelable {
if ((mBuilderFieldsSet & 0x20) == 0) {
mVendorData = defaultVendorData();
}
- if ((mBuilderFieldsSet & 0x40) == 0) {
- mRatePerMinute = defaultRatePerMinute();
- }
AmbientContextEvent o = new AmbientContextEvent(
mEventType,
mStartTime,
mEndTime,
mConfidenceLevel,
mDensityLevel,
- mVendorData,
- mRatePerMinute);
+ mVendorData);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x80) != 0) {
+ if ((mBuilderFieldsSet & 0x40) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -639,10 +567,10 @@ public final class AmbientContextEvent implements Parcelable {
}
@DataClass.Generated(
- time = 1705575046107L,
+ time = 1709014715064L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java",
- inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int EVENT_BACK_DOUBLE_TAP\npublic static final @android.app.ambientcontext.AmbientContextEvent.Event @android.annotation.FlaggedApi int EVENT_HEART_RATE\npublic static final int EVENT_VENDOR_WEARABLE_START\npublic static final java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\npublic static final @android.annotation.FlaggedApi int RATE_PER_MINUTE_UNKNOWN\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate final @android.annotation.IntRange int mRatePerMinute\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nprivate static android.os.PersistableBundle defaultVendorData()\nprivate static int defaultRatePerMinute()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+ inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int EVENT_BACK_DOUBLE_TAP\npublic static final int EVENT_VENDOR_WEARABLE_START\npublic static final java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nprivate static android.os.PersistableBundle defaultVendorData()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index bc8fac5fa0ce..48ea846e8d50 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -29,6 +29,7 @@ import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Trace;
+import android.window.ActivityWindowInfo;
import java.util.Objects;
@@ -49,11 +50,13 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem {
}
@Override
- public void execute(@NonNull ClientTransactionHandler client, @Nullable ActivityClientRecord r,
+ public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
// TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
- client.handleActivityConfigurationChanged(r, mConfiguration, INVALID_DISPLAY);
+ client.handleActivityConfigurationChanged(r, mConfiguration, INVALID_DISPLAY,
+ // TODO(b/287582673): add ActivityWindowInfo
+ new ActivityWindowInfo());
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index cbb0ae784f82..6da871a74383 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -30,6 +30,7 @@ import android.os.Parcel;
import android.os.Trace;
import android.util.MergedConfiguration;
import android.util.Slog;
+import android.window.ActivityWindowInfo;
import com.android.internal.content.ReferrerIntent;
@@ -50,6 +51,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
private int mConfigChanges;
private MergedConfiguration mConfig;
private boolean mPreserveWindow;
+ private ActivityWindowInfo mActivityWindowInfo;
/**
* A record that was properly configured for relaunch. Execution will be cancelled if not
@@ -64,7 +66,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
CompatibilityInfo.applyOverrideScaleIfNeeded(mConfig);
}
mActivityClientRecord = client.prepareRelaunchActivity(getActivityToken(), mPendingResults,
- mPendingNewIntents, mConfigChanges, mConfig, mPreserveWindow);
+ mPendingNewIntents, mConfigChanges, mConfig, mPreserveWindow, mActivityWindowInfo);
}
@Override
@@ -101,7 +103,8 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
public static ActivityRelaunchItem obtain(@NonNull IBinder activityToken,
@Nullable List<ResultInfo> pendingResults,
@Nullable List<ReferrerIntent> pendingNewIntents, int configChanges,
- @NonNull MergedConfiguration config, boolean preserveWindow) {
+ @NonNull MergedConfiguration config, boolean preserveWindow,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
ActivityRelaunchItem instance = ObjectPool.obtain(ActivityRelaunchItem.class);
if (instance == null) {
instance = new ActivityRelaunchItem();
@@ -113,6 +116,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
instance.mConfigChanges = configChanges;
instance.mConfig = new MergedConfiguration(config);
instance.mPreserveWindow = preserveWindow;
+ instance.mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
return instance;
}
@@ -126,6 +130,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
mConfig = null;
mPreserveWindow = false;
mActivityClientRecord = null;
+ mActivityWindowInfo = null;
ObjectPool.recycle(this);
}
@@ -141,6 +146,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
dest.writeInt(mConfigChanges);
dest.writeTypedObject(mConfig, flags);
dest.writeBoolean(mPreserveWindow);
+ dest.writeTypedObject(mActivityWindowInfo, flags);
}
/** Read from Parcel. */
@@ -151,6 +157,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
mConfigChanges = in.readInt();
mConfig = in.readTypedObject(MergedConfiguration.CREATOR);
mPreserveWindow = in.readBoolean();
+ mActivityWindowInfo = in.readTypedObject(ActivityWindowInfo.CREATOR);
}
public static final @NonNull Creator<ActivityRelaunchItem> CREATOR =
@@ -176,7 +183,8 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
return Objects.equals(mPendingResults, other.mPendingResults)
&& Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
&& mConfigChanges == other.mConfigChanges && Objects.equals(mConfig, other.mConfig)
- && mPreserveWindow == other.mPreserveWindow;
+ && mPreserveWindow == other.mPreserveWindow
+ && Objects.equals(mActivityWindowInfo, other.mActivityWindowInfo);
}
@Override
@@ -188,6 +196,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
result = 31 * result + mConfigChanges;
result = 31 * result + Objects.hashCode(mConfig);
result = 31 * result + (mPreserveWindow ? 1 : 0);
+ result = 31 * result + Objects.hashCode(mActivityWindowInfo);
return result;
}
@@ -198,6 +207,7 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
+ ",pendingNewIntents=" + mPendingNewIntents
+ ",configChanges=" + mConfigChanges
+ ",config=" + mConfig
- + ",preserveWindow" + mPreserveWindow + "}";
+ + ",preserveWindow=" + mPreserveWindow
+ + ",activityWindowInfo=" + mActivityWindowInfo + "}";
}
}
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index 1353d1679427..0702c4594075 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -28,6 +28,7 @@ import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Trace;
+import android.window.ActivityWindowInfo;
import java.util.Objects;
@@ -39,6 +40,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
private int mTargetDisplayId;
private Configuration mConfiguration;
+ private ActivityWindowInfo mActivityWindowInfo;
@Override
public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -52,7 +54,8 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
- client.handleActivityConfigurationChanged(r, mConfiguration, mTargetDisplayId);
+ client.handleActivityConfigurationChanged(r, mConfiguration, mTargetDisplayId,
+ mActivityWindowInfo);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -69,7 +72,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
/** Obtain an instance initialized with provided params. */
@NonNull
public static MoveToDisplayItem obtain(@NonNull IBinder activityToken, int targetDisplayId,
- @NonNull Configuration configuration) {
+ @NonNull Configuration configuration, @NonNull ActivityWindowInfo activityWindowInfo) {
MoveToDisplayItem instance = ObjectPool.obtain(MoveToDisplayItem.class);
if (instance == null) {
instance = new MoveToDisplayItem();
@@ -77,6 +80,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
instance.setActivityToken(activityToken);
instance.mTargetDisplayId = targetDisplayId;
instance.mConfiguration = new Configuration(configuration);
+ instance.mActivityWindowInfo = new ActivityWindowInfo(activityWindowInfo);
return instance;
}
@@ -86,6 +90,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
super.recycle();
mTargetDisplayId = 0;
mConfiguration = null;
+ mActivityWindowInfo = null;
ObjectPool.recycle(this);
}
@@ -97,6 +102,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
super.writeToParcel(dest, flags);
dest.writeInt(mTargetDisplayId);
dest.writeTypedObject(mConfiguration, flags);
+ dest.writeTypedObject(mActivityWindowInfo, flags);
}
/** Read from Parcel. */
@@ -104,6 +110,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
super(in);
mTargetDisplayId = in.readInt();
mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ mActivityWindowInfo = in.readTypedObject(ActivityWindowInfo.CREATOR);
}
public static final @NonNull Creator<MoveToDisplayItem> CREATOR = new Creator<>() {
@@ -126,7 +133,8 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
}
final MoveToDisplayItem other = (MoveToDisplayItem) o;
return mTargetDisplayId == other.mTargetDisplayId
- && Objects.equals(mConfiguration, other.mConfiguration);
+ && Objects.equals(mConfiguration, other.mConfiguration)
+ && Objects.equals(mActivityWindowInfo, other.mActivityWindowInfo);
}
@Override
@@ -135,6 +143,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
result = 31 * result + super.hashCode();
result = 31 * result + mTargetDisplayId;
result = 31 * result + mConfiguration.hashCode();
+ result = 31 * result + Objects.hashCode(mActivityWindowInfo);
return result;
}
@@ -142,6 +151,7 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
public String toString() {
return "MoveToDisplayItem{" + super.toString()
+ ",targetDisplayId=" + mTargetDisplayId
- + ",configuration=" + mConfiguration + "}";
+ + ",configuration=" + mConfiguration
+ + ",activityWindowInfo=" + mActivityWindowInfo + "}";
}
}
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 728c350bfb51..b42133939f28 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -169,6 +169,8 @@ import java.util.List;
*/
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class ClipData implements Parcelable {
+ private static final String TAG = "ClipData";
+
static final String[] MIMETYPES_TEXT_PLAIN = new String[] {
ClipDescription.MIMETYPE_TEXT_PLAIN };
static final String[] MIMETYPES_TEXT_HTML = new String[] {
@@ -476,7 +478,6 @@ public class ClipData implements Parcelable {
* @return Returns the item's textual representation.
*/
//BEGIN_INCLUDE(coerceToText)
- @android.ravenwood.annotation.RavenwoodThrow
public CharSequence coerceToText(Context context) {
// If this Item has an explicit textual value, simply return that.
CharSequence text = getText();
@@ -484,13 +485,20 @@ public class ClipData implements Parcelable {
return text;
}
+ // Gracefully handle cases where resolver isn't available
+ ContentResolver resolver = null;
+ try {
+ resolver = context.getContentResolver();
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to obtain ContentResolver: " + e);
+ }
+
// If this Item has a URI value, try using that.
Uri uri = getUri();
- if (uri != null) {
+ if (uri != null && resolver != null) {
// First see if the URI can be opened as a plain text stream
// (of any sub-type). If so, this is the best textual
// representation for it.
- final ContentResolver resolver = context.getContentResolver();
AssetFileDescriptor descr = null;
FileInputStream stream = null;
InputStreamReader reader = null;
@@ -499,7 +507,7 @@ public class ClipData implements Parcelable {
// Ask for a stream of the desired type.
descr = resolver.openTypedAssetFileDescriptor(uri, "text/*", null);
} catch (SecurityException e) {
- Log.w("ClipData", "Failure opening stream", e);
+ Log.w(TAG, "Failure opening stream", e);
} catch (FileNotFoundException|RuntimeException e) {
// Unable to open content URI as text... not really an
// error, just something to ignore.
@@ -519,7 +527,7 @@ public class ClipData implements Parcelable {
return builder.toString();
} catch (IOException e) {
// Something bad has happened.
- Log.w("ClipData", "Failure loading text", e);
+ Log.w(TAG, "Failure loading text", e);
return e.toString();
}
}
@@ -528,7 +536,8 @@ public class ClipData implements Parcelable {
IoUtils.closeQuietly(stream);
IoUtils.closeQuietly(reader);
}
-
+ }
+ if (uri != null) {
// If we couldn't open the URI as a stream, use the URI itself as a textual
// representation (but not for "content", "android.resource" or "file" schemes).
final String scheme = uri.getScheme();
@@ -704,7 +713,7 @@ public class ClipData implements Parcelable {
}
} catch (SecurityException e) {
- Log.w("ClipData", "Failure opening stream", e);
+ Log.w(TAG, "Failure opening stream", e);
} catch (FileNotFoundException e) {
// Unable to open content URI as text... not really an
@@ -712,7 +721,7 @@ public class ClipData implements Parcelable {
} catch (IOException e) {
// Something bad has happened.
- Log.w("ClipData", "Failure loading text", e);
+ Log.w(TAG, "Failure loading text", e);
return Html.escapeHtml(e.toString());
} finally {
@@ -1123,7 +1132,7 @@ public class ClipData implements Parcelable {
*
* @hide
*/
- @android.ravenwood.annotation.RavenwoodThrow
+ @android.ravenwood.annotation.RavenwoodKeep
public void prepareToLeaveProcess(boolean leavingPackage) {
// Assume that callers are going to be granting permissions
prepareToLeaveProcess(leavingPackage, Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -1134,7 +1143,7 @@ public class ClipData implements Parcelable {
*
* @hide
*/
- @android.ravenwood.annotation.RavenwoodThrow
+ @android.ravenwood.annotation.RavenwoodReplace
public void prepareToLeaveProcess(boolean leavingPackage, int intentFlags) {
final int size = mItems.size();
for (int i = 0; i < size; i++) {
@@ -1154,6 +1163,11 @@ public class ClipData implements Parcelable {
}
}
+ /** @hide */
+ public void prepareToLeaveProcess$ravenwood(boolean leavingPackage, int intentFlags) {
+ // No process boundaries on Ravenwood; ignored
+ }
+
/** {@hide} */
@android.ravenwood.annotation.RavenwoodThrow
public void prepareToEnterProcess(AttributionSource source) {
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 107f1078b11e..2fabcbae9bbb 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -50,6 +50,7 @@ import java.util.Objects;
* </div>
*/
@SystemService(Context.CLIPBOARD_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class ClipboardManager extends android.text.ClipboardManager {
/**
@@ -143,6 +144,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION)
+ @android.ravenwood.annotation.RavenwoodThrow
public boolean areClipboardAccessNotificationsEnabled() {
try {
return mService.areClipboardAccessNotificationsEnabledForUser(mContext.getUserId());
@@ -159,6 +161,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION)
+ @android.ravenwood.annotation.RavenwoodThrow
public void setClipboardAccessNotificationsEnabled(boolean enable) {
try {
mService.setClipboardAccessNotificationsEnabledForUser(enable, mContext.getUserId());
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f77ebc148070..70d2c7a3a41a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -83,6 +83,7 @@ import android.os.StatFs;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
+import android.provider.E2eeContactKeysManager;
import android.provider.MediaStore;
import android.telephony.TelephonyRegistryManager;
import android.util.AttributeSet;
@@ -4815,9 +4816,7 @@ public abstract class Context {
* @see android.net.thread.ThreadNetworkManager
* @hide
*/
- // TODO (b/325886480): update the flag to
- // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM"
- @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform")
+ @FlaggedApi(com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM)
@SystemApi
public static final String THREAD_NETWORK_SERVICE = "thread_network";
@@ -6566,10 +6565,10 @@ public abstract class Context {
/**
* Use with {@link #getSystemService(String)} to retrieve a
- * {@link android.provider.ContactKeysManager} to managing contact keys.
+ * {@link E2eeContactKeysManager} to managing contact keys.
*
* @see #getSystemService(String)
- * @see android.provider.ContactKeysManager
+ * @see E2eeContactKeysManager
*/
@FlaggedApi(android.provider.Flags.FLAG_USER_KEYS)
public static final String CONTACT_KEYS_SERVICE = "contact_keys";
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 7a015cd187ca..240cff34ef36 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3974,9 +3974,7 @@ public abstract class PackageManager {
* The device is capable of communicating with other devices via
* <a href="https://www.threadgroup.org">Thread</a> networking protocol.
*/
- // TODO (b/325886480): update the flag to
- // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM"
- @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform")
+ @FlaggedApi(com.android.net.thread.platform.flags.Flags.FLAG_THREAD_ENABLED_PLATFORM)
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 9c6aab4bc9fb..5b0cee75e591 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -163,25 +163,12 @@ public class ServiceInfo extends ComponentInfo
* Because of this, developers must make sure to stop the foreground service even if
* {@link android.app.Service#onTimeout(int, int)} is not called on such versions.
*
- * <p>Apps targeting API level {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and
- * later should <b>NOT</b> use this type: calling
- * {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
- * this type on devices running {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} is
- * still allowed, but it may throw an {@link android.app.InvalidForegroundServiceTypeException}
- * in future platform releases.
- *
- * <p class="note">
- * Use the {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean)} API for
- * user-initiated, network data transfers.
- *
- * @deprecated Use {@link android.app.job.JobInfo.Builder} APIs or alternate FGS types
- * (like {@link #FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING}) applicable to your use-case.
+ * @see android.app.Service#onTimeout(int, int)
*/
@RequiresPermission(
value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC,
conditional = true
)
- @Deprecated
public static final int FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1 << 0;
/**
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 22e233aebf36..ac80561c3b50 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -177,3 +177,10 @@ flag {
description: "Enable the sensitive notifications toggle to be visible in the Private space settings page"
bug: "317067050"
}
+
+flag {
+ name: "enable_private_space_intent_redirection"
+ namespace: "profile_experiences"
+ description: "Enable Private Space telephony and SMS intent redirection to the main user"
+ bug: "325576602"
+}
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
index 6e53fd99873b..3d8ccaaa45cc 100644
--- a/core/java/android/credentials/GetCandidateCredentialsResponse.java
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -18,7 +18,7 @@ package android.credentials;
import android.annotation.Hide;
import android.annotation.NonNull;
-import android.app.PendingIntent;
+import android.content.Intent;
import android.credentials.selection.GetCredentialProviderData;
import android.os.Parcel;
import android.os.Parcelable;
@@ -39,32 +39,22 @@ public final class GetCandidateCredentialsResponse implements Parcelable {
@NonNull
private final List<GetCredentialProviderData> mCandidateProviderDataList;
- private final PendingIntent mPendingIntent;
-
- /**
- * @hide
- */
- @Hide
- public GetCandidateCredentialsResponse(
- GetCredentialResponse getCredentialResponse
- ) {
- mCandidateProviderDataList = null;
- mPendingIntent = null;
- }
+ @NonNull
+ private final Intent mIntent;
/**
* @hide
*/
@Hide
public GetCandidateCredentialsResponse(
- List<GetCredentialProviderData> candidateProviderDataList,
- PendingIntent pendingIntent
+ @NonNull List<GetCredentialProviderData> candidateProviderDataList,
+ @NonNull Intent intent
) {
Preconditions.checkCollectionNotEmpty(
candidateProviderDataList,
/*valueName=*/ "candidateProviderDataList");
mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList);
- mPendingIntent = pendingIntent;
+ mIntent = intent;
}
/**
@@ -81,8 +71,9 @@ public final class GetCandidateCredentialsResponse implements Parcelable {
*
* @hide
*/
- public PendingIntent getPendingIntent() {
- return mPendingIntent;
+ @NonNull
+ public Intent getIntent() {
+ return mIntent;
}
protected GetCandidateCredentialsResponse(Parcel in) {
@@ -91,14 +82,13 @@ public final class GetCandidateCredentialsResponse implements Parcelable {
mCandidateProviderDataList = candidateProviderDataList;
AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList);
-
- mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ mIntent = in.readTypedObject(Intent.CREATOR);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedList(mCandidateProviderDataList);
- dest.writeTypedObject(mPendingIntent, flags);
+ dest.writeTypedObject(mIntent, flags);
}
@Override
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 5dfeac7fca9b..d683d72f17be 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -40,15 +40,12 @@ import android.os.Looper;
import android.os.OperationCanceledException;
import android.os.SystemProperties;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
import android.util.Printer;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.NeverCompile;
@@ -106,14 +103,8 @@ public final class SQLiteDatabase extends SQLiteClosable {
// Stores reference to all databases opened in the current process.
// (The referent Object is not used at this time.)
// INVARIANT: Guarded by sActiveDatabases.
- @GuardedBy("sActiveDatabases")
private static WeakHashMap<SQLiteDatabase, Object> sActiveDatabases = new WeakHashMap<>();
- // Tracks which database files are currently open. If a database file is opened more than
- // once at any given moment, the associated databases are marked as "concurrent".
- @GuardedBy("sActiveDatabases")
- private static final OpenTracker sOpenTracker = new OpenTracker();
-
// Thread-local for database sessions that belong to this database.
// Each thread has its own database session.
// INVARIANT: Immutable.
@@ -519,7 +510,6 @@ public final class SQLiteDatabase extends SQLiteClosable {
private void dispose(boolean finalized) {
final SQLiteConnectionPool pool;
- final String path;
synchronized (mLock) {
if (mCloseGuardLocked != null) {
if (finalized) {
@@ -530,12 +520,10 @@ public final class SQLiteDatabase extends SQLiteClosable {
pool = mConnectionPoolLocked;
mConnectionPoolLocked = null;
- path = isInMemoryDatabase() ? null : getPath();
}
if (!finalized) {
synchronized (sActiveDatabases) {
- sOpenTracker.close(path);
sActiveDatabases.remove(this);
}
@@ -1144,74 +1132,6 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
}
- /**
- * Track the number of times a database file has been opened. There is a primary connection
- * associated with every open database, and these can contend with each other, leading to
- * unexpected SQLiteDatabaseLockedException exceptions. The tracking here is only advisory:
- * multiply-opened databases are logged but no other action is taken.
- *
- * This class is not thread-safe.
- */
- private static class OpenTracker {
- // The list of currently-open databases. This maps the database file to the number of
- // currently-active opens.
- private final ArrayMap<String, Integer> mOpens = new ArrayMap<>();
-
- // The maximum number of concurrently open database paths that will be stored. Once this
- // many paths have been recorded, further paths are logged but not saved.
- private static final int MAX_RECORDED_PATHS = 20;
-
- // The list of databases that were ever concurrently opened.
- private final ArraySet<String> mConcurrent = new ArraySet<>();
-
- /** Return the canonical path. On error, just return the input path. */
- private static String normalize(String path) {
- try {
- return new File(path).toPath().toRealPath().toString();
- } catch (Exception e) {
- // If there is an IO or security exception, just continue, using the input path.
- return path;
- }
- }
-
- /** Return true if the path is currently open in another SQLiteDatabase instance. */
- void open(@Nullable String path) {
- if (path == null) return;
- path = normalize(path);
-
- Integer count = mOpens.get(path);
- if (count == null || count == 0) {
- mOpens.put(path, 1);
- return;
- } else {
- mOpens.put(path, count + 1);
- if (mConcurrent.size() < MAX_RECORDED_PATHS) {
- mConcurrent.add(path);
- }
- Log.w(TAG, "multiple primary connections on " + path);
- return;
- }
- }
-
- void close(@Nullable String path) {
- if (path == null) return;
- path = normalize(path);
- Integer count = mOpens.get(path);
- if (count == null || count <= 0) {
- Log.e(TAG, "open database counting failure on " + path);
- } else if (count == 1) {
- // Implicitly set the count to zero, and make mOpens smaller.
- mOpens.remove(path);
- } else {
- mOpens.put(path, count - 1);
- }
- }
-
- ArraySet<String> getConcurrentDatabasePaths() {
- return new ArraySet<>(mConcurrent);
- }
- }
-
private void open() {
try {
try {
@@ -1233,17 +1153,14 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
private void openInner() {
- final String path;
synchronized (mLock) {
assert mConnectionPoolLocked == null;
mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
mCloseGuardLocked.open("close");
- path = isInMemoryDatabase() ? null : getPath();
}
synchronized (sActiveDatabases) {
sActiveDatabases.put(this, null);
- sOpenTracker.open(path);
}
}
@@ -2428,17 +2345,6 @@ public final class SQLiteDatabase extends SQLiteClosable {
}
/**
- * Return list of databases that have been concurrently opened.
- * @hide
- */
- @VisibleForTesting
- public static ArraySet<String> getConcurrentDatabasePaths() {
- synchronized (sActiveDatabases) {
- return sOpenTracker.getConcurrentDatabasePaths();
- }
- }
-
- /**
* Returns true if the new version code is greater than the current database version.
*
* @param newVersion The new version code.
@@ -2860,19 +2766,6 @@ public final class SQLiteDatabase extends SQLiteClosable {
dumpDatabaseDirectory(printer, new File(dir), isSystem);
}
}
-
- // Dump concurrently-opened database files, if any
- final ArraySet<String> concurrent;
- synchronized (sActiveDatabases) {
- concurrent = sOpenTracker.getConcurrentDatabasePaths();
- }
- if (concurrent.size() > 0) {
- printer.println("");
- printer.println("Concurrently opened database files");
- for (String f : concurrent) {
- printer.println(" " + f);
- }
- }
}
private static void dumpDatabaseDirectory(Printer pw, File dir, boolean isSystem) {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 04e3dab55939..7119730c6984 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -772,6 +772,16 @@ public final class CameraManager {
"CameraDeviceSetup is not supported for Camera ID: " + cameraId);
}
+ return getCameraDeviceSetupUnsafe(cameraId);
+
+ }
+
+ /**
+ * Creates and returns a {@link CameraDeviceSetup} instance without any error checking. To
+ * be used (carefully) by callers who are sure that CameraDeviceSetup instance can be legally
+ * created and don't want to pay the latency cost of calling {@link #getCameraDeviceSetup}.
+ */
+ private CameraDevice.CameraDeviceSetup getCameraDeviceSetupUnsafe(@NonNull String cameraId) {
return new CameraDeviceSetupImpl(cameraId, /*cameraManager=*/ this, mContext);
}
@@ -857,8 +867,9 @@ public final class CameraManager {
ICameraDeviceUser cameraUser = null;
CameraDevice.CameraDeviceSetup cameraDeviceSetup = null;
- if (Flags.cameraDeviceSetup() && isCameraDeviceSetupSupported(cameraId)) {
- cameraDeviceSetup = getCameraDeviceSetup(cameraId);
+ if (Flags.cameraDeviceSetup()
+ && CameraDeviceSetupImpl.isCameraDeviceSetupSupported(characteristics)) {
+ cameraDeviceSetup = getCameraDeviceSetupUnsafe(cameraId);
}
android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 95526a8affbb..8aacd5e3908f 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1379,7 +1379,6 @@ public final class OutputConfiguration implements Parcelable {
mSurfaceType != other.mSurfaceType ||
mIsDeferredConfig != other.mIsDeferredConfig ||
mIsShared != other.mIsShared ||
- mConfiguredFormat != other.mConfiguredFormat ||
mConfiguredDataspace != other.mConfiguredDataspace ||
mConfiguredGenerationId != other.mConfiguredGenerationId ||
!Objects.equals(mPhysicalCameraId, other.mPhysicalCameraId) ||
diff --git a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
index 63ae28f6ff9d..2a118350610f 100644
--- a/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/system_sw_usb_flags.aconfig
@@ -20,3 +20,10 @@ flag {
description: "Enable reporting USB data compliance warnings from HAL when set"
bug: "296119135"
}
+
+flag {
+ name: "enable_usb_data_signal_staking"
+ namespace: "preload_safety"
+ description: "Enables signal API with staking"
+ bug: "296119135"
+} \ No newline at end of file
diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
index 510b14e7acd4..e28f34528f42 100644
--- a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
+++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java
@@ -99,7 +99,11 @@ public final class NavigationBarView extends FrameLayout {
mDeadZone = new android.inputmethodservice.navigationbar.DeadZone(this);
- getImeSwitchButton().setOnClickListener(view -> view.getContext()
+ getBackButton().setLongClickable(false);
+
+ final ButtonDispatcher imeSwitchButton = getImeSwitchButton();
+ imeSwitchButton.setLongClickable(false);
+ imeSwitchButton.setOnClickListener(view -> view.getContext()
.getSystemService(InputMethodManager.class).showInputMethodPicker());
}
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 6efb87225183..9f9aef8acc6a 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -17,3 +17,9 @@ flag {
bug: "307898240"
}
+flag {
+ name: "register_nsd_offload_engine"
+ namespace: "android_core_networking"
+ description: "Flag for registerOffloadEngine API in NsdManager"
+ bug: "294777050"
+}
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index 36730cb07344..f852d3cd69b2 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -19,6 +19,8 @@ package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import java.util.concurrent.Executor;
+
/**
* A {@link Thread} that has a {@link Looper}.
* The {@link Looper} can then be used to create {@link Handler}s.
@@ -30,7 +32,8 @@ public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
- private @Nullable Handler mHandler;
+ private volatile @Nullable Handler mHandler;
+ private volatile @Nullable Executor mExecutor;
public HandlerThread(String name) {
super(name);
@@ -131,6 +134,18 @@ public class HandlerThread extends Thread {
}
/**
+ * @return a shared {@link Executor} associated with this thread
+ * @hide
+ */
+ @NonNull
+ public Executor getThreadExecutor() {
+ if (mExecutor == null) {
+ mExecutor = new HandlerExecutor(getThreadHandler());
+ }
+ return mExecutor;
+ }
+
+ /**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index be17d7c0f0db..ccb534eb1019 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1920,9 +1920,7 @@ public class UserManager {
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
*/
- // TODO (b/325886480): update the flag to
- // "com.android.net.thread.platform.flags.Flags.FLAG_THREAD_USER_RESTRICTION_ENABLED"
- @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
+ @FlaggedApi(com.android.net.thread.platform.flags.Flags.FLAG_THREAD_USER_RESTRICTION_ENABLED)
public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
/**
diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS
index 2cb16d337bba..c21895a2407c 100644
--- a/core/java/android/os/storage/OWNERS
+++ b/core/java/android/os/storage/OWNERS
@@ -3,11 +3,15 @@
# PLEASE ASSIGN NEW BUGS TO android-storage-triage@, NOT TO INDIVIDUAL PEOPLE
# Android Storage Team
+aibra@google.com
+akgaurav@google.com
alukin@google.com
ankitavyas@google.com
dipankarb@google.com
gargshivam@google.com
+ishneet@google.com
krishang@google.com
+oeissa@google.com
riyaghai@google.com
sahanas@google.com
shikhamalhotra@google.com
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 460b4dd9b86c..141ffc9a491a 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -359,8 +359,7 @@ public class PermissionUsageHelper implements AppOpsManager.OnOpActiveChangedLis
new PermissionGroupUsage(usage.packageName, usage.uid, usage.lastAccessTime,
permGroup,
usage.isRunning, isPhone, usage.attributionTag, attributionLabel,
- usagesWithLabels.valueAt(usageNum),
- VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT));
+ usagesWithLabels.valueAt(usageNum), deviceId));
}
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index c13dd363d79e..c03dc718d1a1 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -406,6 +406,7 @@ public class CallLog {
* Builder for the add-call parameters.
*/
public static final class AddCallParametersBuilder {
+ public static final int MAX_NUMBER_OF_CHARACTERS = 256;
private CallerInfo mCallerInfo;
private String mNumber;
private String mPostDialDigits;
@@ -431,7 +432,7 @@ public class CallLog {
private Uri mPictureUri;
private int mIsPhoneAccountMigrationPending;
private boolean mIsBusinessCall;
- private String mBusinessName;
+ private String mAssertedDisplayName;
/**
* @param callerInfo the CallerInfo object to get the target contact from.
@@ -659,11 +660,18 @@ public class CallLog {
}
/**
- * @param businessName should be set if the caller is a business call
+ * @param assertedDisplayName the asserted display name associated with the business
+ * call
+ * @throws IllegalArgumentException if the assertedDisplayName is over 256 characters
*/
@FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
- public @NonNull AddCallParametersBuilder setBusinessName(String businessName) {
- mBusinessName = businessName;
+ public @NonNull AddCallParametersBuilder setAssertedDisplayName(
+ String assertedDisplayName) {
+ if (assertedDisplayName.length() > MAX_NUMBER_OF_CHARACTERS) {
+ throw new IllegalArgumentException("assertedDisplayName exceeds the character"
+ + " limit of " + MAX_NUMBER_OF_CHARACTERS + ".");
+ }
+ mAssertedDisplayName = assertedDisplayName;
return this;
}
@@ -678,7 +686,7 @@ public class CallLog {
mCallBlockReason,
mCallScreeningAppName, mCallScreeningComponentName, mMissedReason,
mPriority, mSubject, mLatitude, mLongitude, mPictureUri,
- mIsPhoneAccountMigrationPending, mIsBusinessCall, mBusinessName);
+ mIsPhoneAccountMigrationPending, mIsBusinessCall, mAssertedDisplayName);
} else {
return new AddCallParams(mCallerInfo, mNumber, mPostDialDigits, mViaNumber,
mPresentation, mCallType, mFeatures, mAccountHandle, mStart, mDuration,
@@ -716,7 +724,7 @@ public class CallLog {
private Uri mPictureUri;
private int mIsPhoneAccountMigrationPending;
private boolean mIsBusinessCall;
- private String mBusinessName;
+ private String mAssertedDisplayName;
private AddCallParams(CallerInfo callerInfo, String number, String postDialDigits,
String viaNumber, int presentation, int callType, int features,
@@ -761,7 +769,8 @@ public class CallLog {
CharSequence callScreeningAppName, String callScreeningComponentName,
long missedReason,
int priority, String subject, double latitude, double longitude, Uri pictureUri,
- int isPhoneAccountMigrationPending, boolean isBusinessCall, String businessName) {
+ int isPhoneAccountMigrationPending, boolean isBusinessCall,
+ String assertedDisplayName) {
mCallerInfo = callerInfo;
mNumber = number;
mPostDialDigits = postDialDigits;
@@ -787,7 +796,7 @@ public class CallLog {
mPictureUri = pictureUri;
mIsPhoneAccountMigrationPending = isPhoneAccountMigrationPending;
mIsBusinessCall = isBusinessCall;
- mBusinessName = businessName;
+ mAssertedDisplayName = assertedDisplayName;
}
}
@@ -1833,7 +1842,7 @@ public class CallLog {
values.put(IS_PHONE_ACCOUNT_MIGRATION_PENDING, params.mIsPhoneAccountMigrationPending);
if (Flags.businessCallComposer()) {
values.put(IS_BUSINESS_CALL, Integer.valueOf(params.mIsBusinessCall ? 1 : 0));
- values.put(ASSERTED_DISPLAY_NAME, params.mBusinessName);
+ values.put(ASSERTED_DISPLAY_NAME, params.mAssertedDisplayName);
}
if ((params.mCallerInfo != null) && (params.mCallerInfo.getContactId() > 0)) {
// Update usage information for the number associated with the contact ID.
diff --git a/core/java/android/provider/ContactKeysManager.java b/core/java/android/provider/E2eeContactKeysManager.java
index 01aaa3d9647e..b69441700b0a 100644
--- a/core/java/android/provider/ContactKeysManager.java
+++ b/core/java/android/provider/E2eeContactKeysManager.java
@@ -39,18 +39,18 @@ import java.util.List;
import java.util.Objects;
/**
- * ContactKeysManager provides access to the provider of end-to-end encryption contact keys.
- * It manages two types of keys - {@link ContactKey} and {@link SelfKey}.
+ * E2eeContactKeysManager provides access to the provider of end-to-end encryption contact keys.
+ * It manages two types of keys - {@link E2eeContactKey} and {@link E2eeSelfKey}.
* <ul>
* <li>
- * A {@link ContactKey} is a public key associated with a contact. It's used to end-to-end
+ * A {@link E2eeContactKey} is a public key associated with a contact. It's used to end-to-end
* encrypt the communications between a user and the contact. This API allows operations on
- * {@link ContactKey}s to insert/update, remove, change the verification state, and retrieving
+ * {@link E2eeContactKey}s to insert/update, remove, change the verification state, and retrieving
* keys (either created by or visible to the caller app).
* </li>
* <li>
- * A {@link SelfKey} is a key for this device, so the key represents the owner of the device.
- * This API allows operations on {@link SelfKey}s to insert/update, remove, and retrieving
+ * A {@link E2eeSelfKey} is a key for this device, so the key represents the owner of the device.
+ * This API allows operations on {@link E2eeSelfKey}s to insert/update, remove, and retrieving
* self keys (either created by or visible to the caller app).
* </li>
* </ul>
@@ -65,29 +65,30 @@ import java.util.Objects;
* </li>
* <li>
* accountId - the app-specified identifier for the account for which the contact key can be used.
- * Usually a phone number.
+ * Using different account IDs allows for multiple key entries representing the same user.
+ * For most apps this would be a phone number.
* </li>
* </ul>
* Contact keys also use lookupKey which is an opaque value used to identify a contact in
* ContactsProvider.
*/
@FlaggedApi(Flags.FLAG_USER_KEYS)
-public final class ContactKeysManager {
+public final class E2eeContactKeysManager {
/**
- * The authority for the contact keys provider.
+ * The authority for the end-to-end encryption contact keys provider.
* @hide
*/
public static final String AUTHORITY = "com.android.contactkeys.contactkeysprovider";
/**
- * A content:// style uri to the authority for the contact keys provider.
+ * A content:// style uri to the authority for the end-to-end encryption contact keys provider.
* @hide
*/
@NonNull
public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
/**
- * Maximum size of a contact key.
+ * Maximum size of an end-to-end encryption contact key.
*/
private static final int MAX_KEY_SIZE_BYTES = 5000;
@@ -100,14 +101,15 @@ public final class ContactKeysManager {
private final ContentResolver mContentResolver;
/** @hide */
- public ContactKeysManager(@NonNull Context context) {
+ public E2eeContactKeysManager(@NonNull Context context) {
Objects.requireNonNull(context);
mContentResolver = context.getContentResolver();
}
/**
- * Inserts a new entry into the contact keys table or updates one if it already exists.
- * The inserted/updated contact key is owned by the caller app.
+ * Inserts a new entry into the end-to-end encryption contact keys table or updates one if it
+ * already exists.
+ * The inserted/updated end-to-end encryption contact key is owned by the caller app.
*
* @param lookupKey value that references the contact
* @param deviceId an app-specified identifier for the device
@@ -115,76 +117,77 @@ public final class ContactKeysManager {
* @param keyValue the raw bytes for the key (max size is {@link #getMaxKeySizeBytes} bytes)
*/
@RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
- public void updateOrInsertContactKey(@NonNull String lookupKey,
+ public void updateOrInsertE2eeContactKey(@NonNull String lookupKey,
@NonNull String deviceId,
@NonNull String accountId,
@NonNull byte[] keyValue) {
validateKeyLength(keyValue);
Bundle extras = new Bundle();
- extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
- extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
- extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
- extras.putByteArray(ContactKeys.KEY_VALUE, Objects.requireNonNull(keyValue));
+ extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putByteArray(E2eeContactKeys.KEY_VALUE, Objects.requireNonNull(keyValue));
nullSafeCall(mContentResolver,
- ContactKeys.UPDATE_OR_INSERT_CONTACT_KEY_METHOD, extras);
+ E2eeContactKeys.UPDATE_OR_INSERT_CONTACT_KEY_METHOD, extras);
}
/**
- * Retrieves a contact key entry given the lookup key, device ID, accountId and inferred
- * caller package name.
+ * Retrieves an end-to-end encryption contact key entry given the lookup key, device ID,
+ * accountId and inferred caller package name.
*
* @param lookupKey the value that references the contact
* @param deviceId an app-specified identifier for the device
* @param accountId an app-specified identifier for the account
*
- * @return a {@link ContactKey} object containing the contact key information,
+ * @return a {@link E2eeContactKey} object containing the contact key information,
* or null if no contact key is found.
*/
@RequiresPermission(android.Manifest.permission.READ_CONTACTS)
@Nullable
- public ContactKey getContactKey(
+ public E2eeContactKey getE2eeContactKey(
@NonNull String lookupKey,
@NonNull String deviceId,
@NonNull String accountId) {
Bundle extras = new Bundle();
- extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
- extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
- extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.GET_CONTACT_KEY_METHOD, extras);
+ E2eeContactKeys.GET_CONTACT_KEY_METHOD, extras);
if (response == null) {
return null;
}
- return response.getParcelable(ContactKeys.KEY_CONTACT_KEY, ContactKey.class);
+ return response.getParcelable(E2eeContactKeys.KEY_CONTACT_KEY, E2eeContactKey.class);
}
/**
- * Retrieves all contact key entries that belong to apps visible to the caller.
+ * Retrieves all end-to-end encryption contact key entries that belong to apps visible to
+ * the caller.
* The keys will be stripped of deviceId, timeUpdated and keyValue data.
*
* @param lookupKey the value that references the contact
*
- * @return a list of {@link ContactKey} objects containing the contact key
+ * @return a list of {@link E2eeContactKey} objects containing the contact key
* information, or an empty list if no keys are found.
*/
@RequiresPermission(android.Manifest.permission.READ_CONTACTS)
@NonNull
- public List<ContactKey> getAllContactKeys(@NonNull String lookupKey) {
+ public List<E2eeContactKey> getAllE2eeContactKeys(@NonNull String lookupKey) {
Bundle extras = new Bundle();
- extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.GET_ALL_CONTACT_KEYS_METHOD, extras);
+ E2eeContactKeys.GET_ALL_CONTACT_KEYS_METHOD, extras);
if (response == null) {
return new ArrayList<>();
}
- List<ContactKey> value = response.getParcelableArrayList(ContactKeys.KEY_CONTACT_KEYS,
- ContactKey.class);
+ List<E2eeContactKey> value = response.getParcelableArrayList(
+ E2eeContactKeys.KEY_CONTACT_KEYS, E2eeContactKey.class);
if (value == null) {
return new ArrayList<>();
}
@@ -192,27 +195,28 @@ public final class ContactKeysManager {
}
/**
- * Retrieves all contact key entries for a given lookupKey that belong to the caller app.
+ * Retrieves all end-to-end encryption contact key entries for a given lookupKey that belong to
+ * the caller app.
*
* @param lookupKey the value that references the contact
*
- * @return a list of {@link ContactKey} objects containing the contact key
- * information, or an empty list if no keys are found.
+ * @return a list of {@link E2eeContactKey} objects containing the end-to-end encryption
+ * contact key information, or an empty list if no keys are found.
*/
@RequiresPermission(android.Manifest.permission.READ_CONTACTS)
@NonNull
- public List<ContactKey> getOwnerContactKeys(@NonNull String lookupKey) {
+ public List<E2eeContactKey> getOwnerE2eeContactKeys(@NonNull String lookupKey) {
Bundle extras = new Bundle();
- extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.GET_OWNER_CONTACT_KEYS_METHOD, extras);
+ E2eeContactKeys.GET_OWNER_CONTACT_KEYS_METHOD, extras);
if (response == null) {
return new ArrayList<>();
}
- List<ContactKey> value = response.getParcelableArrayList(ContactKeys.KEY_CONTACT_KEYS,
- ContactKey.class);
+ List<E2eeContactKey> value = response.getParcelableArrayList(
+ E2eeContactKeys.KEY_CONTACT_KEYS, E2eeContactKey.class);
if (value == null) {
return new ArrayList<>();
}
@@ -220,7 +224,8 @@ public final class ContactKeysManager {
}
/**
- * Updates a contact key entry's local verification state that belongs to the caller app.
+ * Updates an end-to-end encryption contact key entry's local verification state that belongs
+ * to the caller app.
*
* @param lookupKey the value that references the contact
* @param deviceId an app-specified identifier for the device
@@ -230,27 +235,27 @@ public final class ContactKeysManager {
* @return true if the entry was updated, false otherwise.
*/
@RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
- public boolean updateContactKeyLocalVerificationState(@NonNull String lookupKey,
+ public boolean updateE2eeContactKeyLocalVerificationState(@NonNull String lookupKey,
@NonNull String deviceId,
@NonNull String accountId,
@VerificationState int localVerificationState) {
validateVerificationState(localVerificationState);
Bundle extras = new Bundle();
- extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
- extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
- extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
- extras.putInt(ContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
+ extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putInt(E2eeContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
+ E2eeContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
- return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
}
/**
- * Updates a contact key entry's local verification state that belongs to the app identified
- * by ownerPackageName.
+ * Updates an end-to-end encryption contact key entry's local verification state that belongs
+ * to the app identified by ownerPackageName.
*
* @param lookupKey the value that references the contact
* @param deviceId an app-specified identifier for the device
@@ -266,7 +271,7 @@ public final class ContactKeysManager {
@RequiresPermission(allOf = {
android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
android.Manifest.permission.WRITE_CONTACTS})
- public boolean updateContactKeyLocalVerificationState(@NonNull String lookupKey,
+ public boolean updateE2eeContactKeyLocalVerificationState(@NonNull String lookupKey,
@NonNull String deviceId,
@NonNull String accountId,
@NonNull String ownerPackageName,
@@ -274,20 +279,22 @@ public final class ContactKeysManager {
validateVerificationState(localVerificationState);
final Bundle extras = new Bundle();
- extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
- extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
- extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
- extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
- extras.putInt(ContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
+ extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(E2eeContactKeys.OWNER_PACKAGE_NAME,
+ Objects.requireNonNull(ownerPackageName));
+ extras.putInt(E2eeContactKeys.LOCAL_VERIFICATION_STATE, localVerificationState);
final Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
+ E2eeContactKeys.UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD, extras);
- return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
}
/**
- * Updates a contact key entry's remote verification state that belongs to the caller app.
+ * Updates an end-to-end encryption contact key entry's remote verification state that belongs
+ * to the caller app.
*
* @param lookupKey the value that references the contact
* @param deviceId an app-specified identifier for the device
@@ -297,27 +304,27 @@ public final class ContactKeysManager {
* @return true if the entry was updated, false otherwise.
*/
@RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
- public boolean updateContactKeyRemoteVerificationState(@NonNull String lookupKey,
+ public boolean updateE2eeContactKeyRemoteVerificationState(@NonNull String lookupKey,
@NonNull String deviceId,
@NonNull String accountId,
@VerificationState int remoteVerificationState) {
validateVerificationState(remoteVerificationState);
Bundle extras = new Bundle();
- extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
- extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
- extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
- extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+ extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putInt(E2eeContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+ E2eeContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
- return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
}
/**
- * Updates a contact key entry's remote verification state that belongs to the app identified
- * by ownerPackageName.
+ * Updates an end-to-end encryption contact key entry's remote verification state that belongs
+ * to the app identified by ownerPackageName.
*
* @param lookupKey the value that references the contact
* @param deviceId an app-specified identifier for the device
@@ -333,7 +340,7 @@ public final class ContactKeysManager {
@RequiresPermission(allOf = {
android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
android.Manifest.permission.WRITE_CONTACTS})
- public boolean updateContactKeyRemoteVerificationState(@NonNull String lookupKey,
+ public boolean updateE2eeContactKeyRemoteVerificationState(@NonNull String lookupKey,
@NonNull String deviceId,
@NonNull String accountId,
@NonNull String ownerPackageName,
@@ -341,19 +348,19 @@ public final class ContactKeysManager {
validateVerificationState(remoteVerificationState);
final Bundle extras = new Bundle();
- extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
- extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
- extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
- extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
- extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+ extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(E2eeContactKeys.OWNER_PACKAGE_NAME,
+ Objects.requireNonNull(ownerPackageName));
+ extras.putInt(E2eeContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
final Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+ E2eeContactKeys.UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
- return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
}
-
private static void validateVerificationState(int verificationState) {
if (verificationState != VERIFICATION_STATE_UNVERIFIED
&& verificationState != VERIFICATION_STATE_VERIFICATION_FAILED
@@ -364,7 +371,7 @@ public final class ContactKeysManager {
}
/**
- * Removes a contact key entry that belongs to the caller app.
+ * Removes an end-to-end encryption contact key entry that belongs to the caller app.
*
* @param lookupKey the value that references the contact
* @param deviceId an app-specified identifier for the device
@@ -373,22 +380,23 @@ public final class ContactKeysManager {
* @return true if the entry was removed, false otherwise.
*/
@RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
- public boolean removeContactKey(@NonNull String lookupKey,
+ public boolean removeE2eeContactKey(@NonNull String lookupKey,
@NonNull String deviceId,
@NonNull String accountId) {
final Bundle extras = new Bundle();
- extras.putString(ContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
- extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
- extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(E2eeContactKeys.LOOKUP_KEY, Objects.requireNonNull(lookupKey));
+ extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
final Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.REMOVE_CONTACT_KEY_METHOD, extras);
+ E2eeContactKeys.REMOVE_CONTACT_KEY_METHOD, extras);
- return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
}
/**
- * Inserts a new entry into the self keys table or updates one if it already exists.
+ * Inserts a new entry into the end-to-end encryption self keys table or updates one if it
+ * already exists.
* @param deviceId an app-specified identifier for the device
* @param accountId an app-specified identifier for the account
@@ -397,20 +405,20 @@ public final class ContactKeysManager {
* @return true if the entry was added or updated, false otherwise.
*/
@RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
- public boolean updateOrInsertSelfKey(@NonNull String deviceId,
+ public boolean updateOrInsertE2eeSelfKey(@NonNull String deviceId,
@NonNull String accountId,
@NonNull byte[] keyValue) {
validateKeyLength(keyValue);
Bundle extras = new Bundle();
- extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
- extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
- extras.putByteArray(ContactKeys.KEY_VALUE, Objects.requireNonNull(keyValue));
+ extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putByteArray(E2eeContactKeys.KEY_VALUE, Objects.requireNonNull(keyValue));
Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.UPDATE_OR_INSERT_SELF_KEY_METHOD, extras);
+ E2eeContactKeys.UPDATE_OR_INSERT_SELF_KEY_METHOD, extras);
- return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
}
private static void validateKeyLength(byte[] keyValue) {
@@ -422,7 +430,7 @@ public final class ContactKeysManager {
}
/**
- * Updates a self key entry's remote verification state.
+ * Updates an end-to-end encryption self key entry's remote verification state.
*
* @param deviceId an app-specified identifier for the device
* @param accountId an app-specified identifier for the account
@@ -431,25 +439,25 @@ public final class ContactKeysManager {
* @return true if the entry was updated, false otherwise.
*/
@RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
- public boolean updateSelfKeyRemoteVerificationState(@NonNull String deviceId,
+ public boolean updateE2eeSelfKeyRemoteVerificationState(@NonNull String deviceId,
@NonNull String accountId,
@VerificationState int remoteVerificationState) {
validateVerificationState(remoteVerificationState);
Bundle extras = new Bundle();
- extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
- extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
- extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+ extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putInt(E2eeContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+ E2eeContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
- return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
}
/**
- * Updates a self key entry's remote verification state that belongs to the app identified
- * by ownerPackageName.
+ * Updates an end-to-end encryption self key entry's remote verification state that belongs to
+ * the app identified by ownerPackageName.
*
* @param deviceId an app-specified identifier for the device
* @param accountId an app-specified identifier for the account
@@ -464,77 +472,79 @@ public final class ContactKeysManager {
@RequiresPermission(allOf = {
android.Manifest.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS,
android.Manifest.permission.WRITE_CONTACTS})
- public boolean updateSelfKeyRemoteVerificationState(@NonNull String deviceId,
+ public boolean updateE2eeSelfKeyRemoteVerificationState(@NonNull String deviceId,
@NonNull String accountId,
@NonNull String ownerPackageName,
@VerificationState int remoteVerificationState) {
validateVerificationState(remoteVerificationState);
Bundle extras = new Bundle();
- extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
- extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
- extras.putString(ContactKeys.OWNER_PACKAGE_NAME, Objects.requireNonNull(ownerPackageName));
- extras.putInt(ContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
+ extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(E2eeContactKeys.OWNER_PACKAGE_NAME,
+ Objects.requireNonNull(ownerPackageName));
+ extras.putInt(E2eeContactKeys.REMOTE_VERIFICATION_STATE, remoteVerificationState);
Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
+ E2eeContactKeys.UPDATE_SELF_KEY_REMOTE_VERIFICATION_STATE_METHOD, extras);
- return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
}
/**
- * Maximum size of a contact key.
+ * Maximum size of an end-to-end encryption contact key.
*/
public static int getMaxKeySizeBytes() {
return MAX_KEY_SIZE_BYTES;
}
/**
- * Returns a self key entry given the deviceId and the inferred package name of the caller.
+ * Returns an end-to-end encryption self key entry given the deviceId and the inferred package
+ * name of the caller.
*
* @param deviceId an app-specified identifier for the device
* @param accountId an app-specified identifier for the account
*
- * @return a {@link SelfKey} object containing the self key information, or null if no self key
- * is found.
+ * @return a {@link E2eeSelfKey} object containing the end-to-end encryption self key
+ * information, or null if no self key is found.
*/
@RequiresPermission(android.Manifest.permission.READ_CONTACTS)
@Nullable
- public SelfKey getSelfKey(@NonNull String deviceId,
+ public E2eeSelfKey getE2eeSelfKey(@NonNull String deviceId,
@NonNull String accountId) {
Bundle extras = new Bundle();
- extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
- extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.GET_SELF_KEY_METHOD, extras);
+ E2eeContactKeys.GET_SELF_KEY_METHOD, extras);
if (response == null) {
return null;
}
- return response.getParcelable(ContactKeys.KEY_CONTACT_KEY, SelfKey.class);
+ return response.getParcelable(E2eeContactKeys.KEY_CONTACT_KEY, E2eeSelfKey.class);
}
/**
- * Returns all self key entries that belong to apps visible to the caller.
+ * Returns all end-to-end encryption self key entries that belong to apps visible to the caller.
* The keys will be stripped of deviceId, timeUpdated and keyValue data.
*
- * @return a list of {@link SelfKey} objects containing the self key information, or
- * an empty list if no keys are found.
+ * @return a list of {@link E2eeSelfKey} objects containing the end-to-end encryption self key
+ * information, or an empty list if no self keys are found.
*/
@RequiresPermission(android.Manifest.permission.READ_CONTACTS)
@NonNull
- public List<SelfKey> getAllSelfKeys() {
+ public List<E2eeSelfKey> getAllE2eeSelfKeys() {
Bundle extras = new Bundle();
- Bundle response = nullSafeCall(mContentResolver, ContactKeys.GET_ALL_SELF_KEYS_METHOD,
+ Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.GET_ALL_SELF_KEYS_METHOD,
extras);
if (response == null) {
return new ArrayList<>();
}
- List<SelfKey> value = response.getParcelableArrayList(ContactKeys.KEY_CONTACT_KEYS,
- SelfKey.class);
+ List<E2eeSelfKey> value = response.getParcelableArrayList(E2eeContactKeys.KEY_CONTACT_KEYS,
+ E2eeSelfKey.class);
if (value == null) {
return new ArrayList<>();
}
@@ -542,24 +552,24 @@ public final class ContactKeysManager {
}
/**
- * Returns all self key entries that are owned by the caller app.
+ * Returns all end-to-end encryption self key entries that are owned by the caller app.
*
- * @return a list of {@link SelfKey} objects containing the self key information, or
- * an empty list if no keys are found.
+ * @return a list of {@link E2eeSelfKey} objects containing the end-to-end encryption self key
+ * information, or an empty list if no self keys are found.
*/
@RequiresPermission(android.Manifest.permission.READ_CONTACTS)
@NonNull
- public List<SelfKey> getOwnerSelfKeys() {
+ public List<E2eeSelfKey> getOwnerE2eeSelfKeys() {
Bundle extras = new Bundle();
- Bundle response = nullSafeCall(mContentResolver, ContactKeys.GET_OWNER_SELF_KEYS_METHOD,
+ Bundle response = nullSafeCall(mContentResolver, E2eeContactKeys.GET_OWNER_SELF_KEYS_METHOD,
extras);
if (response == null) {
return new ArrayList<>();
}
- List<SelfKey> value = response.getParcelableArrayList(ContactKeys.KEY_CONTACT_KEYS,
- SelfKey.class);
+ List<E2eeSelfKey> value = response.getParcelableArrayList(E2eeContactKeys.KEY_CONTACT_KEYS,
+ E2eeSelfKey.class);
if (value == null) {
return new ArrayList<>();
}
@@ -567,24 +577,24 @@ public final class ContactKeysManager {
}
/**
- * Removes a self key entry given the deviceId and the inferred package name of the caller.
-
+ * Removes an end-to-end encryption self key entry given the deviceId and the inferred
+ * package name of the caller.
* @param deviceId an app-specified identifier for the device
* @param accountId an app-specified identifier for the account
*
* @return true if the entry was removed, false otherwise.
*/
@RequiresPermission(android.Manifest.permission.WRITE_CONTACTS)
- public boolean removeSelfKey(@NonNull String deviceId,
+ public boolean removeE2eeSelfKey(@NonNull String deviceId,
@NonNull String accountId) {
Bundle extras = new Bundle();
- extras.putString(ContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
- extras.putString(ContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
+ extras.putString(E2eeContactKeys.DEVICE_ID, Objects.requireNonNull(deviceId));
+ extras.putString(E2eeContactKeys.ACCOUNT_ID, Objects.requireNonNull(accountId));
Bundle response = nullSafeCall(mContentResolver,
- ContactKeys.REMOVE_SELF_KEY_METHOD, extras);
+ E2eeContactKeys.REMOVE_SELF_KEY_METHOD, extras);
- return response != null && response.getBoolean(ContactKeys.KEY_UPDATED_ROWS);
+ return response != null && response.getBoolean(E2eeContactKeys.KEY_UPDATED_ROWS);
}
private Bundle nullSafeCall(@NonNull ContentResolver resolver, @NonNull String method,
@@ -622,9 +632,9 @@ public final class ContactKeysManager {
public static final int VERIFICATION_STATE_VERIFIED = 2;
/** @hide */
- public static final class ContactKeys {
+ public static final class E2eeContactKeys {
- private ContactKeys() {}
+ private E2eeContactKeys() {}
/**
* <p>
@@ -636,14 +646,16 @@ public final class ContactKeysManager {
/**
* <p>
- * An app-specified identifier for the device for which the contact key can be used.
+ * An app-specified identifier for the device for which the end-to-end encryption
+ * contact key can be used.
* </p>
*/
public static final String DEVICE_ID = "device_id";
/**
* <p>
- * An app-specified identifier for the account for which the contact key can be used.
+ * An app-specified identifier for the account for which the end-to-end encryption
+ * contact key can be used.
* Usually a phone number.
* </p>
*/
@@ -718,29 +730,32 @@ public final class ContactKeysManager {
public static final String GET_CONTACT_KEY_METHOD = "getContactKey";
/**
- * The method to invoke in order to retrieve all contact keys.
+ * The method to invoke in order to retrieve all end-to-end encryption contact keys.
*/
public static final String GET_ALL_CONTACT_KEYS_METHOD = "getAllContactKeys";
/**
- * The method to invoke in order to retrieve contact keys that belong to the caller.
+ * The method to invoke in order to retrieve end-to-end encryption contact keys that belong
+ * to the caller.
*/
public static final String GET_OWNER_CONTACT_KEYS_METHOD = "getOwnerContactKeys";
/**
- * The method to invoke in order to update a contact key local verification state.
+ * The method to invoke in order to update an end-to-end encryption contact key local
+ * verification state.
*/
public static final String UPDATE_CONTACT_KEY_LOCAL_VERIFICATION_STATE_METHOD =
"updateContactKeyLocalVerificationState";
/**
- * The method to invoke in order to update a contact key remote verification state.
+ * The method to invoke in order to update an end-to-end encryption contact key remote
+ * verification state.
*/
public static final String UPDATE_CONTACT_KEY_REMOTE_VERIFICATION_STATE_METHOD =
"updateContactKeyRemoteVerificationState";
/**
- * The method to invoke in order to remove a contact key.
+ * The method to invoke in order to remove a end-to-end encryption contact key.
*/
public static final String REMOVE_CONTACT_KEY_METHOD = "removeContactKey";
@@ -776,12 +791,12 @@ public final class ContactKeysManager {
public static final String REMOVE_SELF_KEY_METHOD = "removeSelfKey";
/**
- * Key in the incoming Bundle for all the contact keys.
+ * Key in the incoming Bundle for all the end-to-end encryption contact keys.
*/
public static final String KEY_CONTACT_KEYS = "key_contact_keys";
/**
- * Key in the incoming Bundle for a single contact key.
+ * Key in the incoming Bundle for a single end-to-end encryption contact key.
*/
public static final String KEY_CONTACT_KEY = "key_contact_key";
@@ -790,18 +805,19 @@ public final class ContactKeysManager {
*/
public static final String KEY_UPDATED_ROWS = "key_updated_rows";
}
-
/**
* A parcelable class encapsulating other users' end to end encrypted contact key.
*/
- public static final class ContactKey implements Parcelable {
+ public static final class E2eeContactKey implements Parcelable {
/**
- * An app-specified identifier for the device for which the contact key can be used.
+ * An app-specified identifier for the device for which the end-to-end encryption
+ * contact key can be used.
*/
private final String mDeviceId;
/**
- * An app-specified identifier for the account for which the contact key can be used.
+ * An app-specified identifier for the account for which the end-to-end encryption
+ * contact key can be used.
* Usually a phone number.
*/
private final String mAccountId;
@@ -851,7 +867,7 @@ public final class ContactKeysManager {
/**
* @hide
*/
- public ContactKey(@Nullable String deviceId, @NonNull String accountId,
+ public E2eeContactKey(@Nullable String deviceId, @NonNull String accountId,
@NonNull String ownerPackageName, long timeUpdated, @Nullable byte[] keyValue,
@VerificationState int localVerificationState,
@VerificationState int remoteVerificationState,
@@ -870,8 +886,10 @@ public final class ContactKeysManager {
}
/**
- * Gets the app-specified identifier for the device for which the contact key can be used.
- * Returns null if the app doesn't have the required visibility into the contact key.
+ * Gets the app-specified identifier for the device for which the end-to-end encryption
+ * contact key can be used.
+ * Returns null if the app doesn't have the required visibility into
+ * the end-to-end encryption contact key.
*
* @return An app-specified identifier for the device.
*/
@@ -881,7 +899,8 @@ public final class ContactKeysManager {
}
/**
- * Gets the app-specified identifier for the account for which the contact key can be used.
+ * Gets the app-specified identifier for the account for which the end-to-end encryption
+ * contact key can be used.
* Usually a phone number.
*
* @return An app-specified identifier for the account.
@@ -903,7 +922,7 @@ public final class ContactKeysManager {
/**
* Gets the timestamp at which the key was updated. Returns -1 if the app doesn't have the
- * required visibility into the contact key.
+ * required visibility into the end-to-end encryption contact key.
*
* @return The timestamp at which the key was updated in the System.currentTimeMillis()
* base.
@@ -914,7 +933,8 @@ public final class ContactKeysManager {
/**
* Gets the raw bytes for the key.
- * Returns null if the app doesn't have the required visibility into the contact key.
+ * Returns null if the app doesn't have the required visibility into
+ * the end-to-end encryption contact key.
*
* @return A copy of the raw bytes for the key.
*/
@@ -984,7 +1004,7 @@ public final class ContactKeysManager {
if (obj == null) return false;
if (obj == this) return true;
- if (!(obj instanceof ContactKey toCompare)) {
+ if (!(obj instanceof E2eeContactKey toCompare)) {
return false;
}
@@ -1023,10 +1043,10 @@ public final class ContactKeysManager {
}
@NonNull
- public static final Creator<ContactKey> CREATOR =
+ public static final Creator<E2eeContactKey> CREATOR =
new Creator<>() {
@Override
- public ContactKey createFromParcel(Parcel source) {
+ public E2eeContactKey createFromParcel(Parcel source) {
String deviceId = source.readString8();
String accountId = source.readString8();
String ownerPackageName = source.readString8();
@@ -1044,14 +1064,14 @@ public final class ContactKeysManager {
String displayName = source.readString8();
String number = source.readString8();
String address = source.readString8();
- return new ContactKey(deviceId, accountId, ownerPackageName,
+ return new E2eeContactKey(deviceId, accountId, ownerPackageName,
timeUpdated, keyValue, localVerificationState,
remoteVerificationState, displayName, number, address);
}
@Override
- public ContactKey[] newArray(int size) {
- return new ContactKey[size];
+ public E2eeContactKey[] newArray(int size) {
+ return new E2eeContactKey[size];
}
};
}
@@ -1059,14 +1079,16 @@ public final class ContactKeysManager {
/**
* A parcelable class encapsulating self end to end encrypted contact key.
*/
- public static final class SelfKey implements Parcelable {
+ public static final class E2eeSelfKey implements Parcelable {
/**
- * An app-specified identifier for the device for which the contact key can be used.
+ * An app-specified identifier for the device for which the end-to-end encryption
+ * contact key can be used.
*/
private final String mDeviceId;
/**
- * An app-specified identifier for the account for which the contact key can be used.
+ * An app-specified identifier for the account for which the end-to-end encryption
+ * contact key can be used.
* Usually a phone number.
*/
private final String mAccountId;
@@ -1096,7 +1118,7 @@ public final class ContactKeysManager {
/**
* @hide
*/
- public SelfKey(@Nullable String deviceId, @NonNull String accountId,
+ public E2eeSelfKey(@Nullable String deviceId, @NonNull String accountId,
@NonNull String ownerPackageName, long timeUpdated, @Nullable byte[] keyValue,
@VerificationState int remoteVerificationState) {
this.mDeviceId = deviceId;
@@ -1108,8 +1130,10 @@ public final class ContactKeysManager {
}
/**
- * Gets the app-specified identifier for the device for which the contact key can be used.
- * Returns null if the app doesn't have the required visibility into the contact key.
+ * Gets the app-specified identifier for the device for which the end-to-end encryption
+ * contact key can be used.
+ * Returns null if the app doesn't have the required visibility into
+ * the end-to-end encryption contact key.
*
* @return An app-specified identifier for the device.
*/
@@ -1119,7 +1143,8 @@ public final class ContactKeysManager {
}
/**
- * Gets the app-specified identifier for the account for which the contact key can be used.
+ * Gets the app-specified identifier for the account for which the end-to-end encryption
+ * contact key can be used.
* Usually a phone number.
*
* @return An app-specified identifier for the device.
@@ -1141,7 +1166,7 @@ public final class ContactKeysManager {
/**
* Gets the timestamp at which the key was updated. Returns -1 if the app doesn't have the
- * required visibility into the contact key.
+ * required visibility into the end-to-end encryption contact key.
*
* @return The timestamp at which the key was updated in the System.currentTimeMillis()
* base.
@@ -1152,7 +1177,8 @@ public final class ContactKeysManager {
/**
* Gets the raw bytes for the key.
- * Returns null if the app doesn't have the required visibility into the contact key.
+ * Returns null if the app doesn't have the required visibility into
+ * the end-to-end encryption contact key.
*
* @return A copy of the raw bytes for the key.
*/
@@ -1182,7 +1208,7 @@ public final class ContactKeysManager {
if (obj == null) return false;
if (obj == this) return true;
- if (!(obj instanceof SelfKey toCompare)) {
+ if (!(obj instanceof E2eeSelfKey toCompare)) {
return false;
}
@@ -1213,10 +1239,10 @@ public final class ContactKeysManager {
}
@NonNull
- public static final Creator<SelfKey> CREATOR =
+ public static final Creator<E2eeSelfKey> CREATOR =
new Creator<>() {
@Override
- public SelfKey createFromParcel(Parcel source) {
+ public E2eeSelfKey createFromParcel(Parcel source) {
String deviceId = source.readString8();
String accountId = source.readString8();
String ownerPackageName = source.readString8();
@@ -1230,13 +1256,13 @@ public final class ContactKeysManager {
keyValue = null;
}
int remoteVerificationState = source.readInt();
- return new SelfKey(deviceId, accountId, ownerPackageName,
+ return new E2eeSelfKey(deviceId, accountId, ownerPackageName,
timeUpdated, keyValue, remoteVerificationState);
}
@Override
- public SelfKey[] newArray(int size) {
- return new SelfKey[size];
+ public E2eeSelfKey[] newArray(int size) {
+ return new E2eeSelfKey[size];
}
};
}
diff --git a/core/java/android/security/ConfirmationPrompt.java b/core/java/android/security/ConfirmationPrompt.java
index d8c44adcc322..f626149b03c4 100644
--- a/core/java/android/security/ConfirmationPrompt.java
+++ b/core/java/android/security/ConfirmationPrompt.java
@@ -92,7 +92,6 @@ public class ConfirmationPrompt {
private Executor mExecutor;
private Context mContext;
- private final KeyStore mKeyStore = KeyStore.getInstance();
private AndroidProtectedConfirmation mProtectedConfirmation;
private AndroidProtectedConfirmation getService() {
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 76314546b4f0..5e7edda31c19 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -31,6 +31,13 @@ flag {
}
flag {
+ name: "keyinfo_unlocked_device_required"
+ namespace: "hardware_backed_security"
+ description: "Add the API android.security.keystore.KeyInfo#isUnlockedDeviceRequired()"
+ bug: "296475382"
+}
+
+flag {
name: "deprecate_fsv_sig"
namespace: "hardware_backed_security"
description: "Feature flag for deprecating .fsv_sig"
diff --git a/core/java/android/security/keystore/recovery/RecoveryController.java b/core/java/android/security/keystore/recovery/RecoveryController.java
index f1054ec8ef15..c171c1b4b3b6 100644
--- a/core/java/android/security/keystore/recovery/RecoveryController.java
+++ b/core/java/android/security/keystore/recovery/RecoveryController.java
@@ -26,7 +26,6 @@ import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
-import android.security.KeyStore;
import android.security.KeyStore2;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore2.AndroidKeyStoreProvider;
@@ -272,11 +271,9 @@ public class RecoveryController {
public static final int ERROR_KEY_NOT_FOUND = 30;
private final ILockSettings mBinder;
- private final KeyStore mKeyStore;
- private RecoveryController(ILockSettings binder, KeyStore keystore) {
+ private RecoveryController(ILockSettings binder) {
mBinder = binder;
- mKeyStore = keystore;
}
/**
@@ -296,7 +293,7 @@ public class RecoveryController {
// lockSettings may be null.
ILockSettings lockSettings =
ILockSettings.Stub.asInterface(ServiceManager.getService("lock_settings"));
- return new RecoveryController(lockSettings, KeyStore.getInstance());
+ return new RecoveryController(lockSettings);
}
/**
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
index 00236dfa7876..d72441f1e4b7 100644
--- a/core/java/android/service/chooser/flags.aconfig
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -27,3 +27,14 @@ flag {
description: "Provides additional callbacks with information about user actions in ChooserResult"
bug: "263474465"
}
+
+flag {
+ name: "legacy_chooser_pinning_removal"
+ namespace: "intentresolver"
+ description: "Removing pinning functionality from the legacy chooser (used by partial screenshare)"
+ bug: "301068735"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 00657ea35e02..66655fca8fc3 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -188,8 +188,7 @@ public class HandwritingInitiator {
// check whether the stylus we are tracking goes up.
if (mState != null) {
mState.mShouldInitHandwriting = false;
- if (!mState.mHasInitiatedHandwriting
- && !mState.mHasPreparedHandwritingDelegation) {
+ if (!mState.mHandled) {
// The user just did a click, long click or another stylus gesture,
// show hover icon again for the connected view.
mShowHoverIconForConnectedView = true;
@@ -204,16 +203,14 @@ public class HandwritingInitiator {
// Either we've already tried to initiate handwriting, or the ongoing MotionEvent
// sequence is considered to be tap, long-click or other gestures.
if (!mState.mShouldInitHandwriting || mState.mExceedHandwritingSlop) {
- return mState.mHasInitiatedHandwriting
- || mState.mHasPreparedHandwritingDelegation;
+ return mState.mHandled;
}
final long timeElapsed =
motionEvent.getEventTime() - mState.mStylusDownTimeInMillis;
if (timeElapsed > mHandwritingTimeoutInMillis) {
mState.mShouldInitHandwriting = false;
- return mState.mHasInitiatedHandwriting
- || mState.mHasPreparedHandwritingDelegation;
+ return mState.mHandled;
}
final int pointerIndex = motionEvent.findPointerIndex(mState.mStylusPointerId);
@@ -223,7 +220,7 @@ public class HandwritingInitiator {
mState.mExceedHandwritingSlop = true;
View candidateView = findBestCandidateView(mState.mStylusDownX,
mState.mStylusDownY, /* isHover */ false);
- if (candidateView != null) {
+ if (candidateView != null && candidateView.isEnabled()) {
if (candidateView == getConnectedOrFocusedView()) {
if (!mInitiateWithoutConnection && !candidateView.hasFocus()) {
requestFocusWithoutReveal(candidateView);
@@ -246,7 +243,7 @@ public class HandwritingInitiator {
}
}
}
- return mState.mHasInitiatedHandwriting || mState.mHasPreparedHandwritingDelegation;
+ return mState.mHandled;
}
return false;
}
@@ -382,7 +379,7 @@ public class HandwritingInitiator {
@VisibleForTesting
public void startHandwriting(@NonNull View view) {
mImm.startStylusHandwriting(view);
- mState.mHasInitiatedHandwriting = true;
+ mState.mHandled = true;
mState.mShouldInitHandwriting = false;
mShowHoverIconForConnectedView = false;
if (view instanceof TextView) {
@@ -402,13 +399,12 @@ public class HandwritingInitiator {
mImm.startConnectionlessStylusHandwritingForDelegation(
view, getCursorAnchorInfoForConnectionless(view), delegatePackageName,
view::post, new DelegationCallback(view, delegatePackageName));
- mState.mHasInitiatedHandwriting = true;
mState.mShouldInitHandwriting = false;
} else {
mImm.prepareStylusHandwritingDelegation(view, delegatePackageName);
view.getHandwritingDelegatorCallback().run();
- mState.mHasPreparedHandwritingDelegation = true;
}
+ mState.mHandled = true;
}
/**
@@ -455,7 +451,7 @@ public class HandwritingInitiator {
private void onDelegationAccepted(View view) {
if (mState != null) {
- mState.mHasInitiatedHandwriting = true;
+ mState.mHandled = true;
mState.mShouldInitHandwriting = false;
}
if (view instanceof TextView) {
@@ -795,12 +791,12 @@ public class HandwritingInitiator {
* This boolean will be set to false, and it won't request to start handwriting.
*/
private boolean mShouldInitHandwriting;
+
/**
- * Whether handwriting mode has already been initiated for the current MotionEvent sequence.
+ * Whether the current MotionEvent sequence has been handled by the handwriting initiator,
+ * either by initiating handwriting mode, or by preparing handwriting delegation.
*/
- private boolean mHasInitiatedHandwriting;
-
- private boolean mHasPreparedHandwritingDelegation;
+ private boolean mHandled;
/**
* Whether the current ongoing stylus MotionEvent sequence already exceeds the
@@ -838,8 +834,7 @@ public class HandwritingInitiator {
mStylusDownY = motionEvent.getY(actionIndex);
mShouldInitHandwriting = true;
- mHasInitiatedHandwriting = false;
- mHasPreparedHandwritingDelegation = false;
+ mHandled = false;
mExceedHandwritingSlop = false;
}
}
@@ -1052,8 +1047,6 @@ public class HandwritingInitiator {
// Fall back to the old delegation flow
mImm.prepareStylusHandwritingDelegation(mView, mDelegatePackageName);
mView.getHandwritingDelegatorCallback().run();
- mState.mHasInitiatedHandwriting = false;
- mState.mHasPreparedHandwritingDelegation = true;
break;
}
}
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index ad326e41a146..a2f767d002f4 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -102,6 +102,7 @@ per-file ISurfaceControlViewHost*.aidl = file:/services/core/java/com/android/se
per-file IWindow*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file RemoteAnimation*.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file RemoteAnimation*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ScreenRecordingCallbacks.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file *SurfaceControl*.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file SurfaceControl*.aidl = file:/services/core/java/com/android/server/wm/OWNERS
per-file SurfaceSession.java = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
index 13b9c4518711..f5f4fd9f7042 100644
--- a/core/java/android/view/PointerIcon.java
+++ b/core/java/android/view/PointerIcon.java
@@ -480,11 +480,19 @@ public final class PointerIcon implements Parcelable {
mBitmapFrames = new Bitmap[frames - 1];
final int width = drawable.getIntrinsicWidth();
final int height = drawable.getIntrinsicHeight();
+ final boolean isVectorAnimation = drawable instanceof VectorDrawable;
+ mDrawNativeDropShadow = isVectorAnimation;
for (int i = 1; i < frames; ++i) {
Drawable drawableFrame = animationDrawable.getFrame(i);
- if (!(drawableFrame instanceof BitmapDrawable)) {
+ if (!(drawableFrame instanceof BitmapDrawable)
+ && !(drawableFrame instanceof VectorDrawable)) {
throw new IllegalArgumentException("Frame of an animated pointer icon "
- + "must refer to a bitmap drawable.");
+ + "must refer to a bitmap drawable or vector drawable.");
+ }
+ if (isVectorAnimation != (drawableFrame instanceof VectorDrawable)) {
+ throw new IllegalArgumentException("The drawable of the " + i + "-th frame "
+ + "is a different type from the others. All frames should be the "
+ + "same type.");
}
if (drawableFrame.getIntrinsicWidth() != width ||
drawableFrame.getIntrinsicHeight() != height) {
@@ -492,8 +500,11 @@ public final class PointerIcon implements Parcelable {
+ "is different. All frames should have the exact same size and "
+ "share the same hotspot.");
}
- BitmapDrawable bitmapDrawableFrame = (BitmapDrawable) drawableFrame;
- mBitmapFrames[i - 1] = getBitmapFromDrawable(bitmapDrawableFrame);
+ if (isVectorAnimation) {
+ drawableFrame = getBitmapDrawableFromVectorDrawable(resources,
+ (VectorDrawable) drawableFrame);
+ }
+ mBitmapFrames[i - 1] = getBitmapFromDrawable((BitmapDrawable) drawableFrame);
}
}
}
diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java
index f68cc691abeb..1f841bf53364 100644
--- a/core/java/android/view/RoundedCorners.java
+++ b/core/java/android/view/RoundedCorners.java
@@ -198,6 +198,12 @@ public class RoundedCorners implements Parcelable {
radius = res.getDimensionPixelSize(R.dimen.rounded_corner_radius);
}
array.recycle();
+ // For devices with round displays (e.g. watches) that don't otherwise provide the rounded
+ // corner radius via resource overlays, we can infer the corner radius directly from the
+ // display size.
+ if (radius == 0 && res.getConfiguration().isScreenRound()) {
+ radius = res.getDisplayMetrics().widthPixels / 2;
+ }
return radius;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cbb3be7c4155..042af1f0fb15 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15314,6 +15314,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* {@link #isLongClickable()}, {@link #isContextClickable()},
* {@link #isScreenReaderFocusable()}, or {@link #isFocusable()}
* <li>Has an {@link AccessibilityDelegate}
+ * <li>Has an {@link AccessibilityNodeProvider}
* <li>Has an interaction listener, e.g. {@link OnTouchListener},
* {@link OnKeyListener}, etc.
* <li>Is an accessibility live region, e.g.
@@ -15346,7 +15347,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
return mode == IMPORTANT_FOR_ACCESSIBILITY_YES || isActionableForAccessibility()
- || hasListenersForAccessibility() || mAccessibilityDelegate != null
+ || hasListenersForAccessibility() || getAccessibilityNodeProvider() != null
+ || getAccessibilityDelegate() != null
|| getAccessibilityLiveRegion() != ACCESSIBILITY_LIVE_REGION_NONE
|| isAccessibilityPane() || isAccessibilityHeading();
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b5f3b9a8fa2d..333cbb39d9c7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -95,6 +95,7 @@ import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.accessibility.Flags.fixMergedContentChangeEvent;
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
+import static android.view.flags.Flags.toolkitFrameRateTypingReadOnly;
import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
@@ -1061,9 +1062,6 @@ public final class ViewRootImpl implements ViewParent,
* the variables below are used to determine whther a dVRR feature should be enabled
*/
- // Used to determine whether to suppress boost on typing
- private boolean mShouldSuppressBoostOnTyping = false;
-
/**
* A temporary object used so relayoutWindow can return the latest SyncSeqId
* system. The SyncSeqId system was designed to work without synchronous relayout
@@ -1117,10 +1115,12 @@ public final class ViewRootImpl implements ViewParent,
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
+ private static boolean sToolkitFrameRateTypingReadOnlyFlagValue;
static {
sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
+ sToolkitFrameRateTypingReadOnlyFlagValue = toolkitFrameRateTypingReadOnly();
}
// The latest input event from the gesture that was used to resolve the pointer icon.
@@ -12417,7 +12417,8 @@ public final class ViewRootImpl implements ViewParent,
boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
|| motionEventAction == MotionEvent.ACTION_MOVE
|| motionEventAction == MotionEvent.ACTION_UP;
- boolean undesiredType = windowType == TYPE_INPUT_METHOD && mShouldSuppressBoostOnTyping;
+ boolean undesiredType = windowType == TYPE_INPUT_METHOD
+ && sToolkitFrameRateTypingReadOnlyFlagValue;
// use toolkitSetFrameRate flag to gate the change
return desiredAction && !undesiredType && shouldEnableDvrr()
&& getFrameRateBoostOnTouchEnabled();
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 9d613bcae29a..05cabd56f532 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -74,4 +74,12 @@ flag {
description: "Feature flag for setting frame rate based on velocity"
bug: "239979904"
is_fixed_read_only: true
+}
+
+flag {
+ name: "toolkit_frame_rate_typing_read_only"
+ namespace: "toolkit"
+ description: "Feature flag for suppressing boost on typing"
+ bug: "239979904"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 74e1d10cdd44..b1fdaa97ffe0 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -340,15 +340,6 @@ public interface ImeTracker {
@SoftInputShowHideReason int reason, boolean fromUser);
/**
- * Alias for {@link #onRequestShow(String, int, int, int, boolean)} with
- * {@code fromUser} set to {@code false}.
- */
- default Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
- @SoftInputShowHideReason int reason) {
- return onRequestShow(component, uid, origin, reason, false /* fromUser */);
- }
-
- /**
* Creates an IME hide request tracking token.
*
* @param component the name of the component that created the IME request, or {@code null}
@@ -365,15 +356,6 @@ public interface ImeTracker {
@SoftInputShowHideReason int reason, boolean fromUser);
/**
- * Alias for {@link #onRequestHide(String, int, int, int, boolean)} with
- * {@code fromUser} set to {@code false}.
- */
- default Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
- @SoftInputShowHideReason int reason) {
- return onRequestHide(component, uid, origin, reason, false /* fromUser */);
- }
-
- /**
* Called when an IME request progresses to a further phase.
*
* @param token the token tracking the current IME request or {@code null} otherwise.
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 7c9678f11e0e..16fecc17d426 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -497,6 +497,25 @@ public final class InputMethodInfo implements Parcelable {
* @hide
*/
@TestApi
+ public InputMethodInfo(@NonNull String packageName, @NonNull String className,
+ @NonNull CharSequence label, @NonNull String settingsActivity,
+ @NonNull String languageSettingsActivity, boolean supportStylusHandwriting,
+ @NonNull String stylusHandwritingSettingsActivityAttr) {
+ this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
+ settingsActivity, languageSettingsActivity, null /* subtypes */,
+ 0 /* isDefaultResId */, false /* forceDefault */,
+ true /* supportsSwitchingToNextInputMethod */,
+ false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
+ false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */,
+ supportStylusHandwriting, false /* supportConnectionlessStylusHandwriting */,
+ stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */);
+ }
+
+ /**
+ * Test API for creating a built-in input method to verify stylus handwriting.
+ * @hide
+ */
+ @TestApi
@FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
public InputMethodInfo(@NonNull String packageName, @NonNull String className,
@NonNull CharSequence label, @NonNull String settingsActivity,
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3bce155049c8..fcc8344cbcd9 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2328,7 +2328,7 @@ public final class InputMethodManager {
synchronized (mH) {
final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestShow(
null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
- SoftInputShowHideReason.SHOW_SOFT_INPUT);
+ SoftInputShowHideReason.SHOW_SOFT_INPUT, false /* fromUser */);
Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
+ " removed soon. If you are using androidx.appcompat.widget.SearchView,"
@@ -3490,8 +3490,7 @@ public final class InputMethodManager {
return false;
}
mServedView = mNextServedView;
- if (initiationWithoutInputConnection() && mServedView.onCheckIsTextEditor()
- && mServedView.isHandwritingDelegate()) {
+ if (initiationWithoutInputConnection() && mServedView.isHandwritingDelegate()) {
mServedView.getViewRootImpl().getHandwritingInitiator().onDelegateViewFocused(
mServedView);
}
@@ -3539,7 +3538,7 @@ public final class InputMethodManager {
void closeCurrentInput() {
final ImeTracker.Token statsToken = ImeTracker.forLogging().onRequestHide(
null /* component */, Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
- SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION);
+ SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION, false /* fromUser */);
ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION,
ActivityThread::currentApplication);
@@ -3639,7 +3638,7 @@ public final class InputMethodManager {
if (statsToken == null) {
statsToken = ImeTracker.forLogging().onRequestHide(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
- SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+ SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API, false /* fromUser */);
}
ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API,
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 13dc4efb374d..0e5747d0e445 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -3874,7 +3874,7 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- private static class SetDrawInstructionAction extends Action {
+ private class SetDrawInstructionAction extends Action {
@Nullable
private final DrawInstructions mInstructions;
@@ -3909,6 +3909,15 @@ public class RemoteViews implements Parcelable, Filter {
}
try (ByteArrayInputStream is = new ByteArrayInputStream(bytes.get(0))) {
player.setDocument(new RemoteComposeDocument(is));
+ player.addClickListener((viewId, metadata) -> {
+ mActions.forEach(action -> {
+ if (viewId == action.mViewId
+ && action instanceof SetOnClickResponse setOnClickResponse) {
+ setOnClickResponse.mResponse.handleViewInteraction(
+ player, params.handler);
+ }
+ });
+ });
} catch (IOException e) {
Log.e(LOG_TAG, "Failed to render draw instructions", e);
}
@@ -6051,16 +6060,6 @@ public class RemoteViews implements Parcelable, Filter {
RemoteViews rvToApply = getRemoteViewsToApply(context, size);
View result = inflateView(context, rvToApply, directParent,
params.applyThemeResId, params.colorResources);
- if (result instanceof RemoteComposePlayer player) {
- player.addClickListener((viewId, metadata) -> {
- mActions.forEach(action -> {
- if (viewId == action.mViewId
- && action instanceof SetOnClickResponse setOnClickResponse) {
- setOnClickResponse.mResponse.handleViewInteraction(player, params.handler);
- }
- });
- });
- }
rvToApply.performApply(result, rootParent, params);
return result;
}
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
index bfe3d052479b..e60fa157e8e4 100644
--- a/core/java/android/widget/flags/notification_widget_flags.aconfig
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -15,4 +15,14 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "conversation_style_set_avatar_async"
+ namespace: "systemui"
+ description: "Offloads conversation avatar drawable loading to the background thread"
+ bug: "305540309"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/android/window/flags/OWNERS b/core/java/android/window/flags/OWNERS
index 3fa376003123..fd73d35e00d2 100644
--- a/core/java/android/window/flags/OWNERS
+++ b/core/java/android/window/flags/OWNERS
@@ -1,2 +1,3 @@
per-file responsible_apis.aconfig = file:/BAL_OWNERS
per-file large_screen_experiences_app_compat.aconfig = file:/LSE_APP_COMPAT_OWNERS
+per-file accessibility.aconfig = file:/core/java/android/view/accessibility/OWNERS
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index 82067defd336..254f4f77c100 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -77,3 +77,10 @@ flag {
bug: "309593314"
is_fixed_read_only: true
}
+
+flag {
+ name: "letterbox_background_wallpaper"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether the blurred letterbox wallpaper background is enabled by default"
+ bug: "297195682"
+}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 4e8661652e85..63a2474e70c1 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -7,3 +7,10 @@ flag {
bug: "320350734"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_desktop_windowing_mode"
+ namespace: "lse_desktop_experience"
+ description: "Enables desktop windowing"
+ bug: "304778354"
+}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 86d3037b1612..29669d312b1b 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -96,6 +96,7 @@ import android.provider.Downloads;
import android.provider.OpenableColumns;
import android.provider.Settings;
import android.service.chooser.ChooserTarget;
+import android.service.chooser.Flags;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.HashedStringCache;
@@ -2543,6 +2544,9 @@ public class ChooserActivity extends ResolverActivity implements
@Override
public boolean isComponentPinned(ComponentName name) {
+ if (Flags.legacyChooserPinningRemoval()) {
+ return false;
+ }
return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
}
@@ -3147,6 +3151,10 @@ public class ChooserActivity extends ResolverActivity implements
}
private boolean shouldShowTargetDetails(TargetInfo ti) {
+ if (Flags.legacyChooserPinningRemoval()) {
+ // Never show the long press menu if we've removed pinning.
+ return false;
+ }
ComponentName nearbyShare = getNearbySharingComponent();
// Suppress target details for nearby share to hide pin/unpin action
boolean isNearbyShare = nearbyShare != null && nearbyShare.equals(
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index 73df5e8e7b1b..5e89d06facd1 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -421,6 +421,8 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
@NonNull
private String[] mUsesStaticLibrariesSorted;
+ private boolean mAppMetadataFileInApk = false;
+
@NonNull
public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath,
@NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp,
@@ -1063,6 +1065,11 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
return memtagMode;
}
+ @Override
+ public boolean isAppMetadataFileInApk() {
+ return mAppMetadataFileInApk;
+ }
+
@Nullable
@Override
public Bundle getMetaData() {
@@ -2151,6 +2158,12 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
}
@Override
+ public PackageImpl setAppMetadataFileInApk(boolean fileInApk) {
+ mAppMetadataFileInApk = fileInApk;
+ return this;
+ }
+
+ @Override
public PackageImpl setMetaData(@Nullable Bundle value) {
metaData = value;
return this;
@@ -3264,6 +3277,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
dest.writeLong(this.mBooleans);
dest.writeLong(this.mBooleans2);
dest.writeBoolean(this.mAllowCrossUidActivitySwitchFromBelow);
+ dest.writeBoolean(this.mAppMetadataFileInApk);
}
public PackageImpl(Parcel in) {
@@ -3426,6 +3440,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
this.mBooleans = in.readLong();
this.mBooleans2 = in.readLong();
this.mAllowCrossUidActivitySwitchFromBelow = in.readBoolean();
+ this.mAppMetadataFileInApk = in.readBoolean();
assignDerivedFields();
assignDerivedFields2();
diff --git a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
index 66cfb69d7871..3c26a7c3eba3 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/ParsedPackage.java
@@ -127,4 +127,7 @@ public interface ParsedPackage extends AndroidPackage {
ParsedPackage setDirectBootAware(boolean directBootAware);
ParsedPackage setPersistent(boolean persistent);
+
+ /** Retrieves whether the apk contains a app metadata file. */
+ boolean isAppMetadataFileInApk();
}
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 5d185af17d48..5ab17a6b2b19 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -133,6 +133,9 @@ public interface ParsingPackage {
@Nullable SparseArray<int[]> splitDependencies
);
+ /** Sets whether the apk contains a app metadata file. */
+ ParsingPackage setAppMetadataFileInApk(boolean fileInApk);
+
ParsingPackage setMetaData(Bundle metaData);
ParsingPackage setForceQueryable(boolean forceQueryable);
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 2e6053d3d904..00c482722fa3 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -46,6 +46,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
import android.content.pm.FeatureGroupInfo;
import android.content.pm.FeatureInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.Property;
@@ -163,6 +164,8 @@ public class ParsingPackageUtils {
*/
public static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
+ public static final String APP_METADATA_FILE_NAME = "app.metadata";
+
/**
* Path prefix for apps on expanded storage
*/
@@ -636,6 +639,11 @@ public class ParsingPackageUtils {
pkg.setSigningDetails(SigningDetails.UNKNOWN);
}
+ if (Flags.aslInApkAppMetadataSource()
+ && ArrayUtils.contains(assets.list(""), APP_METADATA_FILE_NAME)) {
+ pkg.setAppMetadataFileInApk(true);
+ }
+
return input.success(pkg);
} catch (Exception e) {
return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
@@ -686,7 +694,8 @@ public class ParsingPackageUtils {
*/
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
String codePath, Resources res, XmlResourceParser parser, int flags,
- boolean shouldSkipComponents) throws XmlPullParserException, IOException {
+ boolean shouldSkipComponents)
+ throws XmlPullParserException, IOException {
final String splitName;
final String pkgName;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index d463b62e62a3..6ffc63869f26 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -360,8 +360,12 @@ oneway interface IStatusBar
/** Shows rear display educational dialog */
void showRearDisplayDialog(int currentBaseState);
- /** Called when requested to go to fullscreen from the active split app. */
- void goToFullscreenFromSplit();
+ /**
+ * Called when requested to go to fullscreen from the focused app.
+ *
+ * @param displayId the id of the current display.
+ */
+ void moveFocusedTaskToFullscreen(int displayId);
/**
* Enters stage split from a current running app.
diff --git a/core/java/com/android/internal/widget/ConversationAvatarData.java b/core/java/com/android/internal/widget/ConversationAvatarData.java
new file mode 100644
index 000000000000..e04772f72516
--- /dev/null
+++ b/core/java/com/android/internal/widget/ConversationAvatarData.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * @hide
+ */
+interface ConversationAvatarData {
+ final class OneToOneConversationAvatarData implements ConversationAvatarData {
+ final Drawable mDrawable;
+
+ OneToOneConversationAvatarData(Drawable drawable) {
+ mDrawable = drawable;
+ }
+ }
+
+ final class GroupConversationAvatarData implements ConversationAvatarData {
+ final Drawable mLastIcon;
+ final Drawable mSecondLastIcon;
+
+ GroupConversationAvatarData(Drawable lastIcon, Drawable secondLastIcon) {
+ mLastIcon = lastIcon;
+ mSecondLastIcon = secondLastIcon;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/ConversationHeaderData.java b/core/java/com/android/internal/widget/ConversationHeaderData.java
new file mode 100644
index 000000000000..0953b3912a91
--- /dev/null
+++ b/core/java/com/android/internal/widget/ConversationHeaderData.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.Nullable;
+
+/**
+ * @hide
+ */
+final class ConversationHeaderData {
+ private final CharSequence mConversationText;
+
+ private final ConversationAvatarData mConversationAvatarData;
+
+ ConversationHeaderData(CharSequence conversationText,
+ ConversationAvatarData conversationAvatarData) {
+ mConversationText = conversationText;
+ mConversationAvatarData = conversationAvatarData;
+ }
+
+ @Nullable
+ CharSequence getConversationText() {
+ return mConversationText;
+ }
+
+ @Nullable
+ ConversationAvatarData getConversationAvatar() {
+ return mConversationAvatarData;
+ }
+}
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 889434f40472..06835f00755f 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -34,8 +34,10 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Rect;
import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.text.Spannable;
@@ -59,8 +61,11 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
+import android.widget.flags.Flags;
import com.android.internal.R;
+import com.android.internal.widget.ConversationAvatarData.GroupConversationAvatarData;
+import com.android.internal.widget.ConversationAvatarData.OneToOneConversationAvatarData;
import java.util.ArrayList;
import java.util.List;
@@ -403,11 +408,14 @@ public class ConversationLayout extends FrameLayout
*/
@RemotableViewMethod(asyncImpl = "setDataAsync")
public void setData(Bundle extras) {
- bind(parseMessagingData(extras, /* usePrecomputedText= */ false));
+ bind(parseMessagingData(extras,
+ /* usePrecomputedText= */ false,
+ /*includeConversationIcon= */false));
}
@NonNull
- private MessagingData parseMessagingData(Bundle extras, boolean usePrecomputedText) {
+ private MessagingData parseMessagingData(Bundle extras, boolean usePrecomputedText,
+ boolean includeConversationIcon) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
List<Notification.MessagingStyle.Message> newMessages =
Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
@@ -438,8 +446,20 @@ public class ConversationLayout extends FrameLayout
// Lets first find the groups (populate `groups` and `senders`)
findGroups(newHistoricMessagingMessages, newMessagingMessages, user, groups, senders);
+ // load conversation header data, avatar and title.
+ final ConversationHeaderData conversationHeaderData;
+ if (includeConversationIcon && Flags.conversationStyleSetAvatarAsync()) {
+ conversationHeaderData = loadConversationHeaderData(mIsOneToOne,
+ mConversationTitle,
+ mShortcutIcon,
+ mLargeIcon, newMessagingMessages, user, groups, mLayoutColor);
+ } else {
+ conversationHeaderData = null;
+ }
+
return new MessagingData(user, showSpinner, unreadCount,
- newHistoricMessagingMessages, newMessagingMessages, groups, senders);
+ newHistoricMessagingMessages, newMessagingMessages, groups, senders,
+ conversationHeaderData);
}
/**
@@ -457,7 +477,9 @@ public class ConversationLayout extends FrameLayout
}
final MessagingData messagingData =
- parseMessagingData(extras, /* usePrecomputedText= */ true);
+ parseMessagingData(extras,
+ /* usePrecomputedText= */ true,
+ /*includeConversationIcon=*/true);
return () -> {
finalizeInflate(messagingData.getHistoricMessagingMessages());
@@ -536,11 +558,10 @@ public class ConversationLayout extends FrameLayout
mMessages = messagingData.getNewMessagingMessages();
mHistoricMessages = messagingData.getHistoricMessagingMessages();
-
updateHistoricMessageVisibility();
updateTitleAndNamesDisplay();
- updateConversationLayout();
+ updateConversationLayout(messagingData);
// Recycle everything at the end of the update, now that we know it's no longer needed.
for (MessagingLinearLayout.MessagingChild child : mToRecycle) {
@@ -552,7 +573,31 @@ public class ConversationLayout extends FrameLayout
/**
* Update the layout according to the data provided (i.e mIsOneToOne, expanded etc);
*/
- private void updateConversationLayout() {
+ private void updateConversationLayout(MessagingData messagingData) {
+ if (!Flags.conversationStyleSetAvatarAsync()) {
+ computeAndSetConversationAvatarAndName();
+ } else {
+ ConversationHeaderData conversationHeaderData =
+ messagingData.getConversationHeaderData();
+ if (conversationHeaderData == null) {
+ conversationHeaderData = loadConversationHeaderData(mIsOneToOne,
+ mConversationTitle, mShortcutIcon, mLargeIcon, mMessages, mUser,
+ messagingData.getGroups(),
+ mLayoutColor);
+ }
+ setConversationAvatarAndNameFromData(conversationHeaderData);
+ }
+
+ updateAppName();
+ updateIconPositionAndSize();
+ updateImageMessages();
+ updatePaddingsBasedOnContentAvailability();
+ updateActionListPadding();
+ updateAppNameDividerVisibility();
+ }
+
+ @Deprecated
+ private void computeAndSetConversationAvatarAndName() {
// Set avatar and name
CharSequence conversationText = mConversationTitle;
mConversationIcon = mShortcutIcon;
@@ -603,12 +648,43 @@ public class ConversationLayout extends FrameLayout
// Update if the groups can hide the sender if they are first (applies to 1:1 conversations)
// This needs to happen after all of the above o update all of the groups
mPeopleHelper.maybeHideFirstSenderName(mGroups, mIsOneToOne, conversationText);
- updateAppName();
- updateIconPositionAndSize();
- updateImageMessages();
- updatePaddingsBasedOnContentAvailability();
- updateActionListPadding();
- updateAppNameDividerVisibility();
+ }
+
+ private void setConversationAvatarAndNameFromData(
+ ConversationHeaderData conversationHeaderData) {
+ final OneToOneConversationAvatarData oneToOneConversationDrawable;
+ final GroupConversationAvatarData groupConversationAvatarData;
+ final ConversationAvatarData conversationAvatar =
+ conversationHeaderData.getConversationAvatar();
+ if (conversationAvatar instanceof OneToOneConversationAvatarData) {
+ oneToOneConversationDrawable =
+ ((OneToOneConversationAvatarData) conversationAvatar);
+ groupConversationAvatarData = null;
+ } else {
+ oneToOneConversationDrawable = null;
+ groupConversationAvatarData = ((GroupConversationAvatarData) conversationAvatar);
+ }
+
+ if (oneToOneConversationDrawable != null) {
+ mConversationIconView.setVisibility(VISIBLE);
+ mConversationFacePile.setVisibility(GONE);
+ mConversationIconView.setImageDrawable(oneToOneConversationDrawable.mDrawable);
+ } else {
+ mConversationIconView.setVisibility(GONE);
+ // This will also inflate it!
+ mConversationFacePile.setVisibility(VISIBLE);
+ // rebind the value to the inflated view instead of the stub
+ mConversationFacePile = findViewById(R.id.conversation_face_pile);
+ bindFacePile(groupConversationAvatarData);
+ }
+ CharSequence conversationText = conversationHeaderData.getConversationText();
+ if (TextUtils.isEmpty(conversationText)) {
+ conversationText = mIsOneToOne ? mFallbackChatName : mFallbackGroupChatName;
+ }
+ mConversationText.setText(conversationText);
+ // Update if the groups can hide the sender if they are first (applies to 1:1 conversations)
+ // This needs to happen after all of the above o update all of the groups
+ mPeopleHelper.maybeHideFirstSenderName(mGroups, mIsOneToOne, conversationText);
}
private void updateActionListPadding() {
@@ -675,7 +751,12 @@ public class ConversationLayout extends FrameLayout
topView.setImageIcon(secondLastIcon);
}
+ @Deprecated
private void bindFacePile() {
+ bindFacePile(null);
+ }
+
+ private void bindFacePile(@Nullable GroupConversationAvatarData groupConversationAvatarData) {
ImageView bottomBackground = mConversationFacePile.findViewById(
R.id.conversation_face_pile_bottom_background);
ImageView bottomView = mConversationFacePile.findViewById(
@@ -683,7 +764,13 @@ public class ConversationLayout extends FrameLayout
ImageView topView = mConversationFacePile.findViewById(
R.id.conversation_face_pile_top);
- bindFacePile(bottomBackground, bottomView, topView);
+ if (groupConversationAvatarData == null) {
+ bindFacePile(bottomBackground, bottomView, topView);
+ } else {
+ bindFacePileWithDrawable(bottomBackground, bottomView, topView,
+ groupConversationAvatarData);
+
+ }
int conversationAvatarSize;
int facepileAvatarSize;
@@ -718,6 +805,13 @@ public class ConversationLayout extends FrameLayout
bottomBackground.setLayoutParams(layoutParams);
}
+ private void bindFacePileWithDrawable(ImageView bottomBackground, ImageView bottomView,
+ ImageView topView, GroupConversationAvatarData groupConversationAvatarData) {
+ applyNotificationBackgroundColor(bottomBackground);
+ bottomView.setImageDrawable(groupConversationAvatarData.mLastIcon);
+ topView.setImageDrawable(groupConversationAvatarData.mSecondLastIcon);
+ }
+
private void updateAppName() {
mAppName.setVisibility(mIsCollapsed ? GONE : VISIBLE);
}
@@ -789,22 +883,62 @@ public class ConversationLayout extends FrameLayout
mMessagingLinearLayout.getPaddingBottom());
}
+ /**
+ * async version of {@link ConversationLayout#setLargeIcon}
+ */
@RemotableViewMethod
+ public Runnable setLargeIconAsync(Icon largeIcon) {
+ if (!Flags.conversationStyleSetAvatarAsync()) {
+ return () -> setLargeIcon(largeIcon);
+ }
+
+ mLargeIcon = largeIcon;
+ return NotificationRunnables.NOOP;
+ }
+
+ @RemotableViewMethod(asyncImpl = "setLargeIconAsync")
public void setLargeIcon(Icon largeIcon) {
mLargeIcon = largeIcon;
}
+ /**
+ * async version of {@link ConversationLayout#setShortcutIcon}
+ */
@RemotableViewMethod
+ public Runnable setShortcutIconAsync(Icon shortcutIcon) {
+ if (!Flags.conversationStyleSetAvatarAsync()) {
+ return () -> setShortcutIcon(shortcutIcon);
+ }
+
+ mShortcutIcon = shortcutIcon;
+ return NotificationRunnables.NOOP;
+ }
+
+ @RemotableViewMethod(asyncImpl = "setShortcutIconAsync")
public void setShortcutIcon(Icon shortcutIcon) {
mShortcutIcon = shortcutIcon;
}
/**
+ * async version of {@link ConversationLayout#setConversationTitle}
+ */
+ @RemotableViewMethod
+ public Runnable setConversationTitleAsync(CharSequence conversationTitle) {
+ if (!Flags.conversationStyleSetAvatarAsync()) {
+ return () -> setConversationTitle(conversationTitle);
+ }
+
+ // Remove formatting from the title.
+ mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null;
+ return NotificationRunnables.NOOP;
+ }
+
+ /**
* Sets the conversation title of this conversation.
*
* @param conversationTitle the conversation title
*/
- @RemotableViewMethod
+ @RemotableViewMethod(asyncImpl = "setConversationTitleAsync")
public void setConversationTitle(CharSequence conversationTitle) {
// Remove formatting from the title.
mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null;
@@ -888,12 +1022,37 @@ public class ConversationLayout extends FrameLayout
}
}
+ /**
+ * async version of {@link ConversationLayout#setLayoutColor}
+ */
@RemotableViewMethod
+ public Runnable setLayoutColorAsync(int color) {
+ if (!Flags.conversationStyleSetAvatarAsync()) {
+ return () -> setLayoutColor(color);
+ }
+
+ mLayoutColor = color;
+ return NotificationRunnables.NOOP;
+ }
+
+ @RemotableViewMethod(asyncImpl = "setLayoutColorAsync")
public void setLayoutColor(int color) {
mLayoutColor = color;
}
+ /**
+ * async version of {@link ConversationLayout#setIsOneToOne}
+ */
@RemotableViewMethod
+ public Runnable setIsOneToOneAsync(boolean oneToOne) {
+ if (!Flags.conversationStyleSetAvatarAsync()) {
+ return () -> setIsOneToOne(oneToOne);
+ }
+ mIsOneToOne = oneToOne;
+ return NotificationRunnables.NOOP;
+ }
+
+ @RemotableViewMethod(asyncImpl = "setIsOneToOneAsync")
public void setIsOneToOne(boolean oneToOne) {
mIsOneToOne = oneToOne;
}
@@ -1022,6 +1181,125 @@ public class ConversationLayout extends FrameLayout
return person == null ? null : person.getKey() == null ? person.getName() : person.getKey();
}
+ private ConversationHeaderData loadConversationHeaderData(boolean isOneToOne,
+ CharSequence conversationTitle, Icon shortcutIcon, Icon largeIcon,
+ List<MessagingMessage> messages,
+ Person user,
+ List<List<MessagingMessage>> groups, int layoutColor) {
+ Icon conversationIcon = shortcutIcon;
+ CharSequence conversationText = conversationTitle;
+ final CharSequence userKey = getKey(user);
+ if (isOneToOne) {
+ for (int i = messages.size() - 1; i >= 0; i--) {
+ final Notification.MessagingStyle.Message message = messages.get(i).getMessage();
+ final Person sender = message.getSenderPerson();
+ final CharSequence senderKey = getKey(sender);
+ if ((sender != null && senderKey != userKey) || i == 0) {
+ if (conversationText == null || conversationText.length() == 0) {
+ conversationText = sender != null ? sender.getName() : "";
+ }
+ if (conversationIcon == null) {
+ conversationIcon = sender != null ? sender.getIcon()
+ : mPeopleHelper.createAvatarSymbol(conversationText, "",
+ layoutColor);
+ }
+ break;
+ }
+ }
+ }
+
+ if (conversationIcon == null) {
+ conversationIcon = largeIcon;
+ }
+
+ if (isOneToOne || conversationIcon != null) {
+ return new ConversationHeaderData(
+ conversationText,
+ new OneToOneConversationAvatarData(
+ resolveAvatarImage(conversationIcon)));
+ }
+
+ final List<List<Notification.MessagingStyle.Message>> groupMessages = new ArrayList<>();
+ for (int i = 0; i < groups.size(); i++) {
+ final List<Notification.MessagingStyle.Message> groupMessage = new ArrayList<>();
+ for (int j = 0; j < groups.get(i).size(); j++) {
+ groupMessage.add(groups.get(i).get(j).getMessage());
+ }
+ groupMessages.add(groupMessage);
+ }
+
+ final PeopleHelper.NameToPrefixMap nameToPrefixMap =
+ mPeopleHelper.mapUniqueNamesToPrefixWithGroupList(groupMessages);
+
+ Icon lastIcon = null;
+ Icon secondLastIcon = null;
+
+ CharSequence lastKey = null;
+
+ for (int i = groups.size() - 1; i >= 0; i--) {
+ final Notification.MessagingStyle.Message message = groups.get(i).get(0).getMessage();
+ final Person sender =
+ message.getSenderPerson() != null ? message.getSenderPerson() : user;
+ final CharSequence senderKey = getKey(sender);
+ final boolean notUser = senderKey != userKey;
+ final boolean notIncluded = senderKey != lastKey;
+
+ if ((notUser && notIncluded) || (i == 0 && lastKey == null)) {
+ if (lastIcon == null) {
+ if (sender.getIcon() != null) {
+ lastIcon = sender.getIcon();
+ } else {
+ final CharSequence senderName =
+ sender.getName() != null ? sender.getName() : "";
+ lastIcon = mPeopleHelper.createAvatarSymbol(
+ senderName, nameToPrefixMap.getPrefix(senderName),
+ layoutColor);
+ }
+ lastKey = senderKey;
+ } else {
+ if (sender.getIcon() != null) {
+ secondLastIcon = sender.getIcon();
+ } else {
+ final CharSequence senderName =
+ sender.getName() != null ? sender.getName() : "";
+ secondLastIcon = mPeopleHelper.createAvatarSymbol(
+ senderName, nameToPrefixMap.getPrefix(senderName),
+ layoutColor);
+ }
+ break;
+ }
+ }
+ }
+
+ if (lastIcon == null) {
+ lastIcon = mPeopleHelper.createAvatarSymbol(
+ "", "", layoutColor);
+ }
+
+ if (secondLastIcon == null) {
+ secondLastIcon = mPeopleHelper.createAvatarSymbol(
+ "", "", layoutColor);
+ }
+
+ return new ConversationHeaderData(
+ conversationText,
+ new GroupConversationAvatarData(resolveAvatarImage(lastIcon),
+ resolveAvatarImage(secondLastIcon)));
+ }
+
+ /**
+ * {@link ImageResolver#loadImage(Uri)} accepts Uri to load images. However Conversation Avatars
+ * are received as Icon. So, we can't make use of ImageResolver.
+ */
+ @Nullable
+ private Drawable resolveAvatarImage(Icon conversationIcon) {
+ try {
+ return LocalImageResolver.resolveImage(conversationIcon, getContext());
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
/**
* Creates new messages, reusing existing ones if they are available.
*
diff --git a/core/java/com/android/internal/widget/MessagingData.java b/core/java/com/android/internal/widget/MessagingData.java
index 42de60e08e18..fb1f28fb8ef3 100644
--- a/core/java/com/android/internal/widget/MessagingData.java
+++ b/core/java/com/android/internal/widget/MessagingData.java
@@ -16,6 +16,7 @@
package com.android.internal.widget;
+import android.annotation.Nullable;
import android.app.Person;
import java.util.List;
@@ -32,6 +33,8 @@ final class MessagingData {
private final List<Person> mSenders;
private final int mUnreadCount;
+ private ConversationHeaderData mConversationHeaderData;
+
MessagingData(Person user, boolean showSpinner,
List<MessagingMessage> historicMessagingMessages,
List<MessagingMessage> newMessagingMessages, List<List<MessagingMessage>> groups,
@@ -39,7 +42,7 @@ final class MessagingData {
this(user, showSpinner, /* unreadCount= */0,
historicMessagingMessages, newMessagingMessages,
groups,
- senders);
+ senders, null);
}
MessagingData(Person user, boolean showSpinner,
@@ -47,7 +50,8 @@ final class MessagingData {
List<MessagingMessage> historicMessagingMessages,
List<MessagingMessage> newMessagingMessages,
List<List<MessagingMessage>> groups,
- List<Person> senders) {
+ List<Person> senders,
+ @Nullable ConversationHeaderData conversationHeaderData) {
mUser = user;
mShowSpinner = showSpinner;
mUnreadCount = unreadCount;
@@ -55,6 +59,7 @@ final class MessagingData {
mNewMessagingMessages = newMessagingMessages;
mGroups = groups;
mSenders = senders;
+ mConversationHeaderData = conversationHeaderData;
}
public Person getUser() {
@@ -84,4 +89,9 @@ final class MessagingData {
public List<List<MessagingMessage>> getGroups() {
return mGroups;
}
+
+ @Nullable
+ public ConversationHeaderData getConversationHeaderData() {
+ return mConversationHeaderData;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt b/core/java/com/android/internal/widget/NotificationRunnables.java
index b7285da49bb7..cb7ae617fbea 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUiModule.kt
+++ b/core/java/com/android/internal/widget/NotificationRunnables.java
@@ -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.
@@ -13,19 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.globalactions
-import android.content.Context
-import com.android.systemui.statusbar.BlurUtils
-import dagger.Module
-import dagger.Provides
+package com.android.internal.widget;
-/** Provides the UI shown during system shutdown. */
-@Module
-class ShutdownUiModule {
- /** Shutdown UI provider. */
- @Provides
- fun provideShutdownUi(context: Context?, blurUtils: BlurUtils?): ShutdownUi {
- return ShutdownUi(context, blurUtils)
- }
+public final class NotificationRunnables {
+ public static final Runnable NOOP = () -> {
+ };
}
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 01e9f437a79f..06d5eb305ff0 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -362,6 +362,22 @@ static void init_keyboard(JNIEnv* env, const vector<string>& keyboardPaths) {
using namespace android;
+// Called right before aborting by LOG_ALWAYS_FATAL. Print the pending exception.
+void abort_handler(const char* abort_message) {
+ ALOGE("Abort to abort the process...");
+
+ JNIEnv* env = NULL;
+ if (javaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ ALOGE("vm->GetEnv() failed");
+ return;
+ }
+ if (env->ExceptionOccurred() != NULL) {
+ ALOGE("Pending exception:");
+ env->ExceptionDescribe();
+ }
+ ALOGE("Aborting because: %s", abort_message);
+}
+
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
javaVM = vm;
JNIEnv* env = nullptr;
@@ -369,6 +385,8 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
return JNI_ERR;
}
+ __android_log_set_aborter(abort_handler);
+
init_android_graphics();
// Configuration is stored as java System properties.
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 070d07c22fbc..52237989f059 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -18,6 +18,8 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "AudioSystem-JNI"
+#include <android/binder_ibinder_jni.h>
+#include <android/binder_libbinder.h>
#include <android/media/AudioVibratorInfo.h>
#include <android/media/INativeSpatializerCallback.h>
#include <android/media/ISpatializer.h>
@@ -25,6 +27,7 @@
#include <android_media_audiopolicy.h>
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
+#include <binder/IBinder.h>
#include <jni.h>
#include <media/AidlConversion.h>
#include <media/AudioContainers.h>
@@ -150,13 +153,14 @@ static struct {
static jclass gAudioMixClass;
static jmethodID gAudioMixCstor;
static struct {
- jfieldID mRule;
- jfieldID mFormat;
- jfieldID mRouteFlags;
- jfieldID mDeviceType;
- jfieldID mDeviceAddress;
- jfieldID mMixType;
- jfieldID mCallbackFlags;
+ jfieldID mRule;
+ jfieldID mFormat;
+ jfieldID mRouteFlags;
+ jfieldID mDeviceType;
+ jfieldID mDeviceAddress;
+ jfieldID mMixType;
+ jfieldID mCallbackFlags;
+ jfieldID mToken;
} gAudioMixFields;
static jclass gAudioFormatClass;
@@ -2300,11 +2304,15 @@ static jint convertAudioMixFromNative(JNIEnv *env, jobject *jAudioMix, const Aud
if (status != AUDIO_JAVA_SUCCESS) {
return status;
}
+ std::unique_ptr<AIBinder, decltype(&AIBinder_decStrong)> aiBinder(AIBinder_fromPlatformBinder(
+ nAudioMix.mToken),
+ &AIBinder_decStrong);
+ jobject jBinderToken = AIBinder_toJavaBinder(env, aiBinder.get());
jstring deviceAddress = env->NewStringUTF(nAudioMix.mDeviceAddress.c_str());
*jAudioMix = env->NewObject(gAudioMixClass, gAudioMixCstor, jAudioMixingRule, jAudioFormat,
nAudioMix.mRouteFlags, nAudioMix.mCbFlags, nAudioMix.mDeviceType,
- deviceAddress);
+ deviceAddress, jBinderToken);
return AUDIO_JAVA_SUCCESS;
}
@@ -2333,6 +2341,12 @@ static jint convertAudioMixToNative(JNIEnv *env, AudioMix *nAudioMix, const jobj
nAudioMix->mVoiceCommunicationCaptureAllowed =
env->GetBooleanField(jRule, gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed);
+ jobject jToken = env->GetObjectField(jAudioMix, gAudioMixFields.mToken);
+
+ std::unique_ptr<AIBinder, decltype(&AIBinder_decStrong)>
+ aiBinder(AIBinder_fromJavaBinder(env, jToken), &AIBinder_decStrong);
+ nAudioMix->mToken = AIBinder_toPlatformBinder(aiBinder.get());
+
jint status = convertAudioMixingRuleToNative(env, jRule, &(nAudioMix->mCriteria));
env->DeleteLocalRef(jRule);
@@ -3659,9 +3673,10 @@ int register_android_media_AudioSystem(JNIEnv *env)
jclass audioMixClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMix");
gAudioMixClass = MakeGlobalRefOrDie(env, audioMixClass);
if (audio_flags::audio_mix_test_api()) {
- gAudioMixCstor = GetMethodIDOrDie(env, audioMixClass, "<init>",
- "(Landroid/media/audiopolicy/AudioMixingRule;Landroid/"
- "media/AudioFormat;IIILjava/lang/String;)V");
+ gAudioMixCstor =
+ GetMethodIDOrDie(env, audioMixClass, "<init>",
+ "(Landroid/media/audiopolicy/AudioMixingRule;Landroid/"
+ "media/AudioFormat;IIILjava/lang/String;Landroid/os/IBinder;)V");
}
gAudioMixFields.mRule = GetFieldIDOrDie(env, audioMixClass, "mRule",
"Landroid/media/audiopolicy/AudioMixingRule;");
@@ -3673,6 +3688,7 @@ int register_android_media_AudioSystem(JNIEnv *env)
"Ljava/lang/String;");
gAudioMixFields.mMixType = GetFieldIDOrDie(env, audioMixClass, "mMixType", "I");
gAudioMixFields.mCallbackFlags = GetFieldIDOrDie(env, audioMixClass, "mCallbackFlags", "I");
+ gAudioMixFields.mToken = GetFieldIDOrDie(env, audioMixClass, "mToken", "Landroid/os/IBinder;");
jclass audioFormatClass = FindClassOrDie(env, "android/media/AudioFormat");
gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass);
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 285dee364bb0..b39d5cf3842b 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -85,9 +85,24 @@ static void android_view_MotionEvent_setNativePtr(JNIEnv* env, ScopedLocalRef<jo
ScopedLocalRef<jobject> android_view_MotionEvent_obtainAsCopy(JNIEnv* env,
const MotionEvent& event) {
- std::unique_ptr<MotionEvent> destEvent = std::make_unique<MotionEvent>();
+ ScopedLocalRef<jobject> eventObj(env,
+ env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,
+ gMotionEventClassInfo.obtain));
+ if (env->ExceptionCheck() || !eventObj.get()) {
+ ALOGE("An exception occurred while obtaining a motion event.");
+ LOGE_EX(env);
+ env->ExceptionClear();
+ return ScopedLocalRef<jobject>(env);
+ }
+
+ MotionEvent* destEvent = android_view_MotionEvent_getNativePtr(env, eventObj.get());
+ if (!destEvent) {
+ destEvent = new MotionEvent();
+ android_view_MotionEvent_setNativePtr(env, eventObj, destEvent);
+ }
+
destEvent->copyFrom(&event, true);
- return android_view_MotionEvent_obtainFromNative(env, std::move(destEvent));
+ return eventObj;
}
ScopedLocalRef<jobject> android_view_MotionEvent_obtainFromNative(
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 0938ce17a4c0..3ed9f49ede26 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1965,7 +1965,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
// If this zygote isn't root, it won't be able to create a process group,
// since the directory is owned by root.
- if (!is_system_server && getuid() == 0) {
+ if (getuid() == 0) {
const int rc = createProcessGroup(uid, getpid());
if (rc != 0) {
fail_fn(rc == -EROFS ? CREATE_ERROR("createProcessGroup failed, kernel missing "
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 277824cbb4c8..e2e419fcf5b7 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -159,7 +159,7 @@ android_app {
"android.content.pm.flags-aconfig",
"android.provider.flags-aconfig",
"camera_platform_flags",
- "com.android.net.flags-aconfig",
+ "android.net.platform.flags-aconfig",
"com.android.window.flags.window-aconfig",
],
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8720f947bb8d..487b5be9ef5c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2281,7 +2281,7 @@
<!-- @SystemApi @hide Allows changing Thread network state and access to Thread network
credentials such as Network Key and PSKc.
<p>Not for use by third-party applications.
- @FlaggedApi("com.android.net.thread.flags.thread_enabled_platform") -->
+ @FlaggedApi("com.android.net.thread.platform.flags.thread_enabled_platform") -->
<permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED"
android:protectionLevel="signature|privileged" />
@@ -2331,12 +2331,12 @@
<!-- Allows system apps to call methods to register itself as a mDNS offload engine.
<p>Not for use by third-party or privileged applications.
@SystemApi
- @FlaggedApi("com.android.net.flags.register_nsd_offload_engine")
+ @FlaggedApi("android.net.platform.flags.register_nsd_offload_engine")
@hide This should only be used by system apps.
-->
<permission android:name="android.permission.REGISTER_NSD_OFFLOAD_ENGINE"
android:protectionLevel="signature"
- android:featureFlag="com.android.net.flags.register_nsd_offload_engine" />
+ android:featureFlag="android.net.platform.flags.register_nsd_offload_engine" />
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
@@ -3629,7 +3629,7 @@
<!-- Allows an application to set policy related to <a
href="https://www.threadgroup.org">Thread</a> network.
- @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
+ @FlaggedApi("com.android.net.thread.platform.flags.thread_user_restriction_enabled")
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK"
android:protectionLevel="internal|role" />
@@ -7986,7 +7986,7 @@
@hide
-->
<permission android:name="android.permission.GET_APP_METADATA"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer|verifier" />
<!-- @hide @SystemApi Allows an application to stage HealthConnect's remote data so that
HealthConnect can later integrate it. -->
diff --git a/core/res/res/drawable/pointer_wait_vector.xml b/core/res/res/drawable/pointer_wait_vector.xml
new file mode 100644
index 000000000000..4de0690bc26b
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
+ <item android:drawable="@drawable/pointer_wait_vector_0" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_1" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_2" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_3" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_4" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_5" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_6" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_7" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_8" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_9" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_10" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_11" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_12" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_13" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_14" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_15" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_16" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_17" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_18" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_19" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_20" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_21" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_22" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_23" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_24" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_25" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_26" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_27" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_28" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_29" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_30" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_31" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_32" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_33" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_34" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_35" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_36" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_37" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_38" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_39" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_40" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_41" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_42" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_43" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_44" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_45" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_46" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_47" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_48" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_49" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_50" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_51" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_52" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_53" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_54" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_55" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_56" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_57" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_58" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_59" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_60" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_61" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_62" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_63" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_64" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_65" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_66" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_67" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_68" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_69" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_70" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_71" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_72" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_73" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_74" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_75" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_76" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_77" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_78" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_79" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_80" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_81" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_82" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_83" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_84" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_85" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_86" android:duration="16"/>
+ <item android:drawable="@drawable/pointer_wait_vector_87" android:duration="16"/>
+</animation-list>
diff --git a/core/res/res/drawable/pointer_wait_vector_0.xml b/core/res/res/drawable/pointer_wait_vector_0.xml
new file mode 100644
index 000000000000..4bfbc9f08b1e
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_0.xml
@@ -0,0 +1,27 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.116,18.654a1.78,1.78 0,1 0,0.061 3.561,1.78 1.78,0 0,0 -0.061,-3.561m-7.194,-1.73a1.781,1.781 0,1 0,2.594 2.441,1.781 1.781,0 0,0 -2.594,-2.441"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.294,7.294a1.78,1.78 0,1 0,-2.517 -2.519,1.78 1.78,0 0,0 2.517,2.519"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.225,7.294a1.78,1.78 0,1 0,-2.517 -2.519,1.78 1.78,0 0,0 2.517,2.519"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.436,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.747,16.665a1.78,1.78 0,1 0,2.539 2.497,1.78 1.78,0 0,0 -2.539,-2.497"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_1.xml b/core/res/res/drawable/pointer_wait_vector_1.xml
new file mode 100644
index 000000000000..bab91e1d9402
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_1.xml
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.174,18.653a1.782,1.782 0,1 0,0.094 3.562,1.782 1.782,0 0,0 -0.094,-3.562m-7.187,-1.637a1.782,1.782 0,1 0,2.627 2.407,1.782 1.782,0 0,0 -2.627,-2.407"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.294,7.294a1.78,1.78 0,1 0,-2.517 -2.519,1.78 1.78,0 0,0 2.517,2.519"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.225,7.294a1.78,1.78 0,1 0,-2.517 -2.519,1.78 1.78,0 0,0 2.517,2.519m1.203,2.888a1.78,1.78 0,1 0,0.016 3.562,1.78 1.78,0 1,0 -0.016,-3.562m-3.641,6.441a1.782,1.782 0,1 0,2.564 2.475,1.782 1.782,0 0,0 -2.564,-2.475"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_10.xml b/core/res/res/drawable/pointer_wait_vector_10.xml
new file mode 100644
index 000000000000..ae3e36093aaa
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_10.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13.028,20.374m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.952,18.76m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12.066m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.957,6.113m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.779,3.566m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.7,5.78m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.42,11.485m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.462,17.423m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_11.xml b/core/res/res/drawable/pointer_wait_vector_11.xml
new file mode 100644
index 000000000000..d2c014d1ee3d
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_11.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13.101,20.364m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.101,18.868m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12.074m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.931,6.139m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.706,3.569m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.646,5.731m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.41,11.338m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.509,17.366m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_12.xml b/core/res/res/drawable/pointer_wait_vector_12.xml
new file mode 100644
index 000000000000..e9a36c033ca6
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_12.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13.247,20.344m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.222,18.953m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.564,12.147m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.88,6.193m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.632,3.571m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.591,5.681m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.404,11.265m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.602,17.252m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_13.xml b/core/res/res/drawable/pointer_wait_vector_13.xml
new file mode 100644
index 000000000000..5800f39fa7ef
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_13.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13.392,20.321m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.344,19.035m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.565,12.184m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.83,6.246m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.559,3.575m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.535,5.633m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.39,11.118m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.693,17.136m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_14.xml b/core/res/res/drawable/pointer_wait_vector_14.xml
new file mode 100644
index 000000000000..3566aaf358e7
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_14.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13.537,20.295m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.467,19.115m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.567,12.258m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.755,6.327m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.485,3.579m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.423,5.537m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.386,11.082m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.759,17.048m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_15.xml b/core/res/res/drawable/pointer_wait_vector_15.xml
new file mode 100644
index 000000000000..c995faf24f33
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_15.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13.682,20.267m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.718,19.269m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.568,12.295m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.73,6.355m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.412,3.584m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.367,5.49m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.369,10.935m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.847,16.929m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_16.xml b/core/res/res/drawable/pointer_wait_vector_16.xml
new file mode 100644
index 000000000000..179328205776
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_16.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13.826,20.237m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.846,19.343m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.57,12.331m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.706,6.382m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.338,3.589m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.31,5.444m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.354,10.826m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.91,16.839m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_17.xml b/core/res/res/drawable/pointer_wait_vector_17.xml
new file mode 100644
index 000000000000..59772f566644
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_17.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13.969,20.204m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.974,19.414m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.571,12.368m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.681,6.41m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.191,3.602m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.194,5.352m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.333,10.68m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.994,16.718m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_18.xml b/core/res/res/drawable/pointer_wait_vector_18.xml
new file mode 100644
index 000000000000..1bbb242d5663
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_18.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M14.184,20.149m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.236,19.55m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.575,12.442m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.585,6.521m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.118,3.61m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.136,5.307m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.315,10.571m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.075,16.595m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_19.xml b/core/res/res/drawable/pointer_wait_vector_19.xml
new file mode 100644
index 000000000000..b8e0dd54708f
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_19.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M14.325,20.11m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.434,19.646m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.581,12.552m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.537,6.577m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.972,3.626m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.959,5.175m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.282,10.39m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.193,16.408m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_2.xml b/core/res/res/drawable/pointer_wait_vector_2.xml
new file mode 100644
index 000000000000..5c4606f36569
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_2.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.331,20.43m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.355,18.269m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.035m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.435,11.89m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.094,17.834m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_20.xml b/core/res/res/drawable/pointer_wait_vector_20.xml
new file mode 100644
index 000000000000..b3ac2dab2537
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_20.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M14.607,20.024m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.703,19.766m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.592,12.699m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.49,6.634m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.899,3.636m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.899,5.132m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.252,10.246m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.269,16.282m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_21.xml b/core/res/res/drawable/pointer_wait_vector_21.xml
new file mode 100644
index 000000000000..884476e929a6
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_21.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M14.816,19.953m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.908,19.849m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.589,12.662m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.443,6.691m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.753,3.656m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.718,5.006m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.22,10.102m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.414,16.025m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_22.xml b/core/res/res/drawable/pointer_wait_vector_22.xml
new file mode 100644
index 000000000000..20660ca89f3a
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_22.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M15.092,19.849m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.184,19.952m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.617,12.955m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.352,6.806m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.608,3.679m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.595,4.925m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.149,9.816m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.483,15.896m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_23.xml b/core/res/res/drawable/pointer_wait_vector_23.xml
new file mode 100644
index 000000000000..6b594ee78eae
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_23.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M15.296,19.766m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.533,20.068m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.609,12.882m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.262,6.923m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.463,3.705m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.471,4.845m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.11,9.675m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.614,15.632m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_24.xml b/core/res/res/drawable/pointer_wait_vector_24.xml
new file mode 100644
index 000000000000..a35203e3857b
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_24.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M15.632,19.615m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.816,20.149m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.626,13.028m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.175,7.041m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.318,3.733m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.346,4.769m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.046,9.463m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.677,15.498m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_25.xml b/core/res/res/drawable/pointer_wait_vector_25.xml
new file mode 100644
index 000000000000..df8549af4006
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_25.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M15.896,19.483m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.174,20.236m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.645,13.174m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.089,7.161m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.174,3.763m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.219,4.694m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.977,9.253m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.794,15.228m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_26.xml b/core/res/res/drawable/pointer_wait_vector_26.xml
new file mode 100644
index 000000000000..0abb7d41a59a
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_26.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M16.154,19.343m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.535,20.308m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.667,13.32m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.006,7.282m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.959,3.814m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M15.961,4.551m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.902,9.045m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.902,14.955m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_27.xml b/core/res/res/drawable/pointer_wait_vector_27.xml
new file mode 100644
index 000000000000..bde242548af5
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_27.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M16.471,19.155m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.972,20.374m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.691,13.465m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.885,7.467m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.817,3.851m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M15.831,4.483m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.85,8.908m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.023,14.607m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_28.xml b/core/res/res/drawable/pointer_wait_vector_28.xml
new file mode 100644
index 000000000000..19cb410b1405
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_28.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M16.778,18.953m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.411,20.416m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.718,13.61m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.807,7.592m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.604,3.911m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M15.566,4.354m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.677,8.502m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.109,14.325m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_29.xml b/core/res/res/drawable/pointer_wait_vector_29.xml
new file mode 100644
index 000000000000..5d6776fcdb21
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_29.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M17.136,18.693m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.926,20.436m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.748,13.754m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.731,7.718m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.463,3.954m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M15.365,4.263m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.55,8.236m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.203,13.969m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_3.xml b/core/res/res/drawable/pointer_wait_vector_3.xml
new file mode 100644
index 000000000000..8a19401bd401
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_3.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.368,20.429m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.437,18.343m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.94,6.009m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.435,11.853m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.119,17.807m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_30.xml b/core/res/res/drawable/pointer_wait_vector_30.xml
new file mode 100644
index 000000000000..56dcdc228401
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_30.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M17.479,18.415m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12.441,20.425m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.796,13.969m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.586,7.974m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.184,4.047m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M15.092,4.151m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.414,7.975m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.281,13.61m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_31.xml b/core/res/res/drawable/pointer_wait_vector_31.xml
new file mode 100644
index 000000000000..5687a9b46b67
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_31.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M17.807,18.12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M13.028,20.373m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.832,14.112m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.517,8.104m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.908,4.15m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M14.817,4.047m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.269,7.718m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.344,13.247m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_32.xml b/core/res/res/drawable/pointer_wait_vector_32.xml
new file mode 100644
index 000000000000..cb626e41d1d5
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_32.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M18.17,17.754m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M13.61,20.281m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.87,14.254m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.417,8.302m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.704,4.234m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M14.537,3.954m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.076,7.405m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.397,12.809m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_33.xml b/core/res/res/drawable/pointer_wait_vector_33.xml
new file mode 100644
index 000000000000..052f973f2ab4
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_33.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M18.51,17.366m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M14.183,20.149m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.954,14.537m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.323,8.501m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.435,4.354m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M14.184,3.851m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.868,7.101m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.428,12.368m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_34.xml b/core/res/res/drawable/pointer_wait_vector_34.xml
new file mode 100644
index 000000000000..9e665756f4c2
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_34.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M18.911,16.839m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M14.885,19.928m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.999,14.677m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.206,8.771m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.17,4.483m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M13.826,3.764m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.603,6.748m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.433,11.779m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_35.xml b/core/res/res/drawable/pointer_wait_vector_35.xml
new file mode 100644
index 000000000000..1d063fc9fbb9
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_35.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M19.232,16.345m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M15.566,19.646m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.098,14.955m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.098,9.045m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.846,4.657m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M13.465,3.692m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.27,6.355m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.41,11.338m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_36.xml b/core/res/res/drawable/pointer_wait_vector_36.xml
new file mode 100644
index 000000000000..db0ad1c847bc
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_36.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M19.583,15.698m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.345,19.232m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.205,15.229m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.999,9.323m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.592,4.807m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M13.028,3.626m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.035m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.344,10.753m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_37.xml b/core/res/res/drawable/pointer_wait_vector_37.xml
new file mode 100644
index 000000000000..ee944a6ccbe6
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_37.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M19.876,15.023m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.077,18.738m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.323,15.499m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.911,9.604m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.221,5.047m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12.589,3.584m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.59,5.681m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.236,10.174m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_38.xml b/core/res/res/drawable/pointer_wait_vector_38.xml
new file mode 100644
index 000000000000..a347a36e34b0
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_38.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M20.129,14.255m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.861,18.069m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.45,15.764m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.796,10.031m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.864,5.307m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12.147,3.565m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.136,5.307m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.046,9.463m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_39.xml b/core/res/res/drawable/pointer_wait_vector_39.xml
new file mode 100644
index 000000000000..2c86f0dd7849
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_39.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M20.321,13.392m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.557,17.309m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.586,16.026m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.705,10.462m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.521,5.585m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.559,3.575m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.657,4.965m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.766,8.704m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_4.xml b/core/res/res/drawable/pointer_wait_vector_4.xml
new file mode 100644
index 000000000000..a6690c003c4b
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_4.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.441,20.425m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.493,18.391m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.914,5.983m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.433,11.772m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.17,17.754m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_40.xml b/core/res/res/drawable/pointer_wait_vector_40.xml
new file mode 100644
index 000000000000..953fdc1deb23
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_40.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M20.428,12.368m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.232,16.345m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.731,16.282m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.656,10.753m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.193,5.88m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.972,3.626m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.026,4.586m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.449,8.039m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_41.xml b/core/res/res/drawable/pointer_wait_vector_41.xml
new file mode 100644
index 000000000000..a1b17fa2d19d
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_41.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M20.41,11.338m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.766,15.296m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.924,16.595m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.602,11.191m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.83,6.246m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.39,3.719m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M15.364,4.263m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.994,7.282m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_42.xml b/core/res/res/drawable/pointer_wait_vector_42.xml
new file mode 100644
index 000000000000..4de9b8d14dd8
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_42.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M20.244,10.21m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.14,14.219m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.153,16.929m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.571,11.632m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.49,6.634m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.817,3.851m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M14.677,4m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.367,6.465m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_43.xml b/core/res/res/drawable/pointer_wait_vector_43.xml
new file mode 100644
index 000000000000..38187fe62ac4
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_43.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M19.94,9.149m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.365,13.101m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.397,17.252m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.566,12.221m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.089,7.161m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.115,4.072m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M13.826,3.763m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.645,5.73m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_44.xml b/core/res/res/drawable/pointer_wait_vector_44.xml
new file mode 100644
index 000000000000..890ba7ee6533
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_44.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M19.466,8.072m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.436,12.073m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.73,17.645m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.589,12.662m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.768,7.655m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.368,4.385m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12.882,3.61m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.839,5.089m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_45.xml b/core/res/res/drawable/pointer_wait_vector_45.xml
new file mode 100644
index 000000000000..309ac082edef
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_45.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M18.847,7.071m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.382,11.045m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.139,18.069m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.656,13.247m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.417,8.302m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.592,4.807m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.853,3.565m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M15.83,4.483m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_46.xml b/core/res/res/drawable/pointer_wait_vector_46.xml
new file mode 100644
index 000000000000..5a9bf706064a
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_46.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M18.195,6.273m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.237,10.174m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.465,18.367m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.797,13.969m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.124,8.977m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.864,5.307m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.826,3.646m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M14.747,4.023m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_47.xml b/core/res/res/drawable/pointer_wait_vector_47.xml
new file mode 100644
index 000000000000..0068f27c78d5
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_47.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M17.451,5.561m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.046,9.463m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.922,18.738m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.954,14.537m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.851,9.817m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.087,5.983m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.675,3.89m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M13.61,3.718m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_48.xml b/core/res/res/drawable/pointer_wait_vector_48.xml
new file mode 100644
index 000000000000..6ece990ad516
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_48.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M16.748,5.026m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.822,8.84m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.405,19.076m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.205,15.229m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.667,10.68m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.397,6.748m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.569,4.293m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12.588,3.584m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_49.xml b/core/res/res/drawable/pointer_wait_vector_49.xml
new file mode 100644
index 000000000000..d44438975224
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_49.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M16.09,4.621m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.583,8.302m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.974,19.414m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.586,16.025m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.571,11.632m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.768,7.655m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.655,4.768m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.558,3.575m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_5.xml b/core/res/res/drawable/pointer_wait_vector_5.xml
new file mode 100644
index 000000000000..4952fe57397b
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_5.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.515,20.421m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.577,18.463m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.877,5.947m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.433,11.779m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.22,17.7m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_50.xml b/core/res/res/drawable/pointer_wait_vector_50.xml
new file mode 100644
index 000000000000..a85155fc5dd2
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_50.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M15.499,4.323m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.343,7.846m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.636,19.737m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.047,16.778m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.589,12.662m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.234,8.704m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.634,5.49m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.608,3.679m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_51.xml b/core/res/res/drawable/pointer_wait_vector_51.xml
new file mode 100644
index 000000000000..95b4aaa2365a
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_51.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M14.885,4.072m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.155,7.529m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.393,20.024m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.585,17.479m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.748,13.754m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.851,9.817m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.88,6.193m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.816,3.851m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_52.xml b/core/res/res/drawable/pointer_wait_vector_52.xml
new file mode 100644
index 000000000000..34619b5d4361
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_52.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M14.396,3.911m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.953,7.221m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.246,20.252m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.3,18.22m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.072,14.885m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.636,10.899m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.307,6.864m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.114,4.072m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_53.xml b/core/res/res/drawable/pointer_wait_vector_53.xml
new file mode 100644
index 000000000000..926b63998470
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_53.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13.969,3.797m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.782,6.982m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.118,20.39m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.221,18.953m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.551,15.961m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.564,11.927m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.807,7.592m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.568,4.293m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_54.xml b/core/res/res/drawable/pointer_wait_vector_54.xml
new file mode 100644
index 000000000000..c51851fce837
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_54.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13.61,3.718m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.603,6.748m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12.147,20.435m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.17,19.517m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.132,16.899m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.618,12.955m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.45,8.236m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.104,4.517m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_55.xml b/core/res/res/drawable/pointer_wait_vector_55.xml
new file mode 100644
index 000000000000..cdf435fa0637
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_55.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13.247,3.656m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.463,6.577m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M13.247,20.344m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.253,19.977m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.83,17.754m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.748,13.754m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.178,8.84m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.686,4.749m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_56.xml b/core/res/res/drawable/pointer_wait_vector_56.xml
new file mode 100644
index 000000000000..1528711e2542
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_56.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13.028,3.626m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.319,6.41m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M14.325,20.11m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.39,20.282m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.521,18.415m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.954,14.537m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.976,9.393m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.405,4.924m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_57.xml b/core/res/res/drawable/pointer_wait_vector_57.xml
new file mode 100644
index 000000000000..1715db1e2bef
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_57.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.772,3.599m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.22,6.3m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M15.431,19.707m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.411,20.416m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.282,18.994m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.178,15.16m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.851,9.817m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.041,5.175m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_58.xml b/core/res/res/drawable/pointer_wait_vector_58.xml
new file mode 100644
index 000000000000..83aec4b24851
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_58.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.589,3.584m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.12,6.193m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.471,19.155m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12.442,20.425m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M7.974,19.414m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.417,15.698m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.748,10.246m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.864,5.307m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_59.xml b/core/res/res/drawable/pointer_wait_vector_59.xml
new file mode 100644
index 000000000000..2e5e4728ec85
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_59.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.442,3.575m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.069,6.139m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.309,18.556m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M13.465,20.308m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M8.568,19.707m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.657,16.154m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.679,10.608m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.634,5.49m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_6.xml b/core/res/res/drawable/pointer_wait_vector_6.xml
new file mode 100644
index 000000000000..d4f57af49b40
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_6.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.588,20.416m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.633,18.51m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.926,3.564m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.861,5.931m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.431,11.706m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.269,17.645m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_60.xml b/core/res/res/drawable/pointer_wait_vector_60.xml
new file mode 100644
index 000000000000..4f257671d25d
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_60.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.294,3.569m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.069,6.139m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.12,17.807m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M14.183,20.149m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.115,19.928m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M4.845,16.471m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.646,10.826m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.465,5.633m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_61.xml b/core/res/res/drawable/pointer_wait_vector_61.xml
new file mode 100644
index 000000000000..7e06db4d1874
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_61.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.147,3.565m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.017,6.087m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.738,17.077m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M14.885,19.928m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M9.604,20.089m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.047,16.779m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.61,11.118m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.355,5.73m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_62.xml b/core/res/res/drawable/pointer_wait_vector_62.xml
new file mode 100644
index 000000000000..b3d0541a6417
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_62.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.074,3.564m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.231,16.345m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M15.432,19.707m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.102,20.22m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.307,17.136m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.584,11.411m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.246,5.83m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_63.xml b/core/res/res/drawable/pointer_wait_vector_63.xml
new file mode 100644
index 000000000000..ee09c2537d3f
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_63.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.583,15.698m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M15.961,19.449m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.39,20.282m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.397,17.252m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.575,11.558m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.193,5.88m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_64.xml b/core/res/res/drawable/pointer_wait_vector_64.xml
new file mode 100644
index 000000000000..e87f94f33304
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_64.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M19.822,15.16m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.345,19.232m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.826,20.354m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.537,17.423m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.571,11.632m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.139,5.931m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_65.xml b/core/res/res/drawable/pointer_wait_vector_65.xml
new file mode 100644
index 000000000000..b8407c448db5
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_65.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.023,14.607m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.718,18.994m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M10.972,20.374m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.681,17.59m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.569,11.706m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.087,5.983m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_66.xml b/core/res/res/drawable/pointer_wait_vector_66.xml
new file mode 100644
index 000000000000..7e2448df4d11
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_66.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.149,14.184m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M16.959,18.825m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.265,20.405m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.78,17.7m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.565,11.853m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_67.xml b/core/res/res/drawable/pointer_wait_vector_67.xml
new file mode 100644
index 000000000000..615ac04c40b3
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_67.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.252,13.754m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.194,18.648m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.412,20.416m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.88,17.807m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_68.xml b/core/res/res/drawable/pointer_wait_vector_68.xml
new file mode 100644
index 000000000000..0ba4634a131f
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_68.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.308,13.465m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.366,18.51m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.632,20.429m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.931,17.861m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_69.xml b/core/res/res/drawable/pointer_wait_vector_69.xml
new file mode 100644
index 000000000000..efa90bf9df22
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_69.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.354,13.174m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.535,18.367m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.706,20.432m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.931,17.86m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_7.xml b/core/res/res/drawable/pointer_wait_vector_7.xml
new file mode 100644
index 000000000000..86d571a40f73
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_7.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.661,20.411m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.69,18.556m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.024,6.045m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.889,3.564m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.808,5.88m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.428,11.632m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.318,17.59m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_70.xml b/core/res/res/drawable/pointer_wait_vector_70.xml
new file mode 100644
index 000000000000..4e58af5dfb3b
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_70.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.39,12.882m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.645,18.27m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.853,20.435m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_71.xml b/core/res/res/drawable/pointer_wait_vector_71.xml
new file mode 100644
index 000000000000..5a534342ec8d
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_71.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.416,12.589m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.754,18.17m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.853,20.435m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_72.xml b/core/res/res/drawable/pointer_wait_vector_72.xml
new file mode 100644
index 000000000000..40fb5d313f7c
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_72.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.425,12.442m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.807,18.12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_73.xml b/core/res/res/drawable/pointer_wait_vector_73.xml
new file mode 100644
index 000000000000..933cc6ae4813
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_73.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.429,12.368m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.861,18.069m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_74.xml b/core/res/res/drawable/pointer_wait_vector_74.xml
new file mode 100644
index 000000000000..6c7fef264f81
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_74.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.431,12.294m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_75.xml b/core/res/res/drawable/pointer_wait_vector_75.xml
new file mode 100644
index 000000000000..24e64e3491cf
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_75.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.435,12.147m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_76.xml b/core/res/res/drawable/pointer_wait_vector_76.xml
new file mode 100644
index 000000000000..2ede57de05fd
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_76.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.436,12.073m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_77.xml b/core/res/res/drawable/pointer_wait_vector_77.xml
new file mode 100644
index 000000000000..71f811a497a0
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_77.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.436,12.037m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_78.xml b/core/res/res/drawable/pointer_wait_vector_78.xml
new file mode 100644
index 000000000000..e2d56c6f676e
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_78.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.437,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_79.xml b/core/res/res/drawable/pointer_wait_vector_79.xml
new file mode 100644
index 000000000000..e2d56c6f676e
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_79.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.437,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_8.xml b/core/res/res/drawable/pointer_wait_vector_8.xml
new file mode 100644
index 000000000000..ebac74961497
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_8.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.808,20.398m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.806,18.648m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.008,6.061m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.853,3.565m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.754,5.83m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.425,11.558m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.367,17.535m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_80.xml b/core/res/res/drawable/pointer_wait_vector_80.xml
new file mode 100644
index 000000000000..40fb5d313f7c
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_80.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.425,12.442m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.807,18.12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_81.xml b/core/res/res/drawable/pointer_wait_vector_81.xml
new file mode 100644
index 000000000000..933cc6ae4813
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_81.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.429,12.368m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.861,18.069m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_82.xml b/core/res/res/drawable/pointer_wait_vector_82.xml
new file mode 100644
index 000000000000..6c7fef264f81
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_82.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.431,12.294m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_83.xml b/core/res/res/drawable/pointer_wait_vector_83.xml
new file mode 100644
index 000000000000..24e64e3491cf
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_83.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.435,12.147m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_84.xml b/core/res/res/drawable/pointer_wait_vector_84.xml
new file mode 100644
index 000000000000..2ede57de05fd
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_84.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.436,12.073m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_85.xml b/core/res/res/drawable/pointer_wait_vector_85.xml
new file mode 100644
index 000000000000..71f811a497a0
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_85.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.436,12.037m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_86.xml b/core/res/res/drawable/pointer_wait_vector_86.xml
new file mode 100644
index 000000000000..e2d56c6f676e
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_86.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.437,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_87.xml b/core/res/res/drawable/pointer_wait_vector_87.xml
new file mode 100644
index 000000000000..e2d56c6f676e
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_87.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,3.563m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.437,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.966,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M12,20.437m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,17.966m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.034,6.034m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_9.xml b/core/res/res/drawable/pointer_wait_vector_9.xml
new file mode 100644
index 000000000000..d46dfa394c01
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_9.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12.881,20.39m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M6.864,18.693m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M3.563,12.052m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M5.983,6.087m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M11.816,3.565m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M17.754,5.83m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M20.423,11.522m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+ <path
+ android:pathData="M18.415,17.479m-1.781,0a1.781,1.781 0,1 1,3.562 0a1.781,1.781 0,1 1,-3.562 0"
+ android:fillColor="#FFF"/>
+</vector>
diff --git a/core/res/res/drawable/pointer_wait_vector_icon.xml b/core/res/res/drawable/pointer_wait_vector_icon.xml
new file mode 100644
index 000000000000..45d371e95c5d
--- /dev/null
+++ b/core/res/res/drawable/pointer_wait_vector_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ android:bitmap="@drawable/pointer_wait_vector"
+ android:hotSpotX="12dp"
+ android:hotSpotY="12dp" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index c1fd61948e68..48cf09a84e57 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4409,7 +4409,7 @@
<attr name="requireDeviceScreenOn" format="boolean"/>
<!-- Whether the device should default to observe mode when this service is
default or in the foreground. -->
- <attr name="defaultToObserveMode" format="boolean"/>
+ <attr name="shouldDefaultToObserveMode" format="boolean"/>
</declare-styleable>
<!-- Use <code>offhost-apdu-service</code> as the root tag of the XML resource that
@@ -4436,7 +4436,7 @@
<attr name="requireDeviceScreenOn"/>
<!-- Whether the device should default to observe mode when this service is
default or in the foreground. -->
- <attr name="defaultToObserveMode"/>
+ <attr name="shouldDefaultToObserveMode"/>
</declare-styleable>
<!-- Specify one or more <code>aid-group</code> elements inside a
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index b2e0be7c2201..c882938b63ce 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1618,15 +1618,13 @@
<!-- Data (photo, file, account) upload/download, backup/restore, import/export, fetch,
transfer over network between device and cloud.
- <p><b>THIS TYPE IS DEPRECATED.</b>
- <p><em>Note: For apps with <code>targetSdkVersion</code>
- {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, this type should
- <b>NOT</b> be used: calling
- {@link android.app.Service#startForeground(int, android.app.Notification, int)}
- with this type on devices running
- {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} is still allowed, but it may
- throw an {@link android.app.InvalidForegroundServiceTypeException} in future platform
- releases.</em>
+ <p>For apps with <code>targetSdkVersion</code>
+ {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, this type should NOT
+ be used: calling
+ {@link android.app.Service#startForeground(int, android.app.Notification, int)} with
+ this type on devices running {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}
+ is still allowed, but calling it with this type on devices running future platform
+ releases may get a {@link android.app.InvalidForegroundServiceTypeException}.
-->
<flag name="dataSync" value="0x01" />
<!-- Music, video, news or other media play.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1f06b0b7c62b..6134e788be82 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6967,4 +6967,16 @@
<!-- Whether to use file hashes cache in watchlist-->
<bool name="config_watchlistUseFileHashesCache">false</bool>
+
+ <!-- Name of the package responsible to handle Contextual Search. -->
+ <string name="config_defaultContextualSearchPackageName" translatable="false" />
+
+ <!-- The key containing the entrypoint for Contextual Search. -->
+ <string name="config_defaultContextualSearchKey" translatable="false" />
+
+ <!-- The key containing the branching boolean for Contextual Search. -->
+ <string name="config_defaultContextualSearchEnabled" translatable="false" />
+
+ <!-- The key containing the branching boolean for legacy Search. -->
+ <string name="config_defaultContextualSearchLegacyEnabled" translatable="false" />
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 5987f6ea05d4..3303c076c090 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -160,7 +160,7 @@
<!-- @FlaggedApi("android.view.inputmethod.connectionless_handwriting") -->
<public name="supportsConnectionlessStylusHandwriting" />
<!-- @FlaggedApi("android.nfc.Flags.FLAG_OBSERVE_MODE") -->
- <public name="defaultToObserveMode"/>
+ <public name="shouldDefaultToObserveMode"/>
<!-- @FlaggedApi("android.security.asm_restrictions_enabled") -->
<public name="allowCrossUidActivitySwitchFromBelow"/>
<!-- @FlaggedApi("com.android.text.flags.use_bounds_for_width") -->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index adf8d9f95b52..5945f81704c5 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1468,7 +1468,7 @@ please see styles_device_defaults.xml.
<item name="pointerIconHand">@drawable/pointer_hand_vector_icon</item>
<item name="pointerIconContextMenu">@drawable/pointer_context_menu_vector_icon</item>
<item name="pointerIconHelp">@drawable/pointer_help_vector_icon</item>
- <item name="pointerIconWait">@drawable/pointer_wait_icon</item>
+ <item name="pointerIconWait">@drawable/pointer_wait_vector_icon</item>
<item name="pointerIconCell">@drawable/pointer_cell_vector_icon</item>
<item name="pointerIconCrosshair">@drawable/pointer_crosshair_vector_icon</item>
<item name="pointerIconText">@drawable/pointer_text_vector_icon</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b36b1d63cbf2..2f5183fc1455 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5368,4 +5368,8 @@
<java-symbol type="bool" name="config_evenDimmerEnabled" />
<java-symbol type="bool" name="config_watchlistUseFileHashesCache" />
+ <java-symbol type="string" name="config_defaultContextualSearchPackageName" />
+ <java-symbol type="string" name="config_defaultContextualSearchKey" />
+ <java-symbol type="string" name="config_defaultContextualSearchEnabled" />
+ <java-symbol type="string" name="config_defaultContextualSearchLegacyEnabled" />
</resources>
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 37e6780a8109..e72beee71e50 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -28,10 +28,19 @@ filegroup {
visibility: ["//visibility:private"],
}
+java_defaults {
+ name: "FrameworksCoreTests-resources",
+ aaptflags: [
+ "-0 .dat",
+ "-0 .gld",
+ "-c fa",
+ ],
+ resource_dirs: ["res"],
+}
+
android_test {
name: "FrameworksCoreTests",
- // FrameworksCoreTestsRavenwood references the .aapt.srcjar
- use_resource_processor: false,
+ defaults: ["FrameworksCoreTests-resources"],
srcs: [
"src/**/*.java",
@@ -117,7 +126,6 @@ android_test {
certificate: "platform",
- resource_dirs: ["res"],
resource_zips: [":FrameworksCoreTests_apks_as_resources"],
java_resources: [":FrameworksCoreTests_unit_test_cert_der"],
@@ -128,6 +136,24 @@ android_test {
],
}
+// FrameworksCoreTestsRavenwood pulls in the R.java class from this one.
+// Note, "FrameworksCoreTests" and "FrameworksCoreTests-resonly" _might_ not have indentical
+// R.java (not sure if there's a guarantee), but that doesn't matter as long as
+// FrameworksCoreTestsRavenwood consistently uses the R definition in this module.
+android_app {
+ name: "FrameworksCoreTests-resonly",
+ defaults: ["FrameworksCoreTests-resources"],
+
+ // FrameworksCoreTestsRavenwood references the .aapt.srcjar
+ use_resource_processor: false,
+ libs: [
+ "framework-res",
+ "android.test.runner",
+ "org.apache.http.legacy",
+ ],
+ sdk_version: "core_platform",
+}
+
// Rules to copy all the test apks to the intermediate raw resource directory
java_genrule {
name: "FrameworksCoreTests_apks_as_resources",
@@ -225,7 +251,11 @@ android_ravenwood_test {
"src/com/android/internal/util/**/*.java",
"src/com/android/internal/power/EnergyConsumerStatsTest.java",
- ":FrameworksCoreTests{.aapt.srcjar}",
+ // Pull in R.java from FrameworksCoreTests-resonly, not from FrameworksCoreTests,
+ // to avoid having a dependency to FrameworksCoreTests.
+ // This way, when updating source files and running this test, we don't need to
+ // rebuild the entire FrameworksCoreTests, which would be slow.
+ ":FrameworksCoreTests-resonly{.aapt.srcjar}",
":FrameworksCoreTests-aidl",
":FrameworksCoreTests-helpers",
":FrameworksCoreTestDoubles-sources",
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index a7d083cbc399..64c17bdfa731 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -65,6 +65,7 @@ import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.view.Display;
import android.view.View;
+import android.window.ActivityWindowInfo;
import android.window.WindowContextInfo;
import android.window.WindowTokenClientController;
@@ -394,11 +395,13 @@ public class ActivityThreadTest {
olderConfig.seq = seq + 1;
final ActivityClientRecord r = getActivityClientRecord(activity);
- activityThread.handleActivityConfigurationChanged(r, olderConfig, INVALID_DISPLAY);
+ activityThread.handleActivityConfigurationChanged(r, olderConfig, INVALID_DISPLAY,
+ new ActivityWindowInfo());
assertEquals(numOfConfig, activity.mNumOfConfigChanges);
assertEquals(olderConfig.orientation, activity.mConfig.orientation);
- activityThread.handleActivityConfigurationChanged(r, newerConfig, INVALID_DISPLAY);
+ activityThread.handleActivityConfigurationChanged(r, newerConfig, INVALID_DISPLAY,
+ new ActivityWindowInfo());
assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
assertEquals(newerConfig.orientation, activity.mConfig.orientation);
});
@@ -416,7 +419,7 @@ public class ActivityThreadTest {
config.orientation = ORIENTATION_PORTRAIT;
activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
- config, INVALID_DISPLAY);
+ config, INVALID_DISPLAY, new ActivityWindowInfo());
});
final IApplicationThread appThread = activityThread.getApplicationThread();
@@ -487,7 +490,7 @@ public class ActivityThreadTest {
config.orientation = ORIENTATION_PORTRAIT;
activityThread.handleActivityConfigurationChanged(getActivityClientRecord(activity),
- config, INVALID_DISPLAY);
+ config, INVALID_DISPLAY, new ActivityWindowInfo());
});
final int numOfConfig = activity.mNumOfConfigChanges;
@@ -617,7 +620,7 @@ public class ActivityThreadTest {
activityThread.updatePendingActivityConfiguration(activity.getActivityToken(),
newActivityConfig);
activityThread.handleActivityConfigurationChanged(r, newActivityConfig,
- INVALID_DISPLAY);
+ INVALID_DISPLAY, new ActivityWindowInfo());
assertEquals("Virtual display orientation must not change when activity"
+ " configuration orientation changes.",
@@ -782,8 +785,8 @@ public class ActivityThreadTest {
/**
* Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
- * Configuration, int)} to try to push activity configuration to the activity for the given
- * sequence number.
+ * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the
+ * activity for the given sequence number.
* <p>
* It uses orientation to push the configuration and it tries a different orientation if the
* first attempt doesn't make through, to rule out the possibility that the previous
@@ -802,7 +805,8 @@ public class ActivityThreadTest {
Configuration config = new Configuration();
config.orientation = ORIENTATION_PORTRAIT;
config.seq = seq;
- activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
+ activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY,
+ new ActivityWindowInfo());
if (activity.mNumOfConfigChanges > numOfConfig) {
return config.seq;
@@ -811,7 +815,8 @@ public class ActivityThreadTest {
config = new Configuration();
config.orientation = ORIENTATION_LANDSCAPE;
config.seq = seq + 1;
- activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY);
+ activityThread.handleActivityConfigurationChanged(r, config, INVALID_DISPLAY,
+ new ActivityWindowInfo());
return config.seq;
}
@@ -839,7 +844,7 @@ public class ActivityThreadTest {
final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(
activity.getActivityToken(), null, null, 0,
new MergedConfiguration(currentConfig, currentConfig),
- false /* preserveWindow */);
+ false /* preserveWindow */, new ActivityWindowInfo());
final ResumeActivityItem resumeStateRequest =
ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */,
false /* shouldSendCompatFakeFocus*/);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index b5e8203c51ed..85a1b4ee3ebd 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -40,6 +40,7 @@ import android.util.ArrayMap;
import android.util.MergedConfiguration;
import android.view.IWindow;
import android.view.InsetsState;
+import android.window.ActivityWindowInfo;
import android.window.ClientWindowFrames;
import android.window.WindowContext;
import android.window.WindowContextInfo;
@@ -116,7 +117,8 @@ public class ClientTransactionItemTest {
public void testActivityRelaunchItem_getContextToUpdate() {
final ActivityRelaunchItem item = ActivityRelaunchItem
.obtain(mActivityToken, null /* pendingResults */, null /* pendingNewIntents */,
- 0 /* configChange */, mMergedConfiguration, false /* preserveWindow */);
+ 0 /* configChange */, mMergedConfiguration, false /* preserveWindow */,
+ new ActivityWindowInfo());
final Context context = item.getContextToUpdate(mHandler);
assertEquals(mActivity, context);
@@ -175,7 +177,7 @@ public class ClientTransactionItemTest {
@Test
public void testMoveToDisplayItem_getContextToUpdate() {
final MoveToDisplayItem item = MoveToDisplayItem
- .obtain(mActivityToken, DEFAULT_DISPLAY, mConfiguration);
+ .obtain(mActivityToken, DEFAULT_DISPLAY, mConfiguration, new ActivityWindowInfo());
final Context context = item.getContextToUpdate(mHandler);
assertEquals(mActivity, context);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 207fe730aac2..906558f7603b 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -151,12 +151,14 @@ public class ObjectPoolTests {
@Test
public void testRecycleActivityRelaunchItem() {
testRecycle(() -> ActivityRelaunchItem.obtain(mActivityToken,
- resultInfoList(), referrerIntentList(), 42, mergedConfig(), true));
+ resultInfoList(), referrerIntentList(), 42, mergedConfig(), true,
+ new ActivityWindowInfo()));
}
@Test
public void testRecycleMoveToDisplayItem() {
- testRecycle(() -> MoveToDisplayItem.obtain(mActivityToken, 4, config()));
+ testRecycle(() -> MoveToDisplayItem.obtain(mActivityToken, 4, config(),
+ new ActivityWindowInfo()));
}
@Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 03b85dc8b4e2..dbb090fe795b 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -110,8 +110,11 @@ public class TransactionParcelTests {
@Test
public void testMoveToDisplay() {
// Write to parcel
+ final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+ activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 500, 1000),
+ new Rect(0, 0, 500, 500));
MoveToDisplayItem item = MoveToDisplayItem.obtain(mActivityToken, 4 /* targetDisplayId */,
- config());
+ config(), activityWindowInfo);
writeAndPrepareForReading(item);
// Read from parcel and assert
@@ -220,8 +223,11 @@ public class TransactionParcelTests {
// Write to parcel
Configuration overrideConfig = new Configuration();
overrideConfig.assetsSeq = 5;
+ final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+ activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 500, 1000),
+ new Rect(0, 0, 500, 500));
ActivityRelaunchItem item = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(),
- referrerIntentList(), 35, mergedConfig(), true);
+ referrerIntentList(), 35, mergedConfig(), true, activityWindowInfo);
writeAndPrepareForReading(item);
// Read from parcel and assert
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index 3ee565f8e025..e118c98dd4da 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -403,41 +403,4 @@ public class SQLiteDatabaseTest {
}
assertFalse(allowed);
}
-
- /** Return true if the path is in the list of strings. */
- private boolean isConcurrent(String path) throws Exception {
- path = new File(path).toPath().toRealPath().toString();
- return SQLiteDatabase.getConcurrentDatabasePaths().contains(path);
- }
-
- @Test
- public void testDuplicateDatabases() throws Exception {
- // The two database paths in this test are assumed not to have been opened earlier in this
- // process.
-
- // A database path that will be opened twice.
- final String dbName = "never-used-db.db";
- final File dbFile = mContext.getDatabasePath(dbName);
- final String dbPath = dbFile.getPath();
-
- // A database path that will be opened only once.
- final String okName = "never-used-ok.db";
- final File okFile = mContext.getDatabasePath(okName);
- final String okPath = okFile.getPath();
-
- SQLiteDatabase db1 = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
- assertFalse(isConcurrent(dbPath));
- SQLiteDatabase db2 = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
- assertTrue(isConcurrent(dbPath));
- db1.close();
- assertTrue(isConcurrent(dbPath));
- db2.close();
- assertTrue(isConcurrent(dbPath));
-
- SQLiteDatabase db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null);
- db3.close();
- db3 = SQLiteDatabase.openOrCreateDatabase(okFile, null);
- assertFalse(isConcurrent(okPath));
- db3.close();
- }
}
diff --git a/core/tests/coretests/src/android/view/RoundedCornersTest.java b/core/tests/coretests/src/android/view/RoundedCornersTest.java
index 07ef33af3088..ec665ad042ae 100644
--- a/core/tests/coretests/src/android/view/RoundedCornersTest.java
+++ b/core/tests/coretests/src/android/view/RoundedCornersTest.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.content.res.Configuration.SCREENLAYOUT_ROUND_NO;
+import static android.content.res.Configuration.SCREENLAYOUT_ROUND_YES;
import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
import static android.view.RoundedCorner.POSITION_TOP_LEFT;
@@ -28,22 +30,47 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.platform.test.annotations.Presubmit;
+import android.util.DisplayMetrics;
import android.util.Pair;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
+
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
public class RoundedCornersTest {
+ @Mock
+ DisplayMetrics mMockDisplayMetrics;
+ @Mock
+ Resources mMockResources;
+ @Mock TypedArray mMockTypedArray;
+ Configuration mConfiguration;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mConfiguration = Configuration.EMPTY;
+ }
+
final RoundedCorners mRoundedCorners = new RoundedCorners(
new RoundedCorner(POSITION_TOP_LEFT, 10, 10, 10),
new RoundedCorner(POSITION_TOP_RIGHT, 10, 190, 10),
@@ -199,4 +226,63 @@ public class RoundedCornersTest {
assertThat(RoundedCorners.fromRadii(radius, 200, 300),
not(sameInstance(cached)));
}
+
+ @Test
+ public void testGetRoundedCornerRadius_withRoundDevice_usesDisplayRadiusAsDefault() {
+ final int displayWidth = 400;
+ mConfiguration.screenLayout = SCREENLAYOUT_ROUND_YES;
+ mMockDisplayMetrics.widthPixels = displayWidth;
+
+ when(mMockResources.getConfiguration()).thenReturn(mConfiguration);
+ when(mMockResources.getDisplayMetrics()).thenReturn(mMockDisplayMetrics);
+ when(mMockResources.getStringArray(R.array.config_displayUniqueIdArray))
+ .thenReturn(new String[]{"0"});
+ when(mMockTypedArray.length()).thenReturn(0);
+ when(mMockResources.obtainTypedArray(anyInt())).thenReturn(mMockTypedArray);
+ when(mMockResources.getDimensionPixelSize(R.dimen.rounded_corner_radius))
+ .thenReturn(0);
+
+
+ int radius = RoundedCorners.getRoundedCornerRadius(mMockResources, "0");
+ assertEquals((displayWidth / 2), radius);
+ }
+
+ @Test
+ public void testGetRoundedCornerRadius_withRoundDevice_usesOverlayIfProvided() {
+ final int displayWidth = 400;
+ final int overlayValue = 199;
+ mConfiguration.screenLayout = SCREENLAYOUT_ROUND_YES;
+ mMockDisplayMetrics.widthPixels = displayWidth;
+
+ when(mMockResources.getConfiguration()).thenReturn(mConfiguration);
+ when(mMockResources.getDisplayMetrics()).thenReturn(mMockDisplayMetrics);
+ when(mMockResources.getStringArray(R.array.config_displayUniqueIdArray))
+ .thenReturn(new String[]{"0"});
+ when(mMockTypedArray.length()).thenReturn(0);
+ when(mMockResources.obtainTypedArray(anyInt())).thenReturn(mMockTypedArray);
+ when(mMockResources.getDimensionPixelSize(R.dimen.rounded_corner_radius))
+ .thenReturn(overlayValue);
+
+ int radius = RoundedCorners.getRoundedCornerRadius(mMockResources, "0");
+ assertEquals(overlayValue, radius);
+ }
+
+ @Test
+ public void testGetRoundedCornerRadius_withNonRoundDevice_noDisplayDefault() {
+ final int displayWidth = 400;
+ mConfiguration.screenLayout = SCREENLAYOUT_ROUND_NO;
+ mMockDisplayMetrics.widthPixels = displayWidth;
+
+ when(mMockResources.getConfiguration()).thenReturn(mConfiguration);
+ when(mMockResources.getDisplayMetrics()).thenReturn(mMockDisplayMetrics);
+ when(mMockResources.getStringArray(R.array.config_displayUniqueIdArray))
+ .thenReturn(new String[]{"0"});
+ when(mMockTypedArray.length()).thenReturn(0);
+ when(mMockResources.obtainTypedArray(anyInt())).thenReturn(mMockTypedArray);
+ when(mMockResources.getDimensionPixelSize(R.dimen.rounded_corner_radius))
+ .thenReturn(0);
+
+ int radius = RoundedCorners.getRoundedCornerRadius(mMockResources, "0");
+ assertEquals(0, radius);
+ }
}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 51eb41c5a271..b60b806f3444 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -472,6 +472,26 @@ public class HandwritingInitiatorTest {
}
@Test
+ public void onTouchEvent_doesNothing_viewDisabled() {
+ mTestView1.setEnabled(false);
+
+ final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
+ final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
+ MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+ mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+ final int x2 = x1 + mHandwritingSlop * 2;
+ final int y2 = y1;
+
+ MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+ mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+ // HandwritingInitiator will not request focus if it is disabled.
+ verify(mTestView1, never()).requestFocus();
+ verify(mHandwritingInitiator, never()).startHandwriting(mTestView1);
+ }
+
+ @Test
public void onTouchEvent_focusView_inputConnectionAlreadyBuilt_stylusMoveOnce_withinHWArea() {
if (!mInitiateWithoutConnection) {
mHandwritingInitiator.onInputConnectionCreated(mTestView1);
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 11b827117aa3..bd9abec22325 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -21,12 +21,14 @@ import android.os.Build;
import android.os.StrictMode;
/**
- * @hide This should not be made public in its present form because it
- * assumes that private and secret key bytes are available and would
- * preclude the use of hardware crypto.
+ * This class provides some constants and helper methods related to Android's Keystore service.
+ * This class was originally much larger, but its functionality was superseded by other classes.
+ * It now just contains a few remaining pieces for which the users haven't been updated yet.
+ * You may be looking for {@link java.security.KeyStore} instead.
+ *
+ * @hide
*/
public class KeyStore {
- private static final String TAG = "KeyStore";
// ResponseCodes - see system/security/keystore/include/keystore/keystore.h
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -42,50 +44,6 @@ public class KeyStore {
return KEY_STORE;
}
- /** @hide */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public byte[] get(String key) {
- return null;
- }
-
- /** @hide */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public boolean delete(String key) {
- return false;
- }
-
- /**
- * List uids of all keys that are auth bound to the current user.
- * Only system is allowed to call this method.
- * @hide
- * @deprecated This function always returns null.
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public int[] listUidsOfAuthBoundKeys() {
- return null;
- }
-
-
- /**
- * @hide
- * @deprecated This function has no effect.
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public boolean unlock(String password) {
- return false;
- }
-
- /**
- *
- * @return
- * @deprecated This function always returns true.
- * @hide
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- public boolean isEmpty() {
- return true;
- }
-
/**
* Add an authentication record to the keystore authorization table.
*
@@ -105,13 +63,4 @@ public class KeyStore {
public void onDeviceOffBody() {
AndroidKeyStoreMaintenance.onDeviceOffBody();
}
-
- /**
- * Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error
- * code.
- */
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static KeyStoreException getKeyStoreException(int errorCode) {
- return new KeyStoreException(-10000, "Should not be called.");
- }
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
index 62fe54f1f089..ef03d3a3b286 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -19,9 +19,9 @@ package android.security.keystore;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
-import android.security.KeyStore;
import java.io.IOException;
+import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
@@ -47,13 +47,13 @@ public class AndroidKeyStoreProvider extends Provider {
}
/**
- * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto
+ * Gets the Android KeyStore operation handle corresponding to the provided JCA crypto
* primitive.
*
* <p>The following primitives are supported: {@link Cipher} and {@link Mac}.
*
- * @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation
- * is not in progress.
+ * @return Android KeyStore operation handle or {@code 0} if the provided primitive's Android
+ * KeyStore operation is not in progress.
*
* @throws IllegalArgumentException if the provided primitive is not supported or is not backed
* by AndroidKeyStore provider.
@@ -67,10 +67,10 @@ public class AndroidKeyStoreProvider extends Provider {
}
/**
- * Returns an {@code AndroidKeyStore} {@link java.security.KeyStore}} of the specified UID.
- * The {@code KeyStore} contains keys and certificates owned by that UID. Such cross-UID
- * access is permitted to a few system UIDs and only to a few other UIDs (e.g., Wi-Fi, VPN)
- * all of which are system.
+ * Returns an {@code AndroidKeyStore} {@link KeyStore} of the specified UID. The {@code
+ * KeyStore} contains keys and certificates owned by that UID. Such cross-UID access is
+ * permitted to a few system UIDs and only to a few other UIDs (e.g., Wi-Fi, VPN) all of which
+ * are system.
*
* <p>Note: the returned {@code KeyStore} is already initialized/loaded. Thus, there is
* no need to invoke {@code load} on it.
@@ -84,12 +84,12 @@ public class AndroidKeyStoreProvider extends Provider {
*/
@SystemApi
@NonNull
- public static java.security.KeyStore getKeyStoreForUid(int uid)
+ public static KeyStore getKeyStoreForUid(int uid)
throws KeyStoreException, NoSuchProviderException {
- final java.security.KeyStore.LoadStoreParameter loadParameter =
+ final KeyStore.LoadStoreParameter loadParameter =
new android.security.keystore2.AndroidKeyStoreLoadStoreParameter(
KeyProperties.legacyUidToNamespace(uid));
- java.security.KeyStore result = java.security.KeyStore.getInstance(PROVIDER_NAME);
+ KeyStore result = KeyStore.getInstance(PROVIDER_NAME);
try {
result.load(loadParameter);
} catch (NoSuchAlgorithmException | CertificateException | IOException e) {
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 244fe3033dca..7aecfd8d4a0d 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -910,7 +910,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu
/**
* Returns whether this key is critical to the device encryption flow.
*
- * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+ * @see Builder#setCriticalToDeviceEncryption(boolean)
* @hide
*/
public boolean isCriticalToDeviceEncryption() {
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index f50efd2c3328..5cffe46936a2 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -16,6 +16,7 @@
package android.security.keystore;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -81,6 +82,7 @@ public class KeyInfo implements KeySpec {
private final @KeyProperties.AuthEnum int mUserAuthenticationType;
private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware;
private final boolean mUserAuthenticationValidWhileOnBody;
+ private final boolean mUnlockedDeviceRequired;
private final boolean mTrustedUserPresenceRequired;
private final boolean mInvalidatedByBiometricEnrollment;
private final boolean mUserConfirmationRequired;
@@ -107,6 +109,7 @@ public class KeyInfo implements KeySpec {
@KeyProperties.AuthEnum int userAuthenticationType,
boolean userAuthenticationRequirementEnforcedBySecureHardware,
boolean userAuthenticationValidWhileOnBody,
+ boolean unlockedDeviceRequired,
boolean trustedUserPresenceRequired,
boolean invalidatedByBiometricEnrollment,
boolean userConfirmationRequired,
@@ -132,6 +135,7 @@ public class KeyInfo implements KeySpec {
mUserAuthenticationRequirementEnforcedBySecureHardware =
userAuthenticationRequirementEnforcedBySecureHardware;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
+ mUnlockedDeviceRequired = unlockedDeviceRequired;
mTrustedUserPresenceRequired = trustedUserPresenceRequired;
mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
mUserConfirmationRequired = userConfirmationRequired;
@@ -275,6 +279,20 @@ public class KeyInfo implements KeySpec {
}
/**
+ * Returns {@code true} if the key is authorized to be used only when the device is unlocked.
+ *
+ * <p>This authorization applies only to secret key and private key operations. Public key
+ * operations are not restricted.
+ *
+ * @see KeyGenParameterSpec.Builder#setUnlockedDeviceRequired(boolean)
+ * @see KeyProtection.Builder#setUnlockedDeviceRequired(boolean)
+ */
+ @FlaggedApi(android.security.Flags.FLAG_KEYINFO_UNLOCKED_DEVICE_REQUIRED)
+ public boolean isUnlockedDeviceRequired() {
+ return mUnlockedDeviceRequired;
+ }
+
+ /**
* Returns {@code true} if the key is authorized to be used only for messages confirmed by the
* user.
*
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index 2495d1a85864..31b4a5eac619 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -569,7 +569,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
/**
* Return whether this key is critical to the device encryption flow.
*
- * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+ * @see Builder#setCriticalToDeviceEncryption(boolean)
* @hide
*/
public boolean isCriticalToDeviceEncryption() {
@@ -1105,9 +1105,10 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs {
* Set whether this key is critical to the device encryption flow
*
* This is a special flag only available to system servers to indicate the current key
- * is part of the device encryption flow.
+ * is part of the device encryption flow. Setting this flag causes the key to not
+ * be cryptographically bound to the LSKF even if the key is otherwise authentication
+ * bound.
*
- * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
* @hide
*/
public Builder setCriticalToDeviceEncryption(boolean critical) {
diff --git a/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java b/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java
index 2c709ae1ac5b..c42c9e4d99a6 100644
--- a/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java
+++ b/keystore/java/android/security/keystore/KeyStoreCryptoOperation.java
@@ -16,18 +16,16 @@
package android.security.keystore;
-import android.security.KeyStore;
-
/**
- * Cryptographic operation backed by {@link KeyStore}.
+ * Cryptographic operation backed by Android KeyStore.
*
* @hide
*/
public interface KeyStoreCryptoOperation {
/**
- * Gets the KeyStore operation handle of this crypto operation.
+ * Gets the Android KeyStore operation handle of this crypto operation.
*
- * @return handle or {@code 0} if the KeyStore operation is not in progress.
+ * @return handle or {@code 0} if the Android KeyStore operation is not in progress.
*/
long getOperationHandle();
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java
index a8dd7f3f8b14..8eca67f090d4 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java
@@ -16,7 +16,6 @@
package android.security.keystore2;
-import android.security.KeyStore;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyInfo;
@@ -39,8 +38,6 @@ import java.security.spec.X509EncodedKeySpec;
*/
public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi {
- private final KeyStore mKeyStore = KeyStore.getInstance();
-
@Override
protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpecClass)
throws InvalidKeySpecException {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index d204f13d4d78..99100de12684 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -17,7 +17,6 @@
package android.security.keystore2;
import android.annotation.NonNull;
-import android.security.KeyStore;
import android.security.KeyStore2;
import android.security.KeyStoreSecurityLevel;
import android.security.keymaster.KeymasterDefs;
@@ -161,13 +160,13 @@ public class AndroidKeyStoreProvider extends Provider {
}
/**
- * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto
+ * Gets the Android KeyStore operation handle corresponding to the provided JCA crypto
* primitive.
*
* <p>The following primitives are supported: {@link Cipher}, {@link Signature} and {@link Mac}.
*
- * @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation
- * is not in progress.
+ * @return Android KeyStore operation handle or {@code 0} if the provided primitive's Android
+ * KeyStore operation is not in progress.
*
* @throws IllegalArgumentException if the provided primitive is not supported or is not backed
* by AndroidKeyStore provider.
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
index 97592b44ba2e..22230916b084 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -18,7 +18,6 @@ package android.security.keystore2;
import android.annotation.NonNull;
import android.security.GateKeeper;
-import android.security.KeyStore;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterDefs;
import android.security.keystore.KeyGenParameterSpec;
@@ -46,8 +45,6 @@ import javax.crypto.spec.SecretKeySpec;
*/
public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
- private final KeyStore mKeyStore = KeyStore.getInstance();
-
@Override
protected KeySpec engineGetKeySpec(SecretKey key,
@SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException {
@@ -93,6 +90,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
long userAuthenticationValidityDurationSeconds = 0;
boolean userAuthenticationRequired = true;
boolean userAuthenticationValidWhileOnBody = false;
+ boolean unlockedDeviceRequired = false;
boolean trustedUserPresenceRequired = false;
boolean trustedUserConfirmationRequired = false;
int remainingUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT;
@@ -184,6 +182,9 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
+ userAuthenticationValidityDurationSeconds + " seconds");
}
break;
+ case KeymasterDefs.KM_TAG_UNLOCKED_DEVICE_REQUIRED:
+ unlockedDeviceRequired = true;
+ break;
case KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY:
userAuthenticationValidWhileOnBody =
KeyStore2ParameterUtils.isSecureHardware(a.securityLevel);
@@ -257,6 +258,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi {
: keymasterSwEnforcedUserAuthenticators,
userAuthenticationRequirementEnforcedBySecureHardware,
userAuthenticationValidWhileOnBody,
+ unlockedDeviceRequired,
trustedUserPresenceRequired,
invalidatedByBiometricEnrollment,
trustedUserConfirmationRequired,
diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java
index 07d6a69eda01..5bd98bce9f39 100644
--- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java
+++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java
@@ -16,12 +16,11 @@
package android.security.keystore2;
-import android.security.KeyStore;
import android.security.KeyStoreException;
/**
- * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's
- * {@code update} and {@code finish} operations.
+ * Helper for streaming a crypto operation's input and output via KeyStore service's {@code update}
+ * and {@code finish} operations.
*
* <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's
* update and finish operations. Firstly, KeyStore's update operation can consume only a limited
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 0967f4e83c74..b61dda4c4e53 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -8,14 +8,6 @@ flag {
}
flag {
- name: "enable_desktop_windowing"
- namespace: "multitasking"
- description: "Enables desktop windowing"
- bug: "304778354"
- is_fixed_read_only: true
-}
-
-flag {
name: "enable_split_contextual"
namespace: "multitasking"
description: "Enables invoking split contextually"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index d1b1af3e77ab..490f0883fbfb 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -16,6 +16,7 @@
-->
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/desktop_mode_caption"
android:layout_width="match_parent"
@@ -27,6 +28,8 @@
android:id="@+id/open_menu_button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:background="?android:selectableItemBackground"
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
@@ -78,7 +81,9 @@
android:id="@+id/maximize_button_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="end"/>
+ android:layout_gravity="end"
+ android:clickable="true"
+ android:focusable="true" />
<ImageButton
android:id="@+id/close_window"
@@ -86,9 +91,10 @@
android:layout_height="40dp"
android:padding="4dp"
android:layout_marginEnd="8dp"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:background="?android:selectableItemBackgroundBorderless"
android:contentDescription="@string/close_button_text"
android:src="@drawable/decor_close_button_dark"
android:scaleType="fitCenter"
- android:gravity="end"
- android:background="@null"/>
+ android:gravity="end"/>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
index bb6efcec1a70..e0057fe64fd2 100644
--- a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
+++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
@@ -14,7 +14,8 @@
~ limitations under the License.
-->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<ProgressBar
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
@@ -30,7 +31,8 @@
android:layout_height="40dp"
android:padding="9dp"
android:contentDescription="@string/maximize_button_text"
+ android:tint="?androidprv:attr/materialColorOnSurface"
+ android:background="?android:selectableItemBackgroundBorderless"
android:src="@drawable/decor_desktop_mode_maximize_button_dark"
- android:scaleType="fitCenter"
- android:background="@drawable/rounded_button"/>
+ android:scaleType="fitCenter" />
</merge> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index cbfa74e9332b..48e6428524ae 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -101,6 +101,10 @@
<dimen name="split_divider_bar_width">10dp</dimen>
<dimen name="split_divider_corner_size">42dp</dimen>
+ <!-- The distance from the edge of the screen to invoke splitscreen when the user is dragging
+ an intent that can be launched into split. -->
+ <dimen name="drag_launchable_intent_edge_margin">48dp</dimen>
+
<!-- One-Handed Mode -->
<!-- Threshold for dragging distance to enable one-handed mode -->
<dimen name="gestures_onehanded_drag_threshold">20dp</dimen>
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 15350fb19209..96aaf02cb5e3 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
@@ -1799,11 +1799,12 @@ public class BubbleController implements ConfigurationChangeListener,
@Override
public void removeBubble(Bubble removedBubble) {
if (mLayerView != null) {
- mLayerView.removeBubble(removedBubble);
- if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
- mLayerView.setVisibility(INVISIBLE);
- removeFromWindowManagerMaybe();
- }
+ mLayerView.removeBubble(removedBubble, () -> {
+ if (!mBubbleData.hasBubbles() && !isStackExpanded()) {
+ mLayerView.setVisibility(INVISIBLE);
+ removeFromWindowManagerMaybe();
+ }
+ });
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 78a41f759d96..42799d975e1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -242,13 +242,17 @@ public class BubbleBarLayerView extends FrameLayout
}
/** Removes the given {@code bubble}. */
- public void removeBubble(Bubble bubble) {
+ public void removeBubble(Bubble bubble, Runnable endAction) {
+ Runnable cleanUp = () -> {
+ bubble.cleanupViews();
+ endAction.run();
+ };
if (mBubbleData.getBubbles().isEmpty()) {
// we're removing the last bubble. collapse the expanded view and cleanup bubble views
// at the end.
- collapse(bubble::cleanupViews);
+ collapse(cleanUp);
} else {
- bubble.cleanupViews();
+ cleanUp.run();
}
}
@@ -264,6 +268,9 @@ public class BubbleBarLayerView extends FrameLayout
*/
public void collapse(@Nullable Runnable endAction) {
if (!mIsExpanded) {
+ if (endAction != null) {
+ endAction.run();
+ }
return;
}
mIsExpanded = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
index 8305fa6b0fbf..1071d728a56d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java
@@ -51,4 +51,6 @@ public interface DesktopMode {
/** Called when requested to go to desktop mode from the current focused app. */
void enterDesktop(int displayId);
+ /** Called when requested to go to fullscreen from the current focused desktop app. */
+ void moveFocusedTaskToFullscreen(int displayId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
new file mode 100644
index 000000000000..95d47146e834
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.desktopmode
+
+import com.android.internal.util.FrameworkStatsLog
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.util.KtProtoLog
+
+/**
+ * Event logger for logging desktop mode session events
+ */
+class DesktopModeEventLogger {
+ /**
+ * Logs the enter of desktop mode having session id [sessionId] and the reason [enterReason] for
+ * entering desktop mode
+ */
+ fun logSessionEnter(sessionId: Int, enterReason: EnterReason) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging session enter, session: %s reason: %s",
+ sessionId, enterReason.name
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_ATOM_ID,
+ /* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER,
+ /* enterReason */ enterReason.reason,
+ /* exitReason */ 0,
+ /* session_id */ sessionId)
+ }
+
+ /**
+ * Logs the exit of desktop mode having session id [sessionId] and the reason [exitReason] for
+ * exiting desktop mode
+ */
+ fun logSessionExit(sessionId: Int, exitReason: ExitReason) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging session exit, session: %s reason: %s",
+ sessionId, exitReason.name
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_ATOM_ID,
+ /* event */ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT,
+ /* enterReason */ 0,
+ /* exitReason */ exitReason.reason,
+ /* session_id */ sessionId)
+ }
+
+ /**
+ * Logs that the task with update [taskUpdate] was added in the desktop mode session having
+ * session id [sessionId]
+ */
+ fun logTaskAdded(sessionId: Int, taskUpdate: TaskUpdate) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task added, session: %s taskId: %s",
+ sessionId, taskUpdate.instanceId
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
+ /* task_event */
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED,
+ /* instance_id */
+ taskUpdate.instanceId,
+ /* uid */
+ taskUpdate.uid,
+ /* task_height */
+ taskUpdate.taskHeight,
+ /* task_width */
+ taskUpdate.taskWidth,
+ /* task_x */
+ taskUpdate.taskX,
+ /* task_y */
+ taskUpdate.taskY,
+ /* session_id */
+ sessionId)
+ }
+
+ /**
+ * Logs that the task with update [taskUpdate] was removed in the desktop mode session having
+ * session id [sessionId]
+ */
+ fun logTaskRemoved(sessionId: Int, taskUpdate: TaskUpdate) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task remove, session: %s taskId: %s",
+ sessionId, taskUpdate.instanceId
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
+ /* task_event */
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED,
+ /* instance_id */
+ taskUpdate.instanceId,
+ /* uid */
+ taskUpdate.uid,
+ /* task_height */
+ taskUpdate.taskHeight,
+ /* task_width */
+ taskUpdate.taskWidth,
+ /* task_x */
+ taskUpdate.taskX,
+ /* task_y */
+ taskUpdate.taskY,
+ /* session_id */
+ sessionId)
+ }
+
+ /**
+ * Logs that the task with update [taskUpdate] had it's info changed in the desktop mode session
+ * having session id [sessionId]
+ */
+ fun logTaskInfoChanged(sessionId: Int, taskUpdate: TaskUpdate) {
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DesktopModeLogger: Logging task info changed, session: %s taskId: %s",
+ sessionId, taskUpdate.instanceId
+ )
+ FrameworkStatsLog.write(DESKTOP_MODE_TASK_UPDATE_ATOM_ID,
+ /* task_event */
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED,
+ /* instance_id */
+ taskUpdate.instanceId,
+ /* uid */
+ taskUpdate.uid,
+ /* task_height */
+ taskUpdate.taskHeight,
+ /* task_width */
+ taskUpdate.taskWidth,
+ /* task_x */
+ taskUpdate.taskX,
+ /* task_y */
+ taskUpdate.taskY,
+ /* session_id */
+ sessionId)
+ }
+
+ companion object {
+ data class TaskUpdate(
+ val instanceId: Int,
+ val uid: Int,
+ val taskHeight: Int = Int.MIN_VALUE,
+ val taskWidth: Int = Int.MIN_VALUE,
+ val taskX: Int = Int.MIN_VALUE,
+ val taskY: Int = Int.MIN_VALUE,
+ )
+
+ /**
+ * Enum EnterReason mapped to the EnterReason definition in
+ * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+ */
+ enum class EnterReason(val reason: Int) {
+ UNKNOWN_ENTER(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__UNKNOWN_ENTER
+ ),
+ OVERVIEW(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__OVERVIEW
+ ),
+ APP_HANDLE_DRAG(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__APP_HANDLE_DRAG
+ ),
+ APP_HANDLE_MENU_BUTTON(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__APP_HANDLE_MENU_BUTTON
+ ),
+ APP_FREEFORM_INTENT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__APP_FREEFORM_INTENT
+ ),
+ KEYBOARD_SHORTCUT_ENTER(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER
+ ),
+ SCREEN_ON(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__SCREEN_ON
+ );
+ }
+
+ /**
+ * Enum ExitReason mapped to the ExitReason definition in
+ * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
+ */
+ enum class ExitReason(val reason: Int) {
+ UNKNOWN_EXIT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__UNKNOWN_EXIT
+ ),
+ DRAG_TO_EXIT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT
+ ),
+ APP_HANDLE_MENU_BUTTON_EXIT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__APP_HANDLE_MENU_BUTTON_EXIT
+ ),
+ KEYBOARD_SHORTCUT_EXIT(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__KEYBOARD_SHORTCUT_EXIT
+ ),
+ RETURN_HOME_OR_OVERVIEW(
+ FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME
+ ),
+ TASK_FINISHED(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__TASK_FINISHED
+ ),
+ SCREEN_OFF(
+ FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__SCREEN_OFF
+ )
+ }
+
+ private const val DESKTOP_MODE_ATOM_ID = FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED
+ private const val DESKTOP_MODE_TASK_UPDATE_ATOM_ID =
+ FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 88949b2a5acd..22ba70860587 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -18,21 +18,13 @@ package com.android.wm.shell.desktopmode;
import android.os.SystemProperties;
-import com.android.wm.shell.Flags;
+import com.android.window.flags.Flags;
/**
* Constants for desktop mode feature
*/
public class DesktopModeStatus {
- private static final boolean ENABLE_DESKTOP_WINDOWING = Flags.enableDesktopWindowing();
-
- /**
- * Flag to indicate whether desktop mode proto is available on the device
- */
- private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
- "persist.wm.debug.desktop_mode_2", false);
-
/**
* Flag to indicate whether task resizing is veiled.
*/
@@ -75,16 +67,10 @@ public class DesktopModeStatus {
"persist.wm.debug.desktop_use_rounded_corners", true);
/**
- * Return {@code true} is desktop windowing proto 2 is enabled
+ * Return {@code true} if desktop windowing is enabled
*/
public static boolean isEnabled() {
- // Check for aconfig flag first
- if (ENABLE_DESKTOP_WINDOWING) {
- return true;
- }
- // Fall back to sysprop flag
- // TODO(b/304778354): remove sysprop once desktop aconfig flag supports dynamic overriding
- return IS_PROTO2_ENABLED;
+ return Flags.enableDesktopWindowingMode();
}
/**
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 dcffb2d3e8fa..b9d0342137c5 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
@@ -381,6 +381,18 @@ class DesktopTasksController(
}
}
+ /** Enter fullscreen by moving the focused freeform task in given `displayId` to fullscreen. */
+ fun enterFullscreen(displayId: Int) {
+ if (DesktopModeStatus.isEnabled()) {
+ shellTaskOrganizer
+ .getRunningTasks(displayId)
+ .find { taskInfo ->
+ taskInfo.isFocused && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+ ?.let { moveToFullscreenWithAnimation(it, it.positionInParent) }
+ }
+ }
+
/** Move a desktop app to split screen. */
fun moveToSplit(task: RunningTaskInfo) {
KtProtoLog.v(
@@ -1108,6 +1120,12 @@ class DesktopTasksController(
this@DesktopTasksController.enterDesktop(displayId)
}
}
+
+ override fun moveFocusedTaskToFullscreen(displayId: Int) {
+ mainExecutor.execute {
+ this@DesktopTasksController.enterFullscreen(displayId)
+ }
+ }
}
/** The interface for calls from outside the host process. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 1afbdf90eac0..7da1b23dd5b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -59,7 +59,6 @@ import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
@@ -316,12 +315,11 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
return false;
}
// TODO(b/290391688): Also update the session data with task stack changes
- InstanceId loggerSessionId = mLogger.logStart(event);
- pd.activeDragCount++;
- pd.dragSession = new DragSession(mContext, ActivityTaskManager.getInstance(),
+ pd.dragSession = new DragSession(ActivityTaskManager.getInstance(),
mDisplayController.getDisplayLayout(displayId), event.getClipData());
pd.dragSession.update();
- pd.dragLayout.prepare(pd.dragSession, loggerSessionId);
+ pd.activeDragCount++;
+ pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
setDropTargetWindowVisibility(pd, View.VISIBLE);
notifyListeners(l -> {
l.onDragStarted();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
index 2a7dd5aeb341..75b126c47690 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropEventLogger.java
@@ -53,17 +53,21 @@ public class DragAndDropEventLogger {
/**
* Logs the start of a drag.
*/
- public InstanceId logStart(DragEvent event) {
- final ClipDescription description = event.getClipDescription();
- final ClipData data = event.getClipData();
- final ClipData.Item item = data.getItemAt(0);
- mInstanceId = item.getIntent().getParcelableExtra(
- ClipDescription.EXTRA_LOGGING_INSTANCE_ID);
+ public InstanceId logStart(DragSession session) {
+ mInstanceId = session.appData != null
+ ? session.appData.getParcelableExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID,
+ InstanceId.class)
+ : null;
if (mInstanceId == null) {
mInstanceId = mIdSequence.newInstanceId();
}
- mActivityInfo = item.getActivityInfo();
- log(getStartEnum(description), mActivityInfo);
+ mActivityInfo = session.activityInfo;
+ if (session.appData != null) {
+ log(getStartEnum(session.getClipDescription()), mActivityInfo);
+ } else {
+ // TODO(b/255649902): Update this once we have a new enum
+ log(DragAndDropUiEventEnum.GLOBAL_APP_DRAG_START_ACTIVITY, mActivityInfo);
+ }
return mInstanceId;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index a31a773a76a0..eb82da8a8e9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -29,6 +29,8 @@ import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.Intent.EXTRA_SHORTCUT_ID;
import static android.content.Intent.EXTRA_TASK_ID;
import static android.content.Intent.EXTRA_USER;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
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;
@@ -52,9 +54,11 @@ import android.content.pm.LauncherApps;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.Log;
import android.util.Slog;
import androidx.annotation.IntDef;
@@ -63,8 +67,10 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.lang.annotation.Retention;
@@ -104,7 +110,9 @@ public class DragAndDropPolicy {
void start(DragSession session, InstanceId loggerSessionId) {
mLoggerSessionId = loggerSessionId;
mSession = session;
- RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION);
+ RectF disallowHitRegion = mSession.appData != null
+ ? (RectF) mSession.appData.getExtra(EXTRA_DISALLOW_HIT_REGION)
+ : null;
if (disallowHitRegion == null) {
mDisallowHitRegion.setEmpty();
} else {
@@ -223,7 +231,7 @@ public class DragAndDropPolicy {
}
@VisibleForTesting
- void handleDrop(Target target, ClipData data) {
+ void handleDrop(Target target) {
if (target == null || !mTargets.contains(target)) {
return;
}
@@ -238,41 +246,77 @@ public class DragAndDropPolicy {
mSplitScreen.onDroppedToSplit(position, mLoggerSessionId);
}
- final ClipDescription description = data.getDescription();
- final Intent dragData = mSession.dragData;
- startClipDescription(description, dragData, position);
+ if (mSession.appData != null) {
+ launchApp(mSession, position);
+ } else {
+ launchIntent(mSession, position);
+ }
}
- private void startClipDescription(ClipDescription description, Intent intent,
- @SplitPosition int position) {
+ /**
+ * Launches an app provided by SysUI.
+ */
+ private void launchApp(DragSession session, @SplitPosition int position) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d",
+ position);
+ final ClipDescription description = session.getClipDescription();
final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
final Bundle opts = baseActivityOpts.toBundle();
- if (intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
- opts.putAll(intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
+ if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) {
+ opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS));
}
// Put BAL flags to avoid activity start aborted.
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
- final UserHandle user = intent.getParcelableExtra(EXTRA_USER);
+ final UserHandle user = session.appData.getParcelableExtra(EXTRA_USER);
if (isTask) {
- final int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
+ final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
mStarter.startTask(taskId, position, opts);
} else if (isShortcut) {
- final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
- final String id = intent.getStringExtra(EXTRA_SHORTCUT_ID);
+ final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME);
+ final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID);
mStarter.startShortcut(packageName, id, position, opts, user);
} else {
- final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+ final PendingIntent launchIntent =
+ session.appData.getParcelableExtra(EXTRA_PENDING_INTENT);
+ if (Build.IS_DEBUGGABLE) {
+ if (!user.equals(launchIntent.getCreatorUserHandle())) {
+ Log.e(TAG, "Expected app intent's EXTRA_USER to match pending intent user");
+ }
+ }
mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
position, opts);
}
}
/**
+ * Launches an intent sender provided by an application.
+ */
+ private void launchIntent(DragSession session, @SplitPosition int position) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d",
+ position);
+ final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
+ baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true);
+ // TODO(b/255649902): Rework this so that SplitScreenController can always use the options
+ // instead of a fillInIntent since it's assuming that the PendingIntent is mutable
+ baseActivityOpts.setPendingIntentLaunchFlags(FLAG_ACTIVITY_NEW_TASK
+ | FLAG_ACTIVITY_MULTIPLE_TASK);
+
+ final Bundle opts = baseActivityOpts.toBundle();
+ // Put BAL flags to avoid activity start aborted.
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
+
+ mStarter.startIntent(session.launchableIntent,
+ session.launchableIntent.getCreatorUserHandle().getIdentifier(),
+ null /* fillIntent */, position, opts);
+ }
+
+ /**
* Interface for actually committing the task launches.
*/
public interface Starter {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 619f624ff3bc..ecb53dc17a48 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -22,6 +22,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
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;
@@ -38,12 +40,15 @@ import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.view.DragEvent;
import android.view.SurfaceControl;
+import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
@@ -65,7 +70,8 @@ import java.util.ArrayList;
/**
* Coordinates the visible drop targets for the current drag within a single display.
*/
-public class DragLayout extends LinearLayout {
+public class DragLayout extends LinearLayout
+ implements ViewTreeObserver.OnComputeInternalInsetsListener {
// While dragging the status bar is hidden.
private static final int HIDE_STATUS_BAR_FLAGS = StatusBarManager.DISABLE_NOTIFICATION_ICONS
@@ -90,7 +96,9 @@ public class DragLayout extends LinearLayout {
private int mDisplayMargin;
private int mDividerSize;
+ private int mLaunchIntentEdgeMargin;
private Insets mInsets = Insets.NONE;
+ private Region mTouchableRegion;
private boolean mIsShowing;
private boolean mHasDropped;
@@ -106,10 +114,11 @@ public class DragLayout extends LinearLayout {
mStatusBarManager = context.getSystemService(StatusBarManager.class);
mLastConfiguration.setTo(context.getResources().getConfiguration());
- mDisplayMargin = context.getResources().getDimensionPixelSize(
- R.dimen.drop_layout_display_margin);
- mDividerSize = context.getResources().getDimensionPixelSize(
- R.dimen.split_divider_bar_width);
+ final Resources res = context.getResources();
+ mDisplayMargin = res.getDimensionPixelSize(R.dimen.drop_layout_display_margin);
+ mDividerSize = res.getDimensionPixelSize(R.dimen.split_divider_bar_width);
+ mLaunchIntentEdgeMargin =
+ res.getDimensionPixelSize(R.dimen.drag_launchable_intent_edge_margin);
// Always use LTR because we assume dropZoneView1 is on the left and 2 is on the right when
// showing the highlight.
@@ -131,6 +140,66 @@ public class DragLayout extends LinearLayout {
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mTouchableRegion = Region.obtain();
+ getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+ mTouchableRegion.recycle();
+ }
+
+ @Override
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inOutInfo) {
+ if (mSession != null && mSession.launchableIntent != null) {
+ inOutInfo.touchableRegion.set(mTouchableRegion);
+ inOutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ updateTouchableRegion();
+ }
+
+ /**
+ * Updates the touchable region, this should be called after any configuration changes have
+ * been applied.
+ */
+ private void updateTouchableRegion() {
+ mTouchableRegion.setEmpty();
+ if (mSession != null && mSession.launchableIntent != null) {
+ final int width = getMeasuredWidth();
+ final int height = getMeasuredHeight();
+ if (mIsLeftRightSplit) {
+ mTouchableRegion.union(
+ new Rect(0, 0, mInsets.left + mLaunchIntentEdgeMargin, height));
+ mTouchableRegion.union(
+ new Rect(width - mInsets.right - mLaunchIntentEdgeMargin, 0, width,
+ height));
+ } else {
+ mTouchableRegion.union(
+ new Rect(0, 0, width, mInsets.top + mLaunchIntentEdgeMargin));
+ mTouchableRegion.union(
+ new Rect(0, height - mInsets.bottom - mLaunchIntentEdgeMargin, width,
+ height));
+ }
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Updating drag layout width=%d height=%d touchable region=%s",
+ width, height, mTouchableRegion);
+
+ // Reapply insets to update the touchable region
+ requestApplyInsets();
+ }
+ }
+
+
+ @Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout());
recomputeDropTargets();
@@ -164,6 +233,7 @@ public class DragLayout extends LinearLayout {
mDropZoneView2.onThemeChange();
}
mLastConfiguration.setTo(newConfig);
+ requestLayout();
}
private void updateContainerMarginsForSingleTask() {
@@ -242,6 +312,7 @@ public class DragLayout extends LinearLayout {
mSplitScreenController.getStageBounds(topOrLeftBounds, bottomOrRightBounds);
updateDropZoneSizes(topOrLeftBounds, bottomOrRightBounds);
}
+ requestLayout();
}
private void updateDropZoneSizesForSingleTask() {
@@ -392,7 +463,7 @@ public class DragLayout extends LinearLayout {
mHasDropped = true;
// Process the drop
- mPolicy.handleDrop(mCurrentTarget, event.getClipData());
+ mPolicy.handleDrop(mCurrentTarget);
// Start animating the drop UI out with the drag surface
hide(event, dropCompleteCallback);
@@ -505,5 +576,7 @@ public class DragLayout extends LinearLayout {
pw.println(innerPrefix + "mIsShowing=" + mIsShowing);
pw.println(innerPrefix + "mHasDropped=" + mHasDropped);
pw.println(innerPrefix + "mCurrentTarget=" + mCurrentTarget);
+ pw.println(innerPrefix + "mInsets=" + mInsets);
+ pw.println(innerPrefix + "mTouchableRegion=" + mTouchableRegion);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 353d702e5bc4..8f1bc59af1ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -21,12 +21,15 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.ClipData;
-import android.content.Context;
+import android.content.ClipDescription;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.common.DisplayLayout;
import java.util.List;
@@ -39,7 +42,18 @@ public class DragSession {
private final ClipData mInitialDragData;
final DisplayLayout displayLayout;
- Intent dragData;
+ // The activity info associated with the activity in the appData or the launchableIntent
+ @Nullable
+ ActivityInfo activityInfo;
+ // The intent bundle that includes data about an app-type drag that is started by
+ // Launcher/SysUI. Only one of appDragData OR launchableIntent will be non-null for a session.
+ @Nullable
+ Intent appData;
+ // A launchable intent that is specified in the ClipData directly.
+ // Only one of appDragData OR launchableIntent will be non-null for a session.
+ @Nullable
+ PendingIntent launchableIntent;
+ // Stores the current running task at the time that the drag was initiated
ActivityManager.RunningTaskInfo runningTaskInfo;
@WindowConfiguration.WindowingMode
int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
@@ -47,7 +61,7 @@ public class DragSession {
int runningTaskActType = ACTIVITY_TYPE_STANDARD;
boolean dragItemSupportsSplitscreen;
- DragSession(Context context, ActivityTaskManager activityTaskManager,
+ DragSession(ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data) {
mActivityTaskManager = activityTaskManager;
mInitialDragData = data;
@@ -55,6 +69,14 @@ public class DragSession {
}
/**
+ * Returns the clip description associated with the drag.
+ * @return
+ */
+ ClipDescription getClipDescription() {
+ return mInitialDragData.getDescription();
+ }
+
+ /**
* Updates the session data based on the current state of the system.
*/
void update() {
@@ -67,9 +89,11 @@ public class DragSession {
runningTaskActType = task.getActivityType();
}
- final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
- dragItemSupportsSplitscreen = info == null
- || ActivityInfo.isResizeableMode(info.resizeMode);
- dragData = mInitialDragData.getItemAt(0).getIntent();
+ activityInfo = mInitialDragData.getItemAt(0).getActivityInfo();
+ // TODO: This should technically check & respect config_supportsNonResizableMultiWindow
+ dragItemSupportsSplitscreen = activityInfo == null
+ || ActivityInfo.isResizeableMode(activityInfo.resizeMode);
+ appData = mInitialDragData.getItemAt(0).getIntent();
+ launchableIntent = DragUtils.getLaunchIntent(mInitialDragData);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
index f7bcc9477aa1..24f8e186bf76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -36,8 +36,21 @@ public class DragUtils {
* Returns whether we can handle this particular drag.
*/
public static boolean canHandleDrag(DragEvent event) {
- return event.getClipData().getItemCount() > 0
- && (isAppDrag(event.getClipDescription()));
+ if (event.getClipData().getItemCount() <= 0) {
+ // No clip data, ignore this drag
+ return false;
+ }
+ if (isAppDrag(event.getClipDescription())) {
+ // Clip data contains an app drag initiated from SysUI, handle it
+ return true;
+ }
+ if (com.android.window.flags.Flags.delegateUnhandledDrags()
+ && getLaunchIntent(event) != null) {
+ // Clip data contains a launchable intent drag, handle it
+ return true;
+ }
+ // Otherwise ignore
+ return false;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 53dd981755d2..952e2d4b3b9a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -476,7 +476,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void goToFullscreenFromSplit() {
- mStageCoordinator.goToFullscreenFromSplit();
+ if (mStageCoordinator.isSplitActive()) {
+ mStageCoordinator.goToFullscreenFromSplit();
+ }
}
/** Move the specified task to fullscreen, regardless of focus state. */
@@ -806,6 +808,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1,
+ fillInIntent, position);
// Flag this as a no-user-action launch to prevent sending user leaving event to the current
// top activity since it's going to be put into another side of the split. This prevents the
// current top activity from going into pip mode due to user leaving event.
@@ -824,6 +829,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
.map(recentTasks -> recentTasks.findTaskInBackground(component, userId1))
.orElse(null);
if (taskInfo != null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Found suitable background task=%s", taskInfo);
if (ENABLE_SHELL_TRANSITIONS) {
mStageCoordinator.startTask(taskInfo.taskId, position, options);
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c01181b284dc..7a1595fdbf01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -3518,6 +3518,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(stageType, wct);
+ clearSplitPairedInRecents(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
mSplitTransitions.startDismissTransition(wct, StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
Log.w(TAG, splitFailureMessage("onNoLongerSupportMultiWindow",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index c70a8219489e..9130edfa9f26 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -470,10 +470,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
final float cornerRadius;
- if (a.hasRoundedCorners() && isTask) {
- // hasRoundedCorners is currently only enabled for tasks
+ if (a.hasRoundedCorners()) {
+ final int displayId = isTask ? change.getTaskInfo().displayId
+ : info.getRoot(TransitionUtil.rootIndexFor(change, info))
+ .getDisplayId();
final Context displayContext =
- mDisplayController.getDisplayContext(change.getTaskInfo().displayId);
+ mDisplayController.getDisplayContext(displayId);
cornerRadius = displayContext == null ? 0
: ScreenDecorationsUtils.getWindowCornerRadius(displayContext);
} else {
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 c1406d052195..caa894fcbbc7 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
@@ -22,8 +22,10 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
import static android.view.MotionEvent.ACTION_HOVER_EXIT;
+import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -59,7 +61,6 @@ import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
-import android.view.ViewConfiguration;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -321,8 +322,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final GestureDetector mGestureDetector;
private boolean mIsDragging;
+ private boolean mTouchscreenInUse;
private boolean mHasLongClicked;
- private boolean mShouldClick;
private int mDragPointerId = -1;
private final Runnable mCloseMaximizeWindowRunnable;
@@ -343,6 +344,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@Override
public void onClick(View v) {
+ if (mIsDragging) {
+ mIsDragging = false;
+ return;
+ }
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
if (id == R.id.close_window) {
@@ -421,6 +426,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@Override
public boolean onTouch(View v, MotionEvent e) {
final int id = v.getId();
+ if ((e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN) {
+ mTouchscreenInUse = e.getActionMasked() != ACTION_UP
+ && e.getActionMasked() != ACTION_CANCEL;
+ }
if (id != R.id.caption_handle && id != R.id.desktop_mode_caption
&& id != R.id.open_menu_button && id != R.id.close_window
&& id != R.id.maximize_window) {
@@ -432,31 +441,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (!mHasLongClicked && id != R.id.maximize_window) {
decoration.closeMaximizeMenuIfNeeded(e);
}
-
- final long eventDuration = e.getEventTime() - e.getDownTime();
- final boolean isTouchScreen =
- (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
- final boolean shouldLongClick = isTouchScreen && id == R.id.maximize_window
- && !mIsDragging && !mHasLongClicked
- && eventDuration >= ViewConfiguration.getLongPressTimeout();
- if (shouldLongClick) {
- v.performLongClick();
- mHasLongClicked = true;
- return true;
- }
-
return mDragDetector.onMotionEvent(v, e);
}
@Override
public boolean onLongClick(View v) {
final int id = v.getId();
- if (id == R.id.maximize_window) {
+ if (id == R.id.maximize_window && mTouchscreenInUse) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
moveTaskToFront(decoration.mTaskInfo);
if (decoration.isMaximizeMenuActive()) {
decoration.closeMaximizeMenu();
} else {
+ mHasLongClicked = true;
decoration.createMaximizeMenu();
}
return true;
@@ -515,11 +512,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (mGestureDetector.onTouchEvent(e)) {
return true;
}
- if (e.getActionMasked() == MotionEvent.ACTION_CANCEL) {
- // If a motion event is cancelled, reset mShouldClick so a click is not accidentally
- // performed.
- mShouldClick = false;
- }
+ final int id = v.getId();
+ final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window
+ || id == R.id.open_menu_button);
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDragPointerId = e.getPointerId(0);
@@ -527,12 +522,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
0 /* ctrlType */, e.getRawX(0),
e.getRawY(0));
mIsDragging = false;
- mShouldClick = true;
mHasLongClicked = false;
- return true;
+ // Do not consume input event if a button is touched, otherwise it would
+ // prevent the button's ripple effect from showing.
+ return !touchingButton;
}
case MotionEvent.ACTION_MOVE: {
- mShouldClick = false;
// If a decor's resize drag zone is active, don't also try to reposition it.
if (decoration.isHandlingDragResize()) break;
decoration.closeMaximizeMenu();
@@ -553,11 +548,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
case MotionEvent.ACTION_CANCEL: {
final boolean wasDragging = mIsDragging;
if (!wasDragging) {
- if (mShouldClick && v != null && !mHasLongClicked) {
- v.performClick();
- mShouldClick = false;
- return true;
- }
return false;
}
if (e.findPointerIndex(mDragPointerId) == -1) {
@@ -576,8 +566,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
newTaskBounds));
- mIsDragging = false;
- return true;
+ if (touchingButton && !mHasLongClicked) {
+ // We need the input event to not be consumed here to end the ripple
+ // effect on the touched button. We will reset drag state in the ensuing
+ // onClick call that results.
+ return false;
+ } else {
+ mIsDragging = false;
+ return true;
+ }
}
}
return true;
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index ac7380f83233..9a1bd267ea1f 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -206,6 +206,18 @@ class EnterPipToOtherOrientation(flicker: LegacyFlickerTest) : PipTransition(fli
}
}
+ @Presubmit
+ @Test
+ fun pipLayerRemainInsideVisibleBounds() {
+ // during the transition we assert the center point is within the display bounds, since it
+ // might go outside of bounds as we resize from landscape fullscreen to destination bounds,
+ // and once the animation is over we assert that it's fully within the display bounds, at
+ // which point the device also performs orientation change from landscape to portrait
+ flicker.assertLayersVisibleRegion(pipApp.or(ComponentNameMatcher.PIP_CONTENT_OVERLAY)) {
+ regionsCenterPointInside(startingBounds).then().coversAtMost(endingBounds)
+ }
+ }
+
/** {@inheritDoc} */
@FlakyTest(bugId = 267424412)
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 8c4711603904..32c070305e05 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -24,7 +24,10 @@ package {
android_test {
name: "WMShellUnitTests",
-
+ defaults: [
+ // For ExtendedMockito dependencies.
+ "modules-utils-testable-device-config-defaults",
+ ],
srcs: [
"**/*.java",
"**/*.kt",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
new file mode 100644
index 000000000000..4548fcb06c55
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.desktopmode
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.internal.util.FrameworkStatsLog
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.kotlin.eq
+
+/**
+ * Tests for [DesktopModeEventLogger].
+ */
+class DesktopModeEventLoggerTest {
+
+ private val desktopModeEventLogger = DesktopModeEventLogger()
+
+ @JvmField
+ @Rule
+ val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
+ .mockStatic(FrameworkStatsLog::class.java).build()!!
+
+ @Test
+ fun logSessionEnter_enterReason() = runBlocking {
+ desktopModeEventLogger.logSessionEnter(sessionId = SESSION_ID, EnterReason.UNKNOWN_ENTER)
+
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ /* event */
+ eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER),
+ /* enter_reason */
+ eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__UNKNOWN_ENTER),
+ /* exit_reason */
+ eq(0),
+ /* sessionId */
+ eq(SESSION_ID)
+ )
+ }
+ }
+
+ @Test
+ fun logSessionExit_exitReason() = runBlocking {
+ desktopModeEventLogger.logSessionExit(sessionId = SESSION_ID, ExitReason.UNKNOWN_EXIT)
+
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
+ /* event */
+ eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__EXIT),
+ /* enter_reason */
+ eq(0),
+ /* exit_reason */
+ eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__UNKNOWN_EXIT),
+ /* sessionId */
+ eq(SESSION_ID)
+ )
+ }
+ }
+
+ @Test
+ fun logTaskAdded_taskUpdate() = runBlocking {
+ desktopModeEventLogger.logTaskAdded(sessionId = SESSION_ID, TASK_UPDATE)
+
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ /* task_event */
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
+ /* instance_id */
+ eq(TASK_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_UPDATE.uid),
+ /* task_height */
+ eq(TASK_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_UPDATE.taskWidth),
+ /* task_x */
+ eq(TASK_UPDATE.taskX),
+ /* task_y */
+ eq(TASK_UPDATE.taskY),
+ /* session_id */
+ eq(SESSION_ID))
+ }
+ }
+
+ @Test
+ fun logTaskRemoved_taskUpdate() = runBlocking {
+ desktopModeEventLogger.logTaskRemoved(sessionId = SESSION_ID, TASK_UPDATE)
+
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ /* task_event */
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
+ /* instance_id */
+ eq(TASK_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_UPDATE.uid),
+ /* task_height */
+ eq(TASK_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_UPDATE.taskWidth),
+ /* task_x */
+ eq(TASK_UPDATE.taskX),
+ /* task_y */
+ eq(TASK_UPDATE.taskY),
+ /* session_id */
+ eq(SESSION_ID))
+ }
+ }
+
+ @Test
+ fun logTaskInfoChanged_taskUpdate() = runBlocking {
+ desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, TASK_UPDATE)
+
+ ExtendedMockito.verify {
+ FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+ /* task_event */
+ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+ /* instance_id */
+ eq(TASK_UPDATE.instanceId),
+ /* uid */
+ eq(TASK_UPDATE.uid),
+ /* task_height */
+ eq(TASK_UPDATE.taskHeight),
+ /* task_width */
+ eq(TASK_UPDATE.taskWidth),
+ /* task_x */
+ eq(TASK_UPDATE.taskX),
+ /* task_y */
+ eq(TASK_UPDATE.taskY),
+ /* session_id */
+ eq(SESSION_ID))
+ }
+ }
+
+ companion object {
+ private const val SESSION_ID = 1
+ private const val TASK_ID = 1
+ private const val TASK_UID = 1
+ private const val TASK_X = 0
+ private const val TASK_Y = 0
+ private const val TASK_HEIGHT = 100
+ private const val TASK_WIDTH = 100
+
+ private val TASK_UPDATE = TaskUpdate(
+ TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y
+ )
+ }
+} \ No newline at end of file
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 383621beca22..35c803b78674 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
@@ -781,6 +781,23 @@ class DesktopTasksControllerTest : ShellTestCase() {
)
}
+ @Test
+ fun moveFocusedTaskToFullscreen() {
+ val task1 = setUpFreeformTask()
+ val task2 = setUpFreeformTask()
+ val task3 = setUpFreeformTask()
+
+ task1.isFocused = false
+ task2.isFocused = true
+ task3.isFocused = false
+
+ controller.enterFullscreen(DEFAULT_DISPLAY)
+
+ val wct = getLatestExitDesktopWct()
+ assertThat(wct.changes[task2.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+ }
+
private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFreeformTask(displayId)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 1b347e01888e..5dd9d8a859d6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -22,9 +22,11 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
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.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -46,6 +48,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.quality.Strictness.LENIENT;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -61,6 +65,7 @@ import android.content.res.Resources;
import android.graphics.Insets;
import android.os.RemoteException;
import android.view.DisplayInfo;
+import android.view.DragEvent;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -70,12 +75,15 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.startingsurface.TaskSnapshotWindow;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
import java.util.ArrayList;
import java.util.Collections;
@@ -107,6 +115,7 @@ public class DragAndDropPolicyTest extends ShellTestCase {
private DragAndDropPolicy mPolicy;
private ClipData mActivityClipData;
+ private ClipData mLaunchableIntentClipData;
private ClipData mNonResizeableActivityClipData;
private ClipData mTaskClipData;
private ClipData mShortcutClipData;
@@ -115,9 +124,16 @@ public class DragAndDropPolicyTest extends ShellTestCase {
private ActivityManager.RunningTaskInfo mFullscreenAppTask;
private ActivityManager.RunningTaskInfo mNonResizeableFullscreenAppTask;
+ private MockitoSession mMockitoSession;
+
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
+ mMockitoSession = mockitoSession()
+ .strictness(LENIENT)
+ .mockStatic(DragUtils.class)
+ .startMocking();
+ when(DragUtils.canHandleDrag(any())).thenReturn(true);
Resources res = mock(Resources.class);
Configuration config = new Configuration();
@@ -134,11 +150,12 @@ public class DragAndDropPolicyTest extends ShellTestCase {
mInsets = Insets.of(0, 0, 0, 0);
mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter));
- mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
- mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
+ mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
+ mLaunchableIntentClipData = createIntentClipData();
+ mNonResizeableActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
setClipDataResizeable(mNonResizeableActivityClipData, false);
- mTaskClipData = createClipData(MIMETYPE_APPLICATION_TASK);
- mShortcutClipData = createClipData(MIMETYPE_APPLICATION_SHORTCUT);
+ mTaskClipData = createAppClipData(MIMETYPE_APPLICATION_TASK);
+ mShortcutClipData = createAppClipData(MIMETYPE_APPLICATION_SHORTCUT);
mHomeTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
mFullscreenAppTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
@@ -149,10 +166,15 @@ public class DragAndDropPolicyTest extends ShellTestCase {
setRunningTask(mFullscreenAppTask);
}
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
/**
- * Creates a clip data that is by default resizeable.
+ * Creates an app-based clip data that is by default resizeable.
*/
- private ClipData createClipData(String mimeType) {
+ private ClipData createAppClipData(String mimeType) {
ClipDescription clipDescription = new ClipDescription(mimeType, new String[] { mimeType });
Intent i = new Intent();
switch (mimeType) {
@@ -164,7 +186,9 @@ public class DragAndDropPolicyTest extends ShellTestCase {
i.putExtra(Intent.EXTRA_TASK_ID, 12345);
break;
case MIMETYPE_APPLICATION_ACTIVITY:
- i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, mock(PendingIntent.class));
+ final PendingIntent pi = mock(PendingIntent.class);
+ doReturn(android.os.Process.myUserHandle()).when(pi).getCreatorUserHandle();
+ i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, pi);
break;
}
i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
@@ -175,6 +199,22 @@ public class DragAndDropPolicyTest extends ShellTestCase {
return data;
}
+ /**
+ * Creates an intent-based clip data that is by default resizeable.
+ */
+ private ClipData createIntentClipData() {
+ ClipDescription clipDescription = new ClipDescription("Intent",
+ new String[] { MIMETYPE_TEXT_INTENT });
+ PendingIntent intent = mock(PendingIntent.class);
+ when(intent.getCreatorUserHandle()).thenReturn(android.os.Process.myUserHandle());
+ ClipData.Item item = new ClipData.Item.Builder()
+ .setIntentSender(intent.getIntentSender())
+ .build();
+ ClipData data = new ClipData(clipDescription, item);
+ when(DragUtils.getLaunchIntent((ClipData) any())).thenReturn(intent);
+ return data;
+ }
+
private ActivityManager.RunningTaskInfo createTaskInfo(int winMode, int actType) {
ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
info.configuration.windowConfiguration.setActivityType(actType);
@@ -204,58 +244,85 @@ public class DragAndDropPolicyTest extends ShellTestCase {
@Test
public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() {
+ dragOverFullscreenHome_expectOnlyFullscreenTarget(mActivityClipData);
+ }
+
+ @Test
+ public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
+ dragOverFullscreenApp_expectSplitScreenTargets(mActivityClipData);
+ }
+
+ @Test
+ public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+ dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(mActivityClipData);
+ }
+
+ @Test
+ public void testDragIntentOverFullscreenHome_expectOnlyFullscreenTarget() {
+ dragOverFullscreenHome_expectOnlyFullscreenTarget(mLaunchableIntentClipData);
+ }
+
+ @Test
+ public void testDragIntentOverFullscreenApp_expectSplitScreenTargets() {
+ dragOverFullscreenApp_expectSplitScreenTargets(mLaunchableIntentClipData);
+ }
+
+ @Test
+ public void testDragIntentOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+ dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(mLaunchableIntentClipData);
+ }
+
+ private void dragOverFullscreenHome_expectOnlyFullscreenTarget(ClipData data) {
doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mHomeTask);
- DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
- mLandscapeDisplayLayout, mActivityClipData);
+ DragSession dragSession = new DragSession(mActivityTaskManager,
+ mLandscapeDisplayLayout, data);
dragSession.update();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN));
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_UNDEFINED), any());
}
- @Test
- public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
+ private void dragOverFullscreenApp_expectSplitScreenTargets(ClipData data) {
doReturn(true).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mFullscreenAppTask);
- DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
- mLandscapeDisplayLayout, mActivityClipData);
+ DragSession dragSession = new DragSession(mActivityTaskManager,
+ mLandscapeDisplayLayout, data);
dragSession.update();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT));
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_TOP_OR_LEFT), any());
reset(mSplitScreenStarter);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT));
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
}
- @Test
- public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
+ private void dragOverFullscreenAppPhone_expectVerticalSplitScreenTargets(ClipData data) {
doReturn(false).when(mSplitScreenStarter).isLeftRightSplit();
setRunningTask(mFullscreenAppTask);
- DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
- mPortraitDisplayLayout, mActivityClipData);
+ DragSession dragSession = new DragSession(mActivityTaskManager,
+ mPortraitDisplayLayout, data);
dragSession.update();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP));
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_TOP_OR_LEFT), any());
reset(mSplitScreenStarter);
- mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
+ mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM));
verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
}
@@ -263,7 +330,7 @@ public class DragAndDropPolicyTest extends ShellTestCase {
@Test
public void testTargetHitRects() {
setRunningTask(mFullscreenAppTask);
- DragSession dragSession = new DragSession(mContext, mActivityTaskManager,
+ DragSession dragSession = new DragSession(mActivityTaskManager,
mLandscapeDisplayLayout, mActivityClipData);
dragSession.update();
mPolicy.start(dragSession, mLoggerSessionId);
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index d21f07efe36a..b12486e32bd4 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -26,6 +26,7 @@ X(ClipPath)
X(ClipRect)
X(ClipRRect)
X(ClipRegion)
+X(ClipShader)
X(ResetClip)
X(DrawPaint)
X(DrawBehind)
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 3b694c5d399b..54aef55f8b90 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -207,6 +207,13 @@ struct ClipRegion final : Op {
SkClipOp op;
void draw(SkCanvas* c, const SkMatrix&) const { c->clipRegion(region, op); }
};
+struct ClipShader final : Op {
+ static const auto kType = Type::ClipShader;
+ ClipShader(const sk_sp<SkShader>& shader, SkClipOp op) : shader(shader), op(op) {}
+ sk_sp<SkShader> shader;
+ SkClipOp op;
+ void draw(SkCanvas* c, const SkMatrix&) const { c->clipShader(shader, op); }
+};
struct ResetClip final : Op {
static const auto kType = Type::ResetClip;
ResetClip() {}
@@ -822,6 +829,9 @@ void DisplayListData::clipRRect(const SkRRect& rrect, SkClipOp op, bool aa) {
void DisplayListData::clipRegion(const SkRegion& region, SkClipOp op) {
this->push<ClipRegion>(0, region, op);
}
+void DisplayListData::clipShader(const sk_sp<SkShader>& shader, SkClipOp op) {
+ this->push<ClipShader>(0, shader, op);
+}
void DisplayListData::resetClip() {
this->push<ResetClip>(0);
}
@@ -1134,6 +1144,11 @@ void RecordingCanvas::onClipRegion(const SkRegion& region, SkClipOp op) {
fDL->clipRegion(region, op);
this->INHERITED::onClipRegion(region, op);
}
+void RecordingCanvas::onClipShader(sk_sp<SkShader> shader, SkClipOp op) {
+ setClipMayBeComplex();
+ fDL->clipShader(shader, op);
+ this->INHERITED::onClipShader(shader, op);
+}
void RecordingCanvas::onResetClip() {
// This is part of "replace op" emulation, but rely on the following intersection
// clip to potentially mark the clip as complex. If we are already complex, we do
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index afadbfda7471..965264f31119 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -140,6 +140,7 @@ private:
void translateZ(SkScalar);
void clipPath(const SkPath&, SkClipOp, bool aa);
+ void clipShader(const sk_sp<SkShader>& shader, SkClipOp);
void clipRect(const SkRect&, SkClipOp, bool aa);
void clipRRect(const SkRRect&, SkClipOp, bool aa);
void clipRegion(const SkRegion&, SkClipOp);
@@ -216,6 +217,7 @@ public:
void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override;
void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override;
void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override;
+ void onClipShader(sk_sp<SkShader>, SkClipOp) override;
void onClipRegion(const SkRegion&, SkClipOp) override;
void onResetClip() override;
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index f84107e8792c..f9dc5fac7e21 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -41,6 +41,8 @@ namespace android {
namespace {
+static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
+
const ui::Transform kIdentityTransform;
} // namespace
@@ -224,7 +226,7 @@ void PointerController::setPresentation(Presentation presentation) {
mLocked.presentation = presentation;
- if (input_flags::enable_pointer_choreographer()) {
+ if (ENABLE_POINTER_CHOREOGRAPHER) {
// When pointer choreographer is enabled, the presentation mode is only set once when the
// PointerController is constructed, before the display viewport is provided.
// TODO(b/293587049): Clean up the PointerController interface after pointer choreographer
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index ea136edf46be..c90c44152440 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1079,7 +1079,7 @@ public final class AudioFormat implements Parcelable {
* @return one of the values that can be set in {@link Builder#setEncoding(int)} or
* {@link AudioFormat#ENCODING_INVALID} if not set.
*/
- public @Encoding int getEncoding() {
+ public @EncodingCanBeInvalid int getEncoding() {
return mEncoding;
}
@@ -1486,6 +1486,44 @@ public final class AudioFormat implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface Encoding {}
+ /** @hide same as @Encoding, but adding ENCODING_INVALID */
+ @IntDef(flag = false, prefix = "ENCODING", value = {
+ ENCODING_INVALID,
+ ENCODING_DEFAULT,
+ ENCODING_PCM_16BIT,
+ ENCODING_PCM_8BIT,
+ ENCODING_PCM_FLOAT,
+ ENCODING_AC3,
+ ENCODING_E_AC3,
+ ENCODING_DTS,
+ ENCODING_DTS_HD,
+ ENCODING_MP3,
+ ENCODING_AAC_LC,
+ ENCODING_AAC_HE_V1,
+ ENCODING_AAC_HE_V2,
+ ENCODING_IEC61937,
+ ENCODING_DOLBY_TRUEHD,
+ ENCODING_AAC_ELD,
+ ENCODING_AAC_XHE,
+ ENCODING_AC4,
+ ENCODING_E_AC3_JOC,
+ ENCODING_DOLBY_MAT,
+ ENCODING_OPUS,
+ ENCODING_PCM_24BIT_PACKED,
+ ENCODING_PCM_32BIT,
+ ENCODING_MPEGH_BL_L3,
+ ENCODING_MPEGH_BL_L4,
+ ENCODING_MPEGH_LC_L3,
+ ENCODING_MPEGH_LC_L4,
+ ENCODING_DTS_UHD_P1,
+ ENCODING_DRA,
+ ENCODING_DTS_HD_MA,
+ ENCODING_DTS_UHD_P2,
+ ENCODING_DSD }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EncodingCanBeInvalid {}
+
/** @hide */
public static final int[] SURROUND_SOUND_ENCODING = {
ENCODING_AC3,
diff --git a/media/java/android/media/RingtoneSelection.java b/media/java/android/media/RingtoneSelection.java
deleted file mode 100644
index b7c372182464..000000000000
--- a/media/java/android/media/RingtoneSelection.java
+++ /dev/null
@@ -1,742 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media;
-
-import static java.util.Objects.requireNonNull;
-
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.TestApi;
-import android.content.ContentProvider;
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.os.vibrator.Flags;
-import android.provider.MediaStore;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * Immutable representation a desired ringtone, usually originating from a user preference.
- * Unlike sound-only Uris, a "silent" setting is an explicit selection value, rather than null.
- *
- * <p>This representation can be converted into (or from) a URI form for storing within a string
- * preference or when using the ringtone picker via {@link RingtoneManager#ACTION_RINGTONE_PICKER}.
- * It does not carry any actual media data - it only references the components that make
- * up the preference. Initial selections can be built using {@link RingtoneSelection.Builder}.
- *
- * <p>A RingtoneSelection is typically played by passing into a {@link Ringtone.Builder}, and
- * supplementing with contextual defaults from the application. Bad Uris are handled by the
- * {@link Ringtone} class - the RingtoneSelection doesn't validate the target of the Uri.
- *
- * <p>When a RingtoneSelection is created/loaded, the values of its properties are modified
- * to be internally consistent and reflect effective values - with the exception of not verifying
- * the actual URI content. For example, loading a selection Uri that sets a sound source to
- * {@link #SOUND_SOURCE_URI}, but doesn't also have a sound Uri set, will result in this class
- * instead returning {@link #SOUND_SOURCE_UNSPECIFIED} from {@link #getSoundSource}.
- *
- * <h2>Storing preferences</h2>
- *
- * <p>A ringtone preference can have several states: either unset, set to a ringtone selection Uri,
- * or, from prior to the introduction of {@code RingtoneSelection}, set to a sound-only Uri or
- * explicitly set to null to indicate silent.
- *
- * @hide
- */
-@TestApi
-@FlaggedApi(Flags.FLAG_HAPTICS_CUSTOMIZATION_ENABLED)
-public final class RingtoneSelection {
-
- /**
- * The sound source was specified but its value was not recognized. This value is used
- * internally for not stripping unrecognised (possibly future) values during processing.
- * @hide
- */
- public static final int SOUND_SOURCE_UNKNOWN = -1;
-
- /**
- * The sound source is not explicitly specified, so it can follow default behavior for its
- * context.
- */
- public static final int SOUND_SOURCE_UNSPECIFIED = 0;
-
- /**
- * Sound is explicitly disabled, such as the user having selected "Silent" in the sound picker.
- */
- public static final int SOUND_SOURCE_OFF = 1;
-
- /**
- * The sound Uri should be used as the source of sound.
- */
- public static final int SOUND_SOURCE_URI = 2;
-
- /**
- * The sound should explicitly use the system default.
- *
- * <p>This value isn't valid within the system default itself.
- */
- public static final int SOUND_SOURCE_SYSTEM_DEFAULT = 3;
-
- // Note: Value 4 reserved for possibility of SOURCE_SOURCE_APPLICATION_DEFAULT.
-
- /**
- * Directive for how to make sound.
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "SOUND_SOURCE_", value = {
- SOUND_SOURCE_UNKNOWN,
- SOUND_SOURCE_UNSPECIFIED,
- SOUND_SOURCE_OFF,
- SOUND_SOURCE_URI,
- SOUND_SOURCE_SYSTEM_DEFAULT,
- })
- public @interface SoundSource {}
-
- /**
- * The vibration source was specified but its value was not recognized.
- * This value is used internally for not stripping unrecognised (possibly
- * future) values during processing.
- * @hide
- */
- public static final int VIBRATION_SOURCE_UNKNOWN = -1;
-
- /**
- * Vibration source is not explicitly specified. If vibration is enabled, this will use the
- * first available of {@link #VIBRATION_SOURCE_AUDIO_CHANNEL},
- * {@link #VIBRATION_SOURCE_APPLICATION_DEFAULT}, or {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
- */
- public static final int VIBRATION_SOURCE_UNSPECIFIED = 0;
-
- /** Specifies that vibration is explicitly disabled for this ringtone. */
- public static final int VIBRATION_SOURCE_OFF = 1;
-
- /** The vibration Uri should be used as the source of vibration. */
- public static final int VIBRATION_SOURCE_URI = 2;
-
- /**
- * The vibration should explicitly use the system default.
- *
- * <p>This value isn't valid within the system default itself.
- */
- public static final int VIBRATION_SOURCE_SYSTEM_DEFAULT = 3;
-
- /**
- * Specifies that vibration should use the vibration provided by the application. This is
- * typically the application's own default for the use-case, provided via
- * {@link Ringtone.Builder#setVibrationEffect}. For notification channels, this is the vibration
- * effect saved on the notification channel.
- *
- * <p>If no vibration is specified by the application, this value behaves if the source was
- * {@link #VIBRATION_SOURCE_UNSPECIFIED}.
- *
- * <p>This value isn't valid within the system default.
- */
- public static final int VIBRATION_SOURCE_APPLICATION_DEFAULT = 4;
-
- /**
- * Specifies that vibration should use haptic audio channels from the
- * sound Uri. If the sound URI doesn't have haptic channels, then reverts to the order specified
- * by {@link #VIBRATION_SOURCE_UNSPECIFIED}.
- */
- // Numeric gap from VIBRATION_SOURCE_APPLICATION_DEFAULT in case we want other common elements.
- public static final int VIBRATION_SOURCE_AUDIO_CHANNEL = 10;
-
- /**
- * Specifies that vibration should generate haptic audio channels from the
- * audio tracks of the sound Uri.
- *
- * If the sound Uri already has haptic channels, then behaves as though
- * {@link #VIBRATION_SOURCE_AUDIO_CHANNEL} was specified instead.
- */
- public static final int VIBRATION_SOURCE_HAPTIC_GENERATOR = 11;
-
- /**
- * Directive for how to vibrate.
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "VIBRATION_SOURCE_", value = {
- VIBRATION_SOURCE_UNKNOWN,
- VIBRATION_SOURCE_UNSPECIFIED,
- VIBRATION_SOURCE_OFF,
- VIBRATION_SOURCE_URI,
- VIBRATION_SOURCE_APPLICATION_DEFAULT,
- VIBRATION_SOURCE_AUDIO_CHANNEL,
- VIBRATION_SOURCE_HAPTIC_GENERATOR,
- })
- public @interface VibrationSource {}
-
- /**
- * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as the sound Uri
- * for the returned {@link RingtoneSelection}, with null meaning {@link #SOUND_SOURCE_OFF},
- * and symbolic default URIs ({@link RingtoneManager#getDefaultUri}) meaning
- * {@link #SOUND_SOURCE_SYSTEM_DEFAULT}.
- *
- * <p>This behavior is particularly suited to loading values from older settings that may
- * contain a raw sound Uri or null for silent.
- *
- * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false.
- */
- public static final int FROM_URI_RINGTONE_SELECTION_OR_SOUND = 1;
-
- /**
- * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as the vibration
- * Uri for the returned {@link RingtoneSelection}, with null meaning
- * {@link #VIBRATION_SOURCE_OFF} and symbolic default URIs
- * ({@link RingtoneManager#getDefaultUri}) meaning {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
- *
- * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false.
- */
- public static final int FROM_URI_RINGTONE_SELECTION_OR_VIBRATION = 2;
-
- /**
- * Configures {@link #RingtoneSelection#fromUri} to treat an unrecognized Uri as an invalid
- * value. Null or an invalid values will revert to default behavior correspnoding to
- * {@link #DEFAULT_SELECTION_URI_STRING}. Symbolic default URIs
- * ({@link RingtoneManager#getDefaultUri}) will set both
- * {@link #SOUND_SOURCE_SYSTEM_DEFAULT} and {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
- *
- * <p>An unrecognized Uri is one for which {@link #isRingtoneSelectionUri(Uri)} returns false,
- * which include {@code null}.
- */
- public static final int FROM_URI_RINGTONE_SELECTION_ONLY = 3;
-
- /**
- * How to treat values in {@link #fromUri}.
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "FROM_URI_", value = {
- FROM_URI_RINGTONE_SELECTION_OR_SOUND,
- FROM_URI_RINGTONE_SELECTION_OR_VIBRATION,
- FROM_URI_RINGTONE_SELECTION_ONLY
- })
- public @interface FromUriBehavior {}
-
- private static final String BASE_RINGTONE_URI = "content://media/ringtone";
- /**
- * String representation of a RingtoneSelection Uri that says to use defaults (equivalent
- * to {@code new RingtoneSelection.Builder().build()}).
- */
- public static final String DEFAULT_SELECTION_URI_STRING = BASE_RINGTONE_URI;
-
- private static final String MEDIA_URI_RINGTONE_PATH = "/ringtone";
-
- /* Query param keys. */
- private static final String URI_PARAM_SOUND_URI = "su";
- private static final String URI_PARAM_SOUND_SOURCE = "ss";
- private static final String URI_PARAM_VIBRATION_URI = "vu";
- private static final String URI_PARAM_VIBRATION_SOURCE = "vs";
-
- /* Common param values */
- private static final String SOURCE_OFF_STRING = "off";
- private static final String SOURCE_SYSTEM_DEFAULT_STRING = "sys";
-
- /* Vibration source param values. */
- private static final String VIBRATION_SOURCE_AUDIO_CHANNEL_STRING = "ac";
- private static final String VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING = "app";
- private static final String VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING = "hg";
-
- @Nullable
- private final Uri mSoundUri;
- @SoundSource
- private final int mSoundSource;
-
- @Nullable
- private final Uri mVibrationUri;
- @VibrationSource
- private final int mVibrationSource;
-
- private RingtoneSelection(@Nullable Uri soundUri, @SoundSource int soundSource,
- @Nullable Uri vibrationUri, @VibrationSource int vibrationSource) {
- // Enforce guarantees on the source values: revert to unspecified if they depend on
- // something that's not set.
- //
- // The non-public "unknown" value can't appear in a getter result, it's just a reserved
- // "null" value and should be treated the same as an unrecognized value. This can be seen
- // in Uri parsing. For this and other unrecognized values, we either revert them to the URI
- // source, if a Uri was included, or the "unspecified" source otherwise. This can be
- // seen in action in the Uri parsing.
- //
- // The "unspecified" source is a public value meaning that there is no specific
- // behavior indicated, and the defaults and fallbacks should be applied. For example, an
- // vibration source value of "system default" means to explicitly use the system default
- // vibration. However, an "unspecified" vibration source will first see if audio coupled
- // or application-default vibrations are available.
- mSoundSource = switch (soundSource) {
- // Supported explicit values that don't have a Uri.
- case SOUND_SOURCE_OFF, SOUND_SOURCE_UNSPECIFIED, SOUND_SOURCE_SYSTEM_DEFAULT ->
- soundSource;
- // Uri and unknown/unrecognized values: use a Uri if one is present, else revert to
- // unspecified.
- default ->
- soundUri != null ? SOUND_SOURCE_URI : SOUND_SOURCE_UNSPECIFIED;
- };
- mVibrationSource = switch (vibrationSource) {
- // Enforce vibration sources that require a sound Uri.
- case VIBRATION_SOURCE_AUDIO_CHANNEL, VIBRATION_SOURCE_HAPTIC_GENERATOR ->
- soundUri != null ? vibrationSource : VIBRATION_SOURCE_UNSPECIFIED;
- // Supported explicit values that don't rely on any Uri.
- case VIBRATION_SOURCE_OFF, VIBRATION_SOURCE_UNSPECIFIED,
- VIBRATION_SOURCE_SYSTEM_DEFAULT, VIBRATION_SOURCE_APPLICATION_DEFAULT ->
- vibrationSource;
- // Uri and unknown/unrecognized values: use a Uri if one is present, else revert to
- // unspecified.
- default ->
- vibrationUri != null ? VIBRATION_SOURCE_URI : VIBRATION_SOURCE_UNSPECIFIED;
- };
- // Clear Uri values if they're un-used by the source.
- mSoundUri = mSoundSource == SOUND_SOURCE_URI ? soundUri : null;
- mVibrationUri = mVibrationSource == VIBRATION_SOURCE_URI ? vibrationUri : null;
- }
-
- /**
- * Returns the stored sound behavior.
- */
- @SoundSource
- public int getSoundSource() {
- return mSoundSource;
- }
-
- /**
- * Returns the sound Uri for this selection. This is guaranteed to be non-null if
- * {@link #getSoundSource} returns {@link #SOUND_SOURCE_URI}.
- */
- @Nullable
- public Uri getSoundUri() {
- return mSoundUri;
- }
-
- /**
- * Returns the selected vibration behavior.
- */
- @VibrationSource
- public int getVibrationSource() {
- return mVibrationSource;
- }
-
- /**
- * Returns the vibration Uri for this selection. This is guaranteed to be non-null if
- * {@link #getVibrationSource} returns {@link #SOUND_SOURCE_URI}.
- */
- @Nullable
- public Uri getVibrationUri() {
- return mVibrationUri;
- }
-
- /**
- * Converts the ringtone selection into a Uri-form, suitable for storing as a user preference
- * or returning as a result.
- */
- @NonNull
- public Uri toUri() {
- Uri.Builder builder = new Uri.Builder()
- .scheme(ContentResolver.SCHEME_CONTENT)
- .authority(MediaStore.AUTHORITY)
- .path(MEDIA_URI_RINGTONE_PATH);
- if (mSoundUri != null) {
- builder.appendQueryParameter(URI_PARAM_SOUND_URI, mSoundUri.toString());
- }
- // Only off is explicit for sound sources
- String soundSourceStr = soundSourceToString(mSoundSource);
- if (soundSourceStr != null) {
- builder.appendQueryParameter(URI_PARAM_SOUND_SOURCE, soundSourceStr);
- }
- if (mVibrationUri != null) {
- builder.appendQueryParameter(URI_PARAM_VIBRATION_URI, mVibrationUri.toString());
- }
- String vibrationSourceStr = vibrationSourceToString(mVibrationSource);
- if (vibrationSourceStr != null) {
- builder.appendQueryParameter(URI_PARAM_VIBRATION_SOURCE, vibrationSourceStr);
- }
- return builder.build();
- }
-
- /**
- * Returns true if the Uri is an encoded {@link RingtoneSelection}. This method doesn't
- * validate the parameters of the selection.
- *
- * @see #fromUri
- * @see #toUri
- */
- public static boolean isRingtoneSelectionUri(@Nullable Uri uri) {
- if (uri == null) {
- return false;
- }
- // Any URI content://media/ringtone
- return ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())
- && MediaStore.AUTHORITY.equals(
- ContentProvider.getAuthorityWithoutUserId(uri.getAuthority()))
- && MEDIA_URI_RINGTONE_PATH.equals(uri.getPath());
- }
-
- /**
- * Strip the specified userId from internal Uris. Non-stripped userIds will typically be
- * for work profiles referencing system ringtones from the host user.
- *
- * This is only for use in RingtoneManager.
- * @hide
- */
- @VisibleForTesting
- public RingtoneSelection getWithoutUserId(int userIdToStrip) {
- if (mSoundSource != SOUND_SOURCE_URI && mVibrationSource != VIBRATION_SOURCE_URI) {
- return this;
- }
-
- // Ok if uri is null. We only replace explicit references to the specified (current) userId.
- int soundUserId = ContentProvider.getUserIdFromUri(mSoundUri, UserHandle.USER_NULL);
- int vibrationUserId = ContentProvider.getUserIdFromUri(mVibrationUri, UserHandle.USER_NULL);
- boolean needToChangeSound =
- soundUserId != UserHandle.USER_NULL && soundUserId == userIdToStrip;
- boolean needToChangeVibration =
- vibrationUserId != UserHandle.USER_NULL && vibrationUserId == userIdToStrip;
-
- // Anything to do?
- if (!needToChangeSound && !needToChangeVibration) {
- return this;
- }
-
- RingtoneSelection.Builder updated = new Builder(this);
- // The relevant uris can't be null, because they contain userIdToStrip.
- if (needToChangeSound) {
- // mSoundUri is not null, so the result of getUriWithoutUserId won't be null.
- updated.setSoundSource(ContentProvider.getUriWithoutUserId(mSoundUri));
- }
- if (needToChangeVibration) {
- updated.setVibrationSource(ContentProvider.getUriWithoutUserId(mVibrationUri));
- }
- return updated.build();
- }
-
- @Override
- public String toString() {
- return toUri().toString();
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (!(o instanceof RingtoneSelection other)) {
- return false;
- }
- return this.mSoundSource == other.mSoundSource
- && this.mVibrationSource == other.mVibrationSource
- && Objects.equals(this.mSoundUri, other.mSoundUri)
- && Objects.equals(this.mVibrationUri, other.mVibrationUri);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mSoundSource, mVibrationSource, mSoundUri, mVibrationUri);
- }
-
- /**
- * Converts a Uri into a RingtoneSelection.
- *
- * <p>Null values, Uris that {@link #isRingtoneSelectionUri(Uri)} returns false (except for
- * old-style symbolic default Uris {@link RingtoneManager#getDefaultUri}) will be treated
- * according to the behaviour specified by the {@code unrecognizedValueBehavior} parameter.
- *
- * <p>Symbolic default Uris (where {@link RingtoneManager#getDefaultType(Uri)} returns -1,
- * will map sources to {@link #SOUND_SOURCE_SYSTEM_DEFAULT} and
- * {@link #VIBRATION_SOURCE_SYSTEM_DEFAULT}.
- *
- * @param uri The Uri to convert, potentially null.
- * @param unrecognizedValueBehavior indicates how to treat values for which
- * {@link #isRingtoneSelectionUri(Uri)} returns false (including null).
- * @return the RingtoneSelection represented by the given uri.
- */
- @NonNull
- public static RingtoneSelection fromUri(@Nullable Uri uri,
- @FromUriBehavior int unrecognizedValueBehavior) {
- if (isRingtoneSelectionUri(uri)) {
- return parseRingtoneSelectionUri(uri);
- }
- // Old symbolic default URIs map to the sources suggested by the unrecognized behavior.
- // It doesn't always map to both sources because the app may have its own default behavior
- // to apply, so non-primary sources are left as unspecified, which will revert to the
- // system default in the absence of an app default.
- boolean isDefaultUri = RingtoneManager.getDefaultType(uri) > 0;
- RingtoneSelection.Builder builder = new RingtoneSelection.Builder();
- switch (unrecognizedValueBehavior) {
- case FROM_URI_RINGTONE_SELECTION_ONLY:
- if (isDefaultUri) {
- builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT);
- builder.setVibrationSource(VIBRATION_SOURCE_SYSTEM_DEFAULT);
- }
- // Always return unspecified (defaults) for unrecognized ringtone selection Uris.
- return builder.build();
- case FROM_URI_RINGTONE_SELECTION_OR_SOUND:
- if (uri == null) {
- return builder.setSoundSource(SOUND_SOURCE_OFF).build();
- } else if (isDefaultUri) {
- return builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT).build();
- } else {
- return builder.setSoundSource(uri).build();
- }
- case FROM_URI_RINGTONE_SELECTION_OR_VIBRATION:
- if (uri == null) {
- return builder.setVibrationSource(VIBRATION_SOURCE_OFF).build();
- } else if (isDefaultUri) {
- return builder.setVibrationSource(VIBRATION_SOURCE_SYSTEM_DEFAULT).build();
- } else {
- // Unlike sound, there's no legacy settings alias uri for the default.
- return builder.setVibrationSource(uri).build();
- }
- default:
- throw new IllegalArgumentException("Unknown behavior parameter: "
- + unrecognizedValueBehavior);
- }
- }
-
- /**
- * Parses the Uri, which has already been checked for {@link #isRingtoneSelectionUri(Uri)}.
- * As a special case to be more compatible, if the RingtoneSelection has a userId specified in
- * the authority, then this is pushed down into the sound and vibration Uri, as the selection
- * itself is agnostic.
- */
- @NonNull
- private static RingtoneSelection parseRingtoneSelectionUri(@NonNull Uri uri) {
- RingtoneSelection.Builder builder = new RingtoneSelection.Builder();
- int soundSource = stringToSoundSource(uri.getQueryParameter(URI_PARAM_SOUND_SOURCE));
- int vibrationSource = stringToVibrationSource(
- uri.getQueryParameter(URI_PARAM_VIBRATION_SOURCE));
- Uri soundUri = getParamAsUri(uri, URI_PARAM_SOUND_URI);
- Uri vibrationUri = getParamAsUri(uri, URI_PARAM_VIBRATION_URI);
-
- // Add userId if necessary. This won't override an existing one in the sound/vib Uris.
- int userIdFromAuthority = ContentProvider.getUserIdFromAuthority(
- uri.getAuthority(), UserHandle.USER_NULL);
- if (userIdFromAuthority != UserHandle.USER_NULL) {
- // Won't override existing user id.
- if (soundUri != null) {
- soundUri = ContentProvider.maybeAddUserId(soundUri, userIdFromAuthority);
- }
- if (vibrationUri != null) {
- vibrationUri = ContentProvider.maybeAddUserId(vibrationUri, userIdFromAuthority);
- }
- }
-
- // Set sound uri if present, but map system default Uris to the system default source.
- if (soundUri != null) {
- if (RingtoneManager.getDefaultType(uri) >= 0) {
- builder.setSoundSource(SOUND_SOURCE_SYSTEM_DEFAULT);
- } else {
- builder.setSoundSource(soundUri);
- }
- }
- if (vibrationUri != null) {
- builder.setVibrationSource(vibrationUri);
- }
-
- // Don't set the source if there's a URI and the source is default, because that will
- // override the Uri source set above. In effect, we prioritise "explicit" sources over
- // an implicit Uri source - except for "default", which isn't really explicit.
- if (soundSource != SOUND_SOURCE_UNSPECIFIED || soundUri == null) {
- builder.setSoundSource(soundSource);
- }
- if (vibrationSource != VIBRATION_SOURCE_UNSPECIFIED || vibrationUri == null) {
- builder.setVibrationSource(vibrationSource);
- }
- return builder.build();
- }
-
- @Nullable
- private static Uri getParamAsUri(@NonNull Uri uri, String param) {
- // This returns the uri-decoded value, no need to further decode.
- String value = uri.getQueryParameter(param);
- if (value == null) {
- return null;
- }
- return Uri.parse(value);
- }
-
- /**
- * Converts the {@link SoundSource} to the uri query param value for it, or null
- * if the sound source is default, unknown, or implicit (uri).
- */
- @Nullable
- private static String soundSourceToString(@SoundSource int soundSource) {
- return switch (soundSource) {
- case SOUND_SOURCE_OFF -> SOURCE_OFF_STRING;
- case SOUND_SOURCE_SYSTEM_DEFAULT -> SOURCE_SYSTEM_DEFAULT_STRING;
- default -> null;
- };
- }
-
- /**
- * Returns the sound source int corresponding to the query string value. Returns
- * {@link #SOUND_SOURCE_UNKNOWN} if the value isn't recognised, and
- * {@link #SOUND_SOURCE_UNSPECIFIED} if the value is {@code null} (not in the Uri).
- */
- @SoundSource
- private static int stringToSoundSource(@Nullable String soundSource) {
- if (soundSource == null) {
- return SOUND_SOURCE_UNSPECIFIED;
- }
- return switch (soundSource) {
- case SOURCE_OFF_STRING -> SOUND_SOURCE_OFF;
- case SOURCE_SYSTEM_DEFAULT_STRING -> SOUND_SOURCE_SYSTEM_DEFAULT;
- default -> SOUND_SOURCE_UNKNOWN;
- };
- }
-
- /**
- * Converts the {@code vibrationSource} to the uri query param value for it, or null
- * if the vibration source is default, unknown, or implicit (uri).
- */
- @Nullable
- private static String vibrationSourceToString(@VibrationSource int vibrationSource) {
- return switch (vibrationSource) {
- case VIBRATION_SOURCE_OFF -> SOURCE_OFF_STRING;
- case VIBRATION_SOURCE_AUDIO_CHANNEL -> VIBRATION_SOURCE_AUDIO_CHANNEL_STRING;
- case VIBRATION_SOURCE_HAPTIC_GENERATOR -> VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING;
- case VIBRATION_SOURCE_APPLICATION_DEFAULT ->
- VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING;
- case VIBRATION_SOURCE_SYSTEM_DEFAULT -> SOURCE_SYSTEM_DEFAULT_STRING;
- default -> null;
- };
- }
-
- @VibrationSource
- private static int stringToVibrationSource(@Nullable String vibrationSource) {
- if (vibrationSource == null) {
- return VIBRATION_SOURCE_UNSPECIFIED;
- }
- return switch (vibrationSource) {
- case SOURCE_OFF_STRING -> VIBRATION_SOURCE_OFF;
- case SOURCE_SYSTEM_DEFAULT_STRING -> VIBRATION_SOURCE_SYSTEM_DEFAULT;
- case VIBRATION_SOURCE_AUDIO_CHANNEL_STRING -> VIBRATION_SOURCE_AUDIO_CHANNEL;
- case VIBRATION_SOURCE_HAPTIC_GENERATOR_STRING -> VIBRATION_SOURCE_HAPTIC_GENERATOR;
- case VIBRATION_SOURCE_APPLICATION_DEFAULT_STRING ->
- VIBRATION_SOURCE_APPLICATION_DEFAULT;
- default -> VIBRATION_SOURCE_UNKNOWN;
- };
- }
-
- /**
- * Builder for {@link RingtoneSelection}. In general, this builder will be used by interfaces
- * allowing the user to configure their selection. Once a selection is stored as a Uri, then
- * the RingtoneSelection can be loaded directly using {@link RingtoneSelection#fromUri}.
- */
- @FlaggedApi(Flags.FLAG_HAPTICS_CUSTOMIZATION_ENABLED)
- public static final class Builder {
- private Uri mSoundUri;
- private Uri mVibrationUri;
- @SoundSource private int mSoundSource = SOUND_SOURCE_UNSPECIFIED;
- @VibrationSource private int mVibrationSource = VIBRATION_SOURCE_UNSPECIFIED;
-
- /**
- * Creates a new {@link RingtoneSelection} builder. A default ringtone selection has its
- * sound and vibration source unspecified, which means they would fall back to app/system
- * defaults.
- */
- public Builder() {}
-
- /**
- * Creates a builder initialized with the given ringtone selection.
- */
- public Builder(@NonNull RingtoneSelection selection) {
- requireNonNull(selection);
- mSoundSource = selection.getSoundSource();
- mSoundUri = selection.getSoundUri();
- mVibrationSource = selection.getVibrationSource();
- mVibrationUri = selection.getVibrationUri();
- }
-
- /**
- * Sets the desired sound source.
- *
- * <p>Values other than {@link #SOUND_SOURCE_URI} will clear any previous sound Uri.
- * For {@link #SOUND_SOURCE_URI}, the {@link #setSoundSource(Uri)} method should be
- * used instead, as setting it here will have no effect unless the Uri is also set.
- */
- @NonNull
- public Builder setSoundSource(@SoundSource int soundSource) {
- mSoundSource = soundSource;
- if (soundSource != SOUND_SOURCE_URI && soundSource != SOUND_SOURCE_UNKNOWN) {
- // Note that this means the configuration of "silent sound, but use haptic
- // generator" is currently not supported. Future support could be added by either
- // using the vibration uri in that case, or by having a special
- // "setSoundUriForVibrationOnly(Uri)" method that sets sound source to off but
- // also retains the Uri.
- mSoundUri = null;
- }
- return this;
- }
-
- /**
- * Sets the sound source to {@link #SOUND_SOURCE_URI}, and the sound Uri to the
- * specified {@link Uri}.
- */
- @NonNull
- public Builder setSoundSource(@NonNull Uri soundUri) {
- // getCanonicalUri shouldn't return null. If it somehow did, then the
- // RingtoneSelection constructor will revert this to unspecified.
- mSoundUri = requireNonNull(soundUri).getCanonicalUri();
- mSoundSource = SOUND_SOURCE_URI;
- return this;
- }
-
- /**
- * Sets the vibration source to the specified value.
- *
- * <p>Values other than {@link #VIBRATION_SOURCE_URI} will clear any previous vibration Uri.
- * For {@link #VIBRATION_SOURCE_URI}, the {@link #setVibrationSource(Uri)} method should be
- * used instead, as setting it here will have no effect unless the Uri is also set.
- */
- @NonNull
- public Builder setVibrationSource(@VibrationSource int vibrationSource) {
- mVibrationSource = vibrationSource;
- if (vibrationSource != VIBRATION_SOURCE_URI
- && vibrationSource != VIBRATION_SOURCE_UNKNOWN) {
- mVibrationUri = null;
- }
- return this;
- }
-
- /**
- * Sets the vibration source to {@link #VIBRATION_SOURCE_URI}, and the vibration Uri to the
- * specified {@link Uri}.
- */
- @NonNull
- public Builder setVibrationSource(@NonNull Uri vibrationUri) {
- // getCanonicalUri shouldn't return null. If it somehow did, then the
- // RingtoneSelection constructor will revert this to unspecified.
- mVibrationUri = requireNonNull(vibrationUri).getCanonicalUri();
- mVibrationSource = VIBRATION_SOURCE_URI;
- return this;
- }
-
- /**
- * Returns the ringtone Uri that was configured.
- */
- @NonNull
- public RingtoneSelection build() {
- return new RingtoneSelection(mSoundUri, mSoundSource, mVibrationUri, mVibrationSource);
- }
- }
-}
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index 48b476651c91..60ab1a4e42ac 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -27,7 +27,9 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioSystem;
+import android.os.Binder;
import android.os.Build;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -52,6 +54,8 @@ public class AudioMix implements Parcelable {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private int mMixType = MIX_TYPE_INVALID;
+ private final IBinder mToken;
+
// written by AudioPolicy
int mMixState = MIX_STATE_DISABLED;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -68,7 +72,7 @@ public class AudioMix implements Parcelable {
*/
private AudioMix(@NonNull AudioMixingRule rule, @NonNull AudioFormat format,
int routeFlags, int callbackFlags,
- int deviceType, @Nullable String deviceAddress) {
+ int deviceType, @Nullable String deviceAddress, IBinder token) {
mRule = Objects.requireNonNull(rule);
mFormat = Objects.requireNonNull(format);
mRouteFlags = routeFlags;
@@ -76,6 +80,7 @@ public class AudioMix implements Parcelable {
mCallbackFlags = callbackFlags;
mDeviceSystemType = deviceType;
mDeviceAddress = (deviceAddress == null) ? new String("") : deviceAddress;
+ mToken = token;
}
// CALLBACK_FLAG_* values: keep in sync with AudioMix::kCbFlag* values defined
@@ -273,13 +278,14 @@ public class AudioMix implements Parcelable {
return Objects.equals(this.mRouteFlags, that.mRouteFlags)
&& Objects.equals(this.mRule, that.mRule)
&& Objects.equals(this.mMixType, that.mMixType)
- && Objects.equals(this.mFormat, that.mFormat);
+ && Objects.equals(this.mFormat, that.mFormat)
+ && Objects.equals(this.mToken, that.mToken);
}
/** @hide */
@Override
public int hashCode() {
- return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
+ return Objects.hash(mRouteFlags, mRule, mMixType, mFormat, mToken);
}
@Override
@@ -298,6 +304,7 @@ public class AudioMix implements Parcelable {
dest.writeString8(mDeviceAddress);
mFormat.writeToParcel(dest, flags);
mRule.writeToParcel(dest, flags);
+ dest.writeStrongBinder(mToken);
}
public static final @NonNull Parcelable.Creator<AudioMix> CREATOR = new Parcelable.Creator<>() {
@@ -317,6 +324,7 @@ public class AudioMix implements Parcelable {
mixBuilder.setDevice(p.readInt(), p.readString8());
mixBuilder.setFormat(AudioFormat.CREATOR.createFromParcel(p));
mixBuilder.setMixingRule(AudioMixingRule.CREATOR.createFromParcel(p));
+ mixBuilder.setToken(p.readStrongBinder());
return mixBuilder.build();
}
@@ -339,6 +347,7 @@ public class AudioMix implements Parcelable {
private AudioFormat mFormat = null;
private int mRouteFlags = 0;
private int mCallbackFlags = 0;
+ private IBinder mToken = null;
// an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_*
private int mDeviceSystemType = AudioSystem.DEVICE_NONE;
private String mDeviceAddress = null;
@@ -380,6 +389,15 @@ public class AudioMix implements Parcelable {
/**
* @hide
+ * Only used by AudioMix internally.
+ */
+ Builder setToken(IBinder token) {
+ mToken = token;
+ return this;
+ }
+
+ /**
+ * @hide
* Only used by AudioPolicyConfig, not a public API.
* @param callbackFlags which callbacks are called from native
* @return the same Builder instance.
@@ -540,8 +558,13 @@ public class AudioMix implements Parcelable {
throw new IllegalArgumentException(error);
}
}
+
+ if (mToken == null) {
+ mToken = new Binder();
+ }
+
return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType,
- mDeviceAddress);
+ mDeviceAddress, mToken);
}
private int getLoopbackDeviceSystemTypeForAudioMixingRule(AudioMixingRule rule) {
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index bbe461c95ed9..508c0a2b9a21 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -337,7 +337,9 @@ public class AudioPolicy {
/**
* Update the current configuration of the set of audio mixes by adding new ones, while
- * keeping the policy registered.
+ * keeping the policy registered. If any of the provided audio mixes is invalid then none of
+ * the passed mixes will be registered.
+ *
* This method can only be called on a registered policy.
* @param mixes the list of {@link AudioMix} to add
* @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR}
@@ -375,12 +377,15 @@ public class AudioPolicy {
}
/**
- * Update the current configuration of the set of audio mixes by removing some, while
- * keeping the policy registered.
- * This method can only be called on a registered policy.
+ * Update the current configuration of the set of audio mixes for this audio policy by
+ * removing some, while keeping the policy registered. Will unregister all provided audio
+ * mixes, if possible.
+ *
+ * This method can only be called on a registered policy and only affects this current policy.
* @param mixes the list of {@link AudioMix} to remove
* @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR}
- * otherwise.
+ * otherwise. If only some of the provided audio mixes were detached but any one mix
+ * failed to be detached, this method returns {@link AudioManager#ERROR}.
*/
public int detachMixes(@NonNull List<AudioMix> mixes) {
if (mixes == null) {
@@ -394,7 +399,6 @@ public class AudioPolicy {
for (AudioMix mix : mixes) {
if (mix == null) {
throw new IllegalArgumentException("Illegal null AudioMix in detachMixes");
- // TODO also check mix is currently contained in list of mixes
} else {
zeMixes.add(mix);
}
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index d277c7dfbea4..5e7c7c453df3 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -228,7 +228,7 @@ public class AudioPolicyConfig implements Parcelable {
}
}
- private void setMixRegistration(@NonNull final AudioMix mix) {
+ protected void setMixRegistration(@NonNull final AudioMix mix) {
if (!mRegistrationId.isEmpty()) {
if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) ==
AudioMix.ROUTE_FLAG_LOOP_BACK) {
@@ -246,7 +246,9 @@ public class AudioPolicyConfig implements Parcelable {
@GuardedBy("mMixes")
protected void add(@NonNull ArrayList<AudioMix> mixes) {
for (AudioMix mix : mixes) {
- setMixRegistration(mix);
+ if (mix.getRegistration() == null || mix.getRegistration().isEmpty()) {
+ setMixRegistration(mix);
+ }
mMixes.add(mix);
}
}
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index d1453975f801..ed543e67fdf1 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -519,7 +519,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
@Override
public void stopPlayback(int mode) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_STOP_PLAYBACK, mode));
+ mCaller.executeOrSendMessage(mCaller.obtainMessageI(DO_STOP_PLAYBACK, mode));
}
@Override
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 0fb7c95e3680..9d0221a3ae68 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -206,9 +206,9 @@ package android.nfc.cardemulation {
method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
method public boolean removeAidsForService(android.content.ComponentName, String);
- method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
+ method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setShouldDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean);
method public boolean supportsAidPrefixRegistration();
method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
method public boolean unsetPreferredService(android.app.Activity);
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 64f7fa44c12f..85a07b74871b 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -30,7 +30,7 @@ interface INfcCardEmulation
boolean isDefaultServiceForAid(int userHandle, in ComponentName service, String aid);
boolean setDefaultServiceForCategory(int userHandle, in ComponentName service, String category);
boolean setDefaultForNextTap(int userHandle, in ComponentName service);
- boolean setDefaultToObserveModeForService(int userId, in android.content.ComponentName service, boolean enable);
+ boolean setShouldDefaultToObserveModeForService(int userId, in android.content.ComponentName service, boolean enable);
boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter, boolean autoTransact);
boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index e62e37bd4ca0..2c7d61eea777 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -141,7 +141,7 @@ public final class ApduServiceInfo implements Parcelable {
/**
* Whether the NFC stack should default to Observe Mode when this preferred service.
*/
- private boolean mDefaultToObserveMode;
+ private boolean mShouldDefaultToObserveMode;
/**
* @hide
@@ -275,8 +275,8 @@ public final class ApduServiceInfo implements Parcelable {
com.android.internal.R.styleable.HostApduService_settingsActivity);
mOffHostName = null;
mStaticOffHostName = mOffHostName;
- mDefaultToObserveMode = sa.getBoolean(
- R.styleable.HostApduService_defaultToObserveMode,
+ mShouldDefaultToObserveMode = sa.getBoolean(
+ R.styleable.HostApduService_shouldDefaultToObserveMode,
false);
sa.recycle();
} else {
@@ -297,8 +297,8 @@ public final class ApduServiceInfo implements Parcelable {
com.android.internal.R.styleable.HostApduService_settingsActivity);
mOffHostName = sa.getString(
com.android.internal.R.styleable.OffHostApduService_secureElementName);
- mDefaultToObserveMode = sa.getBoolean(
- R.styleable.HostApduService_defaultToObserveMode,
+ mShouldDefaultToObserveMode = sa.getBoolean(
+ R.styleable.HostApduService_shouldDefaultToObserveMode,
false);
if (mOffHostName != null) {
if (mOffHostName.equals("eSE")) {
@@ -633,22 +633,22 @@ public final class ApduServiceInfo implements Parcelable {
}
/**
- * Returns whether the NFC stack should default to observe mode when this servise is preferred.
- * @return whether the NFC stack should default to observe mode when this servise is preferred
+ * Returns whether the NFC stack should default to observe mode when this service is preferred.
+ * @return whether the NFC stack should default to observe mode when this service is preferred
*/
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
- public boolean defaultToObserveMode() {
- return mDefaultToObserveMode;
+ public boolean shouldDefaultToObserveMode() {
+ return mShouldDefaultToObserveMode;
}
/**
- * Sets whether the NFC stack should default to observe mode when this servise is preferred.
- * @param defaultToObserveMode whether the NFC stack should default to observe mode when this
- * servise is preferred
+ * Sets whether the NFC stack should default to observe mode when this service is preferred.
+ * @param shouldDefaultToObserveMode whether the NFC stack should default to observe mode when
+ * this service is preferred
*/
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
- public void setDefaultToObserveMode(boolean defaultToObserveMode) {
- mDefaultToObserveMode = defaultToObserveMode;
+ public void setShouldDefaultToObserveMode(boolean shouldDefaultToObserveMode) {
+ mShouldDefaultToObserveMode = shouldDefaultToObserveMode;
}
/**
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 47ddd9de224f..ea58504063d7 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -348,11 +348,11 @@ public final class CardEmulation {
* @return whether the change was successful.
*/
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
- public boolean setDefaultToObserveModeForService(@NonNull ComponentName service,
+ public boolean setShouldDefaultToObserveModeForService(@NonNull ComponentName service,
boolean enable) {
try {
- return sService.setDefaultToObserveModeForService(mContext.getUser().getIdentifier(),
- service, enable);
+ return sService.setShouldDefaultToObserveModeForService(
+ mContext.getUser().getIdentifier(), service, enable);
} catch (RemoteException e) {
Log.e(TAG, "Failed to reach CardEmulationService.");
}
diff --git a/packages/CrashRecovery/framework/Android.bp b/packages/CrashRecovery/framework/Android.bp
index c0d93531a1e6..9480327253f4 100644
--- a/packages/CrashRecovery/framework/Android.bp
+++ b/packages/CrashRecovery/framework/Android.bp
@@ -3,7 +3,7 @@ soong_config_module_type {
module_type: "filegroup",
config_namespace: "ANDROID",
bool_variables: [
- "move_crashrecovery_files",
+ "crashrecovery_files_in_platform",
],
properties: [
"srcs",
@@ -12,14 +12,13 @@ soong_config_module_type {
platform_filegroup {
name: "framework-crashrecovery-sources",
- srcs: [
- "java/**/*.java",
- "java/**/*.aidl",
- ],
soong_config_variables: {
- // if the flag is enabled, then files would be moved to module
- move_crashrecovery_files: {
- srcs: [],
+ // if this flag is enabled, then files are part of platform
+ crashrecovery_files_in_platform: {
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ],
},
},
path: "java",
@@ -31,7 +30,7 @@ soong_config_module_type {
module_type: "filegroup",
config_namespace: "ANDROID",
bool_variables: [
- "move_crashrecovery_files",
+ "crashrecovery_files_in_module",
],
properties: [
"srcs",
@@ -40,10 +39,9 @@ soong_config_module_type {
module_filegroup {
name: "framework-crashrecovery-module-sources",
- srcs: [],
soong_config_variables: {
- // if the flag is enabled, then files would be moved to module
- move_crashrecovery_files: {
+ // if this flag is enabled, then files are part of module
+ crashrecovery_files_in_module: {
srcs: [
"java/**/*.java",
"java/**/*.aidl",
diff --git a/packages/CrashRecovery/services/Android.bp b/packages/CrashRecovery/services/Android.bp
index ab10b5a23676..961b41f4a633 100644
--- a/packages/CrashRecovery/services/Android.bp
+++ b/packages/CrashRecovery/services/Android.bp
@@ -3,7 +3,7 @@ soong_config_module_type {
module_type: "filegroup",
config_namespace: "ANDROID",
bool_variables: [
- "move_crashrecovery_files",
+ "crashrecovery_files_in_platform",
],
properties: [
"srcs",
@@ -12,15 +12,14 @@ soong_config_module_type {
platform_filegroup {
name: "services-crashrecovery-sources",
- srcs: [
- "java/**/*.java",
- "java/**/*.aidl",
- ":statslog-crashrecovery-java-gen",
- ],
soong_config_variables: {
- // if the flag is enabled, then files would be moved to module
- move_crashrecovery_files: {
- srcs: [],
+ // if this flag is enabled, then files are part of platform
+ crashrecovery_files_in_platform: {
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ":statslog-crashrecovery-java-gen",
+ ],
},
},
visibility: ["//frameworks/base:__subpackages__"],
@@ -31,7 +30,7 @@ soong_config_module_type {
module_type: "filegroup",
config_namespace: "ANDROID",
bool_variables: [
- "move_crashrecovery_files",
+ "crashrecovery_files_in_module",
],
properties: [
"srcs",
@@ -40,10 +39,9 @@ soong_config_module_type {
module_filegroup {
name: "services-crashrecovery-module-sources",
- srcs: [],
soong_config_variables: {
- // if the flag is enabled, then files would be moved to module
- move_crashrecovery_files: {
+ // if this flag is enabled, then files are part of module
+ crashrecovery_files_in_module: {
srcs: [
"java/**/*.java",
"java/**/*.aidl",
diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
index ce8fb6568bd5..5d71b7d98fdc 100644
--- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java
@@ -38,13 +38,13 @@ 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 com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 5fb47dd9b95a..0fb932735ab4 100644
--- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -75,6 +75,9 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
+ private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
+ "persist.device_config.configuration.disable_high_impact_rollback";
+
private final Context mContext;
private final Handler mHandler;
private final ApexManager mApexManager;
@@ -605,6 +608,10 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
// Apply all available low impact rollbacks.
mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
} else if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH) {
+ // Check disable_high_impact_rollback device config before performing rollback
+ if (SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
+ return;
+ }
// Rollback one package at a time. If that doesn't resolve the issue, rollback
// next with same impact level.
mHandler.post(() -> rollbackHighImpact(availableRollbacks, rollbackReason));
@@ -718,7 +725,9 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
break;
case PackageManager.ROLLBACK_USER_IMPACT_HIGH:
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90;
+ if (!SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90;
+ }
break;
default:
impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
diff --git a/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java b/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
new file mode 100644
index 000000000000..a6ae68f62f10
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/util/BackgroundThread.java
@@ -0,0 +1,103 @@
+/*
+ * * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Thread for asynchronous event processing. This thread is configured as
+ * {@link android.os.Process#THREAD_PRIORITY_BACKGROUND}, which means fewer CPU
+ * resources will be dedicated to it, and it will "have less chance of impacting
+ * the responsiveness of the user interface."
+ * <p>
+ * This thread is best suited for tasks that the user is not actively waiting
+ * for, or for tasks that the user expects to be executed eventually.
+ *
+ * @see com.android.internal.os.BackgroundThread
+ *
+ * TODO: b/326916057 depend on modules-utils-backgroundthread instead
+ * @hide
+ */
+public final class BackgroundThread extends HandlerThread {
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static BackgroundThread sInstance;
+ @GuardedBy("sLock")
+ private static Handler sHandler;
+ @GuardedBy("sLock")
+ private static HandlerExecutor sHandlerExecutor;
+
+ private BackgroundThread() {
+ super(BackgroundThread.class.getName(), android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ }
+
+ @GuardedBy("sLock")
+ private static void ensureThreadLocked() {
+ if (sInstance == null) {
+ sInstance = new BackgroundThread();
+ sInstance.start();
+ sHandler = new Handler(sInstance.getLooper());
+ sHandlerExecutor = new HandlerExecutor(sHandler);
+ }
+ }
+
+ /**
+ * Get the singleton instance of this class.
+ *
+ * @return the singleton instance of this class
+ */
+ @NonNull
+ public static BackgroundThread get() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sInstance;
+ }
+ }
+
+ /**
+ * Get the singleton {@link Handler} for this class.
+ *
+ * @return the singleton {@link Handler} for this class.
+ */
+ @NonNull
+ public static Handler getHandler() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sHandler;
+ }
+ }
+
+ /**
+ * Get the singleton {@link Executor} for this class.
+ *
+ * @return the singleton {@link Executor} for this class.
+ */
+ @NonNull
+ public static Executor getExecutor() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sHandlerExecutor;
+ }
+ }
+}
diff --git a/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java b/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
new file mode 100644
index 000000000000..948ebcca0263
--- /dev/null
+++ b/packages/CrashRecovery/services/java/com/android/util/HandlerExecutor.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * An adapter {@link Executor} that posts all executed tasks onto the given
+ * {@link Handler}.
+ *
+ * TODO: b/326916057 depend on modules-utils-backgroundthread instead
+ * @hide
+ */
+public class HandlerExecutor implements Executor {
+ private final Handler mHandler;
+
+ public HandlerExecutor(@NonNull Handler handler) {
+ mHandler = Objects.requireNonNull(handler);
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ if (!mHandler.post(command)) {
+ throw new RejectedExecutionException(mHandler + " is shutting down");
+ }
+ }
+}
diff --git a/packages/CredentialManager/res/drawable/ic_passkey_24.xml b/packages/CredentialManager/res/drawable/ic_passkey_24.xml
index a2c4f374caf1..5298f9eda88e 100644
--- a/packages/CredentialManager/res/drawable/ic_passkey_24.xml
+++ b/packages/CredentialManager/res/drawable/ic_passkey_24.xml
@@ -13,16 +13,14 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<vector
- android:alpha="0.8"
- android:height="24dp"
- android:viewportHeight="24"
- android:viewportWidth="24"
- android:width="24dp"
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools">
- <path android:fillColor="#4C463C" android:fillType="evenOdd" android:pathData="M22.18,14.09C22.18,15.364 21.408,16.459 20.306,16.931L21.247,17.872L20.219,18.9L21.247,19.928L19.068,22.107L18.099,21.138L18.099,17.017C16.878,16.604 16,15.45 16,14.09C16,12.383 17.383,11 19.09,11C20.796,11 22.18,12.383 22.18,14.09ZM20.692,14.091C20.692,14.976 19.975,15.693 19.09,15.693C18.205,15.693 17.488,14.976 17.488,14.091C17.488,13.206 18.205,12.488 19.09,12.488C19.975,12.488 20.692,13.206 20.692,14.091Z"/>
- <path android:fillColor="#4C463C" android:pathData="M14.978,8.476C14.978,10.865 13.041,12.802 10.652,12.802C8.263,12.802 6.326,10.865 6.326,8.476C6.326,6.087 8.263,4.15 10.652,4.15C13.041,4.15 14.978,6.087 14.978,8.476Z"/>
- <path android:fillColor="#4C463C" android:pathData="M2,19.263C2,16.39 7.762,14.937 10.652,14.937C11.782,14.937 13.353,15.16 14.845,15.602C15.177,16.491 15.804,17.236 16.607,17.717V21.454H2V19.263Z"/>
+<!--LINT.IfChange-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M120,800L120,688Q120,654 137.5,625.5Q155,597 184,582Q246,551 310,535.5Q374,520 440,520Q460,520 480,521.5Q500,523 520,526Q516,584 541,635.5Q566,687 614,720L614,800L120,800ZM760,920L700,860L700,674Q656,661 628,624.5Q600,588 600,540Q600,482 641,441Q682,400 740,400Q798,400 839,441Q880,482 880,540Q880,585 854.5,620Q829,655 790,670L840,720L780,780L840,840L760,920ZM440,480Q374,480 327,433Q280,386 280,320Q280,254 327,207Q374,160 440,160Q506,160 553,207Q600,254 600,320Q600,386 553,433Q506,480 440,480ZM740,560Q757,560 768.5,548.5Q780,537 780,520Q780,503 768.5,491.5Q757,480 740,480Q723,480 711.5,491.5Q700,503 700,520Q700,537 711.5,548.5Q723,560 740,560Z"/>
</vector>
+<!--LINT.ThenChange(/packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml)--> \ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 82b47a964204..527701c06bc6 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -63,9 +63,9 @@
<!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
<string name="choose_provider_body">Select a password manager to save your info and sign in faster next time</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 passkey. [CHAR LIMIT=200] -->
- <string name="choose_create_option_passkey_title">Create passkey for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
+ <string name="choose_create_option_passkey_title">Create passkey to sign in to <xliff:g id="appName" 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 password. [CHAR LIMIT=200] -->
- <string name="choose_create_option_password_title">Save password for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
+ <string name="choose_create_option_password_title">Save password to sign in to <xliff:g id="appName" 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="appName" example="Tribank">%1$s</xliff:g>?</string>
<!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/shared/Android.bp b/packages/CredentialManager/shared/Android.bp
index 47ca944c479c..f8ee96ebe7e4 100644
--- a/packages/CredentialManager/shared/Android.bp
+++ b/packages/CredentialManager/shared/Android.bp
@@ -11,6 +11,7 @@ android_library {
name: "CredentialManagerShared",
manifest: "AndroidManifest.xml",
srcs: ["src/**/*.kt"],
+ resource_dirs: ["res"],
static_libs: [
"androidx.activity_activity-compose",
"androidx.core_core-ktx",
diff --git a/packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml b/packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml
new file mode 100644
index 000000000000..b81f7c500753
--- /dev/null
+++ b/packages/CredentialManager/shared/res/drawable/ic_passkey_24.xml
@@ -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.
+ -->
+<!--LINT.IfChange-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M120,800L120,688Q120,654 137.5,625.5Q155,597 184,582Q246,551 310,535.5Q374,520 440,520Q460,520 480,521.5Q500,523 520,526Q516,584 541,635.5Q566,687 614,720L614,800L120,800ZM760,920L700,860L700,674Q656,661 628,624.5Q600,588 600,540Q600,482 641,441Q682,400 740,400Q798,400 839,441Q880,482 880,540Q880,585 854.5,620Q829,655 790,670L840,720L780,780L840,840L760,920ZM440,480Q374,480 327,433Q280,386 280,320Q280,254 327,207Q374,160 440,160Q506,160 553,207Q600,254 600,320Q600,386 553,433Q506,480 440,480ZM740,560Q757,560 768.5,548.5Q780,537 780,520Q780,503 768.5,491.5Q757,480 740,480Q723,480 711.5,491.5Q700,503 700,520Q700,537 711.5,548.5Q723,560 740,560Z"/>
+</vector>
+<!--LINT.ThenChange(/packages/CredentialManager/res/drawable/ic_passkey_24.xml)--> \ 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 a5f227a3adc9..39d3f42e8bf4 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -39,6 +39,7 @@ import androidx.credentials.provider.PasswordCredentialEntry
import androidx.credentials.provider.PublicKeyCredentialEntry
import androidx.credentials.provider.RemoteEntry
import com.android.credentialmanager.IS_AUTO_SELECTED_KEY
+import com.android.credentialmanager.R
import com.android.credentialmanager.model.get.ActionEntryInfo
import com.android.credentialmanager.model.get.AuthenticationEntryInfo
import com.android.credentialmanager.model.get.CredentialEntryInfo
@@ -127,10 +128,10 @@ private fun getCredentialOptionInfoList(
userName = credentialEntry.username.toString(),
displayName = credentialEntry.displayName?.toString(),
icon = credentialEntry.icon.loadDrawable(context),
- shouldTintIcon = credentialEntry.isDefaultIcon,
+ shouldTintIcon = credentialEntry.hasDefaultIcon,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
- credentialEntry.autoSelectAllowedFromOption,
+ credentialEntry.isAutoSelectAllowedFromOption,
)
)
}
@@ -147,11 +148,13 @@ private fun getCredentialOptionInfoList(
credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
userName = credentialEntry.username.toString(),
displayName = credentialEntry.displayName?.toString(),
- icon = credentialEntry.icon.loadDrawable(context),
- shouldTintIcon = credentialEntry.isDefaultIcon,
+ icon = if (credentialEntry.hasDefaultIcon)
+ context.getDrawable(R.drawable.ic_passkey_24)
+ else credentialEntry.icon.loadDrawable(context),
+ shouldTintIcon = credentialEntry.hasDefaultIcon,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
- credentialEntry.autoSelectAllowedFromOption,
+ credentialEntry.isAutoSelectAllowedFromOption,
)
)
}
@@ -170,10 +173,10 @@ private fun getCredentialOptionInfoList(
userName = credentialEntry.title.toString(),
displayName = credentialEntry.subtitle?.toString(),
icon = credentialEntry.icon.loadDrawable(context),
- shouldTintIcon = credentialEntry.isDefaultIcon,
+ shouldTintIcon = credentialEntry.hasDefaultIcon,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
- credentialEntry.autoSelectAllowedFromOption,
+ credentialEntry.isAutoSelectAllowedFromOption,
)
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 5830b9fc1028..ccf401da5a4c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -282,6 +282,8 @@ class CreateFlowUtils {
val appPreferredDefaultProviderId: String? =
if (!requestInfo.hasPermissionToOverrideDefault()) null
else createCredentialRequestJetpack?.displayInfo?.preferDefaultProvider
+ val typeDisplayIcon = createCredentialRequestJetpack?.displayInfo?.credentialTypeIcon
+ ?.loadDrawable(context)
return when (createCredentialRequestJetpack) {
is CreatePasswordRequest -> RequestDisplayInfo(
createCredentialRequestJetpack.id,
@@ -302,7 +304,6 @@ class CreateFlowUtils {
newRequestDisplayInfoFromPasskeyJson(
requestJson = createCredentialRequestJetpack.requestJson,
appLabel = appLabel,
- context = context,
preferImmediatelyAvailableCredentials =
createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId = appPreferredDefaultProviderId,
@@ -311,6 +312,7 @@ class CreateFlowUtils {
// the passkey type. For now, directly parse it ourselves.
isAutoSelectRequest = createCredentialRequest.credentialData.getBoolean(
Constants.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS, false),
+ typeIcon = context.getDrawable(R.drawable.ic_passkey_24) ?: return null,
)
}
is CreateCustomCredentialRequest -> {
@@ -323,7 +325,7 @@ class CreateFlowUtils {
subtitle = displayInfo.userDisplayName?.toString(),
type = CredentialType.UNKNOWN,
appName = appLabel,
- typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
+ typeIcon = typeDisplayIcon
?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null,
preferImmediatelyAvailableCredentials =
createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
@@ -502,7 +504,7 @@ class CreateFlowUtils {
private fun newRequestDisplayInfoFromPasskeyJson(
requestJson: String,
appLabel: String,
- context: Context,
+ typeIcon: Drawable,
preferImmediatelyAvailableCredentials: Boolean,
appPreferredDefaultProviderId: String?,
userSetDefaultProviderIds: Set<String>,
@@ -525,7 +527,7 @@ class CreateFlowUtils {
displayname,
CredentialType.PASSKEY,
appLabel,
- context.getDrawable(R.drawable.ic_passkey_24) ?: return null,
+ typeIcon,
preferImmediatelyAvailableCredentials,
appPreferredDefaultProviderId,
userSetDefaultProviderIds,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 0f1721790295..13260231038d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -213,7 +213,7 @@ class CredentialAutofillService : AutofillService() {
autofillIdToProvidersMap.forEach { (autofillId, providers) ->
validFillResponse = processProvidersForAutofillId(
filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder,
- getCredResponse.pendingIntent)
+ getCredResponse.intent)
.or(validFillResponse)
}
if (!validFillResponse) {
@@ -229,7 +229,7 @@ class CredentialAutofillService : AutofillService() {
providerDataList: ArrayList<GetCredentialProviderData>,
entryIconMap: Map<String, Icon>,
fillResponseBuilder: FillResponse.Builder,
- bottomSheetPendingIntent: PendingIntent?
+ bottomSheetIntent: Intent
): Boolean {
val providerList = GetFlowUtils.toProviderList(
providerDataList,
@@ -267,6 +267,8 @@ class CredentialAutofillService : AutofillService() {
}
}
}
+ bottomSheetIntent.putExtra(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{
val primaryEntry = it.sortedCredentialEntryList.first()
val pendingIntent = primaryEntry.pendingIntent
@@ -330,16 +332,16 @@ class CredentialAutofillService : AutofillService() {
datasetAdded = true
i++
- if (i == lastDropdownDatasetIndex && bottomSheetPendingIntent != null) {
- addDropdownMoreOptionsPresentation(bottomSheetPendingIntent, autofillId,
+ if (i == lastDropdownDatasetIndex) {
+ addDropdownMoreOptionsPresentation(bottomSheetIntent, autofillId,
fillResponseBuilder)
}
}
val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
inlinePresentationSpecsCount)
- if (datasetAdded && bottomSheetPendingIntent != null && pinnedSpec != null) {
- addPinnedInlineSuggestion(bottomSheetPendingIntent, pinnedSpec, autofillId,
- fillResponseBuilder, providerDataList)
+ if (datasetAdded && pinnedSpec != null) {
+ addPinnedInlineSuggestion(pinnedSpec, autofillId,
+ fillResponseBuilder, bottomSheetIntent)
}
return datasetAdded
}
@@ -370,13 +372,14 @@ class CredentialAutofillService : AutofillService() {
}
private fun addDropdownMoreOptionsPresentation(
- bottomSheetPendingIntent: PendingIntent,
+ bottomSheetIntent: Intent,
autofillId: AutofillId,
fillResponseBuilder: FillResponse.Builder
) {
val presentationBuilder = Presentations.Builder()
.setMenuPresentation(
RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+ val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent)
fillResponseBuilder.addDataset(
Dataset.Builder()
@@ -385,7 +388,7 @@ class CredentialAutofillService : AutofillService() {
Field.Builder().setPresentations(
presentationBuilder.build())
.build())
- .setAuthentication(bottomSheetPendingIntent.intentSender)
+ .setAuthentication(pendingIntent.intentSender)
.build()
)
}
@@ -401,37 +404,41 @@ class CredentialAutofillService : AutofillService() {
}
private fun addPinnedInlineSuggestion(
- bottomSheetPendingIntent: PendingIntent,
spec: InlinePresentationSpec,
autofillId: AutofillId,
fillResponseBuilder: FillResponse.Builder,
- providerDataList: ArrayList<GetCredentialProviderData>
+ bottomSheetIntent: Intent
) {
+ val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent)
+
val dataSetBuilder = Dataset.Builder()
val sliceBuilder = InlineSuggestionUi
- .newContentBuilder(bottomSheetPendingIntent)
+ .newContentBuilder(pendingIntent)
.setStartIcon(Icon.createWithResource(this,
com.android.credentialmanager.R.drawable.more_horiz_24px))
val presentationBuilder = Presentations.Builder()
.setInlinePresentation(InlinePresentation(
sliceBuilder.build().slice, spec, /* pinned= */ true))
- val extrasIntent = Intent()
- extrasIntent.putExtra(ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList)
-
fillResponseBuilder.addDataset(
dataSetBuilder
.setField(
autofillId,
Field.Builder().setPresentations(
- presentationBuilder.build())
- .build())
- .setAuthentication(bottomSheetPendingIntent.intentSender)
- .setCredentialFillInIntent(extrasIntent)
+ presentationBuilder.build()
+ ).build()
+ )
+ .setAuthentication(pendingIntent.intentSender)
.build()
)
}
+ private fun setUpBottomSheetPendingIntent(intent: Intent): PendingIntent {
+ intent.setAction(java.util.UUID.randomUUID().toString())
+ return PendingIntent.getActivity(this, /*requestCode=*/0, intent,
+ PendingIntent.FLAG_MUTABLE, /*options=*/null)
+ }
+
/**
* Maps Autofill Id to provider list. For example, passing in a provider info
*
@@ -571,7 +578,7 @@ class CredentialAutofillService : AutofillService() {
responseClientState: Bundle
): GetCredentialRequest? {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
- traverseStructureForRequest(structure, credentialOptions, responseClientState)
+ traverseStructureForRequest(structure, credentialOptions, responseClientState, sessionId)
if (credentialOptions.isNotEmpty()) {
val dataBundle = Bundle()
@@ -587,7 +594,8 @@ class CredentialAutofillService : AutofillService() {
private fun traverseStructureForRequest(
structure: AssistStructure,
cmRequests: MutableList<CredentialOption>,
- responseClientState: Bundle
+ responseClientState: Bundle,
+ sessionId: Int
) {
val traversedViewNodes: MutableSet<AutofillId> = mutableSetOf()
val credentialOptionsFromHints: MutableMap<String, CredentialOption> = mutableMapOf()
@@ -599,7 +607,7 @@ class CredentialAutofillService : AutofillService() {
windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
traverseNodeForRequest(
windowNode.rootViewNode, cmRequests, responseClientState, traversedViewNodes,
- credentialOptionsFromHints)
+ credentialOptionsFromHints, sessionId)
}
}
@@ -608,11 +616,12 @@ class CredentialAutofillService : AutofillService() {
cmRequests: MutableList<CredentialOption>,
responseClientState: Bundle,
traversedViewNodes: MutableSet<AutofillId>,
- credentialOptionsFromHints: MutableMap<String, CredentialOption>
+ credentialOptionsFromHints: MutableMap<String, CredentialOption>,
+ sessionId: Int
) {
viewNode.autofillId?.let {
cmRequests.addAll(getCredentialOptionsFromViewNode(viewNode, it, responseClientState,
- traversedViewNodes, credentialOptionsFromHints))
+ traversedViewNodes, credentialOptionsFromHints, sessionId))
traversedViewNodes.add(it)
}
@@ -623,7 +632,7 @@ class CredentialAutofillService : AutofillService() {
children.forEach { childNode: AssistStructure.ViewNode ->
traverseNodeForRequest(childNode, cmRequests, responseClientState, traversedViewNodes,
- credentialOptionsFromHints)
+ credentialOptionsFromHints, sessionId)
}
}
@@ -632,7 +641,8 @@ class CredentialAutofillService : AutofillService() {
autofillId: AutofillId,
responseClientState: Bundle,
traversedViewNodes: MutableSet<AutofillId>,
- credentialOptionsFromHints: MutableMap<String, CredentialOption>
+ credentialOptionsFromHints: MutableMap<String, CredentialOption>,
+ sessionId: Int
): MutableList<CredentialOption> {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
if (Flags.autofillCredmanDevIntegration() && viewNode.credentialManagerRequest != null) {
@@ -643,12 +653,25 @@ class CredentialAutofillService : AutofillService() {
.getParcelableArrayList(
CredentialProviderService.EXTRA_AUTOFILL_ID, AutofillId::class.java)
?.let { associatedAutofillIds ->
+ // Set sessionId in autofillIds. The autofillIds stored in Credential
+ // Options do not have associated session id and will result in
+ // different hashes than the ones in assistStructure.
+ associatedAutofillIds.forEach { associatedAutofillId ->
+ associatedAutofillId.sessionId = sessionId
+ }
+
// Check whether any of the associated autofill ids have already been
// traversed. If so, skip, to dedupe on duplicate credential options.
if ((traversedViewNodes intersect associatedAutofillIds.toSet())
.isEmpty()) {
credentialOptions.add(credentialOption)
}
+
+ // Set the autofillIds with session id back to credential option.
+ credentialOption.candidateQueryData.putParcelableArrayList(
+ CredentialProviderService.EXTRA_AUTOFILL_ID,
+ associatedAutofillIds
+ )
}
}
}
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 d319e4cc9ef9..a7b5c36215cf 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -16,8 +16,15 @@
package com.android.credentialmanager.common.ui
+import android.credentials.flags.Flags
+import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope
@@ -25,6 +32,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.android.compose.rememberSystemUiController
import com.android.compose.theme.LocalAndroidColorScheme
+import androidx.compose.ui.unit.dp
import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
@@ -34,40 +42,68 @@ import kotlinx.coroutines.launch
/** Draws a modal bottom sheet with the same styles and effects shared by various flows. */
@Composable
+@OptIn(ExperimentalMaterial3Api::class)
fun ModalBottomSheet(
- sheetContent: @Composable ColumnScope.() -> Unit,
- onDismiss: () -> Unit,
- isInitialRender: Boolean,
- onInitialRenderComplete: () -> Unit,
- isAutoSelectFlow: Boolean,
+ sheetContent: @Composable () -> Unit,
+ onDismiss: () -> Unit,
+ isInitialRender: Boolean,
+ onInitialRenderComplete: () -> Unit,
+ isAutoSelectFlow: Boolean,
) {
- val scope = rememberCoroutineScope()
- val state = rememberModalBottomSheetState(
- initialValue = if (isAutoSelectFlow) ModalBottomSheetValue.Expanded
- else ModalBottomSheetValue.Hidden,
- skipHalfExpanded = true
- )
- val sysUiController = rememberSystemUiController()
- if (state.targetValue == ModalBottomSheetValue.Hidden || isAutoSelectFlow) {
- setTransparentSystemBarsColor(sysUiController)
+ if (Flags.selectorUiImprovementsEnabled()) {
+ val state = androidx.compose.material3.rememberModalBottomSheetState(
+ skipPartiallyExpanded = true
+ )
+ androidx.compose.material3.ModalBottomSheet(
+ onDismissRequest = onDismiss,
+ containerColor = LocalAndroidColorScheme.current.surfaceBright,
+ sheetState = state,
+ content = {
+ Box(
+ modifier = Modifier
+ .animateContentSize()
+ .wrapContentHeight()
+ .fillMaxWidth()
+ ) {
+ sheetContent()
+ }
+ },
+ scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = .32f),
+ shape = EntryShape.TopRoundedCorner,
+ 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.
+ modifier = Modifier.padding(top = 56.dp),
+ )
} else {
- setBottomSheetSystemBarsColor(sysUiController)
- }
- ModalBottomSheetLayout(
- sheetBackgroundColor = LocalAndroidColorScheme.current.surfaceBright,
- modifier = Modifier.background(Color.Transparent),
- sheetState = state,
- sheetContent = sheetContent,
- sheetShape = EntryShape.TopRoundedCorner,
- ) {}
- LaunchedEffect(state.currentValue, state.targetValue) {
- if (state.currentValue == ModalBottomSheetValue.Hidden) {
- if (isInitialRender) {
- onInitialRenderComplete()
- scope.launch { state.show() }
- } else if (state.targetValue == ModalBottomSheetValue.Hidden) {
- // Only dismiss ui when the motion is downwards
- onDismiss()
+ val scope = rememberCoroutineScope()
+ val state = rememberModalBottomSheetState(
+ initialValue = if (isAutoSelectFlow) ModalBottomSheetValue.Expanded
+ else ModalBottomSheetValue.Hidden,
+ skipHalfExpanded = true
+ )
+ val sysUiController = rememberSystemUiController()
+ if (state.targetValue == ModalBottomSheetValue.Hidden || isAutoSelectFlow) {
+ setTransparentSystemBarsColor(sysUiController)
+ } else {
+ setBottomSheetSystemBarsColor(sysUiController)
+ }
+ ModalBottomSheetLayout(
+ sheetBackgroundColor = LocalAndroidColorScheme.current.surfaceBright,
+ modifier = Modifier.background(Color.Transparent),
+ sheetState = state,
+ sheetContent = { sheetContent() },
+ sheetShape = EntryShape.TopRoundedCorner,
+ ) {}
+ LaunchedEffect(state.currentValue, state.targetValue) {
+ if (state.currentValue == ModalBottomSheetValue.Hidden) {
+ if (isInitialRender) {
+ onInitialRenderComplete()
+ scope.launch { state.show() }
+ } else if (state.targetValue == ModalBottomSheetValue.Hidden) {
+ // Only dismiss ui when the motion is downwards
+ onDismiss()
+ }
}
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index bdfe39920d44..c68ae8b168fb 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -18,7 +18,10 @@ package com.android.credentialmanager.common.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
@@ -66,6 +69,9 @@ fun SheetContainerCard(
horizontalAlignment = Alignment.CenterHorizontally,
content = content,
verticalArrangement = contentVerticalArrangement,
+ // The bottom sheet overlaps with the navigation bars but make sure the actual content
+ // in the bottom sheet does not.
+ contentPadding = WindowInsets.navigationBars.asPaddingValues(),
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index a6253b8d4e07..8ff17e0d333a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -29,13 +29,10 @@ import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.Lock
-import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.SuggestionChipDefaults
-import androidx.compose.material3.TopAppBar
-import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -278,31 +275,6 @@ fun ActionEntry(
}
/**
- * A single row of leading icon and text describing a benefit of passkeys, used by the
- * [com.android.credentialmanager.createflow.PasskeyIntroCard].
- */
-@Composable
-fun PasskeyBenefitRow(
- leadingIconPainter: Painter,
- text: String,
-) {
- Row(
- horizontalArrangement = Arrangement.spacedBy(16.dp),
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier.fillMaxWidth()
- ) {
- Icon(
- modifier = Modifier.size(24.dp),
- painter = leadingIconPainter,
- tint = LocalAndroidColorScheme.current.onSurfaceVariant,
- // Decorative purpose only.
- contentDescription = null,
- )
- BodyMediumText(text = text)
- }
-}
-
-/**
* A single row of one or two CTA buttons for continuing or cancelling the current step.
*/
@Composable
@@ -327,40 +299,36 @@ fun CtaButtonRow(
}
}
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MoreOptionTopAppBar(
text: String,
onNavigationIconClicked: () -> Unit,
bottomPadding: Dp,
) {
- TopAppBar(
- title = {
- LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp))
- },
- navigationIcon = {
- IconButton(
+ Row(
+ modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ IconButton(
modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 4.dp).size(48.dp),
onClick = onNavigationIconClicked
- ) {
- Box(
+ ) {
+ Box(
modifier = Modifier.size(48.dp),
contentAlignment = Alignment.Center,
- ) {
- Icon(
+ ) {
+ Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = stringResource(
- R.string.accessibility_back_arrow_button
+ R.string.accessibility_back_arrow_button
),
modifier = Modifier.size(24.dp).autoMirrored(),
tint = LocalAndroidColorScheme.current.onSurfaceVariant,
- )
- }
+ )
}
- },
- colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
- modifier = Modifier.padding(top = 12.dp, bottom = bottomPadding)
- )
+ }
+ LargeTitleText(text = text, modifier = Modifier.padding(horizontal = 4.dp))
+ }
}
private fun Modifier.autoMirrored() = composed {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 4ed84b908865..ccc46604ff98 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -554,15 +554,11 @@ fun CredentialEntryRow(
if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
else null,
entryHeadlineText = username,
- entrySecondLineText = if (
- credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
- "••••••••••••"
- } else {
- val itemsToDisplay = listOf(
+ entrySecondLineText = listOf(
displayName,
credentialEntryInfo.credentialTypeDisplayName,
credentialEntryInfo.providerDisplayName
- ).filterNot(TextUtils::isEmpty)
+ ).filterNot(TextUtils::isEmpty).let { itemsToDisplay ->
if (itemsToDisplay.isEmpty()) null
else itemsToDisplay.joinToString(
separator = stringResource(R.string.get_dialog_sign_in_type_username_separator)
@@ -653,4 +649,4 @@ fun EmptyAuthEntrySnackBarScreen(
contentText = stringResource(R.string.no_sign_in_info_in, lastLocked.providerDisplayName),
)
onLog(GetCredentialEvent.CREDMAN_GET_CRED_SCREEN_EMPTY_AUTH_SNACKBAR_SCREEN)
-} \ No newline at end of file
+}
diff --git a/packages/CredentialManager/tests/robotests/Android.bp b/packages/CredentialManager/tests/robotests/Android.bp
index baebfeb399f2..75a0dcce0b9e 100644
--- a/packages/CredentialManager/tests/robotests/Android.bp
+++ b/packages/CredentialManager/tests/robotests/Android.bp
@@ -37,7 +37,7 @@ android_robolectric_test {
":CredentialManagerScreenshotTestFiles",
],
- // Do not add any libraries here, instead add them to the ScreenshotTestStub
+ // Do not add any libraries here, instead add them to the ScreenshotTestRobo
static_libs: [
"androidx.compose.runtime_runtime",
"androidx.test.uiautomator_uiautomator",
@@ -45,6 +45,7 @@ android_robolectric_test {
"inline-mockito-robolectric-prebuilt",
"platform-parametric-runner-lib",
"uiautomator-helpers",
+ "flag-junit-base",
],
libs: [
"android.test.runner",
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_landscape_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_landscape_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 000000000000..81860e538275
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_landscape_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_portrait_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_portrait_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 000000000000..8c1fff7df8ab
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/phone/dark_portrait_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/phone/light_landscape_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/phone/light_landscape_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 000000000000..4eb025fcd190
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/phone/light_landscape_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/phone/light_portrait_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/phone/light_portrait_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 000000000000..c709f934f78d
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/phone/light_portrait_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_landscape_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_landscape_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 000000000000..278c13f6c7da
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_landscape_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_portrait_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_portrait_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 000000000000..cb85df350ccf
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/tablet/dark_portrait_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_landscape_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_landscape_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 000000000000..2eca70741a3c
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_landscape_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_portrait_singleCredentialScreen_newM3BottomSheet.png b/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_portrait_singleCredentialScreen_newM3BottomSheet.png
new file mode 100644
index 000000000000..7ee91b3705df
--- /dev/null
+++ b/packages/CredentialManager/tests/robotests/customization/assets/tablet/light_portrait_singleCredentialScreen_newM3BottomSheet.png
Binary files differ
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
index a0e1fed0ac96..e609d0c5c008 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
@@ -16,7 +16,10 @@
package com.android.credentialmanager
+import android.credentials.flags.Flags
import android.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.compose.ui.test.isPopup
import com.android.credentialmanager.getflow.RequestDisplayInfo
import com.android.credentialmanager.model.CredentialType
import com.android.credentialmanager.model.get.ProviderInfo
@@ -59,8 +62,11 @@ class GetCredScreenshotTest(emulationSpec: DeviceEmulationSpec) {
CredentialManagerGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
)
+ @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
@Test
- fun singleCredentialScreen() {
+ fun singleCredentialScreen_M3BottomSheetDisabled() {
+ setFlagsRule.disableFlags(Flags.FLAG_SELECTOR_UI_IMPROVEMENTS_ENABLED)
val providerInfoList = buildProviderInfoList()
val providerDisplayInfo = toProviderDisplayInfo(providerInfoList)
val activeEntry = toActiveEntry(providerDisplayInfo)
@@ -86,6 +92,39 @@ class GetCredScreenshotTest(emulationSpec: DeviceEmulationSpec) {
}
}
+ @Test
+ fun singleCredentialScreen_M3BottomSheetEnabled() {
+ setFlagsRule.enableFlags(Flags.FLAG_SELECTOR_UI_IMPROVEMENTS_ENABLED)
+ val providerInfoList = buildProviderInfoList()
+ val providerDisplayInfo = toProviderDisplayInfo(providerInfoList)
+ val activeEntry = toActiveEntry(providerDisplayInfo)
+ screenshotRule.screenshotTest(
+ "singleCredentialScreen_newM3BottomSheet",
+ // M3's ModalBottomSheet lives in a new window, meaning we have two windows with
+ // a root. Hence use a different matcher `isPopup`.
+ viewFinder = { screenshotRule.composeRule.onNode(isPopup()) },
+ ) {
+ ModalBottomSheet(
+ sheetContent = {
+ PrimarySelectionCard(
+ requestDisplayInfo = REQUEST_DISPLAY_INFO,
+ providerDisplayInfo = providerDisplayInfo,
+ providerInfoList = providerInfoList,
+ activeEntry = activeEntry,
+ onEntrySelected = {},
+ onConfirm = {},
+ onMoreOptionSelected = {},
+ onLog = {},
+ )
+ },
+ isInitialRender = true,
+ onDismiss = {},
+ onInitialRenderComplete = {},
+ isAutoSelectFlow = false,
+ )
+ }
+ }
+
private fun buildProviderInfoList(): List<ProviderInfo> {
val context = ApplicationProvider.getApplicationContext<Context>()
val provider1 = ProviderInfo(
diff --git a/packages/PackageInstaller/res/layout/install_content_view.xml b/packages/PackageInstaller/res/layout/install_content_view.xml
index 2ecd2d55ac71..524a88a638ad 100644
--- a/packages/PackageInstaller/res/layout/install_content_view.xml
+++ b/packages/PackageInstaller/res/layout/install_content_view.xml
@@ -24,114 +24,116 @@
android:paddingLeft="?android:attr/dialogPreferredPadding"
android:paddingRight="?android:attr/dialogPreferredPadding">
- <LinearLayout
- android:id="@+id/staging"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:visibility="invisible">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
- android:text="@string/message_staging" />
-
- <ProgressBar
- android:id="@+id/progress_indeterminate"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="8dp"
- style="?android:attr/progressBarStyleHorizontal"
- android:indeterminate="true" />
-
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/installing"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:visibility="invisible">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
- android:text="@string/installing" />
-
- <ProgressBar
- android:id="@+id/progress"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="8dp"
- style="?android:attr/progressBarStyleHorizontal"
- android:indeterminate="true" />
-
- </LinearLayout>
-
- <TextView
- android:id="@+id/install_confirm_question"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
- android:text="@string/install_confirm_question"
- android:visibility="invisible" />
-
- <TextView
- android:id="@+id/install_confirm_question_update"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
- android:text="@string/install_confirm_question_update"
- android:visibility="invisible" />
+ <LinearLayout
+ android:id="@+id/staging"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:visibility="invisible">
<TextView
- android:id="@+id/install_success"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@android:style/TextAppearance.Material.Subhead"
- android:text="@string/install_done"
- android:visibility="invisible" />
+ android:text="@string/message_staging" />
- <TextView
- android:id="@+id/install_failed"
- android:layout_width="wrap_content"
+ <ProgressBar
+ android:id="@+id/progress_indeterminate"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
- android:text="@string/install_failed"
- android:visibility="invisible" />
+ android:paddingTop="8dp"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:indeterminate="true" />
- <TextView
- android:id="@+id/install_failed_blocked"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
- android:text="@string/install_failed_blocked"
- android:visibility="invisible" />
+ </LinearLayout>
- <TextView
- android:id="@+id/install_failed_conflict"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
- android:text="@string/install_failed_conflict"
- android:visibility="invisible" />
+ <LinearLayout
+ android:id="@+id/installing"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:visibility="invisible">
<TextView
- android:id="@+id/install_failed_incompatible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@android:style/TextAppearance.Material.Subhead"
- android:text="@string/install_failed_incompatible"
- android:visibility="invisible" />
+ android:text="@string/installing" />
- <TextView
- android:id="@+id/install_failed_invalid_apk"
- android:layout_width="wrap_content"
+ <ProgressBar
+ android:id="@+id/progress"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead"
- android:text="@string/install_failed_invalid_apk"
- android:visibility="invisible" />
-
-</FrameLayout>
+ android:paddingTop="8dp"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:indeterminate="true" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/install_confirm_question"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.Material.Subhead"
+ android:text="@string/install_confirm_question"
+ android:visibility="invisible"
+ android:scrollbars="vertical" />
+
+ <TextView
+ android:id="@+id/install_confirm_question_update"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.Material.Subhead"
+ android:text="@string/install_confirm_question_update"
+ android:visibility="invisible"
+ android:scrollbars="vertical" />
+
+ <TextView
+ android:id="@+id/install_success"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.Material.Subhead"
+ android:text="@string/install_done"
+ android:visibility="invisible" />
+
+ <TextView
+ android:id="@+id/install_failed"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.Material.Subhead"
+ android:text="@string/install_failed"
+ android:visibility="invisible" />
+
+ <TextView
+ android:id="@+id/install_failed_blocked"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.Material.Subhead"
+ android:text="@string/install_failed_blocked"
+ android:visibility="invisible" />
+
+ <TextView
+ android:id="@+id/install_failed_conflict"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.Material.Subhead"
+ android:text="@string/install_failed_conflict"
+ android:visibility="invisible" />
+
+ <TextView
+ android:id="@+id/install_failed_incompatible"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.Material.Subhead"
+ android:text="@string/install_failed_incompatible"
+ android:visibility="invisible" />
+
+ <TextView
+ android:id="@+id/install_failed_invalid_apk"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.Material.Subhead"
+ android:text="@string/install_failed_invalid_apk"
+ android:visibility="invisible" />
+
+</FrameLayout> \ No newline at end of file
diff --git a/packages/PackageInstaller/res/layout/uninstall_content_view.xml b/packages/PackageInstaller/res/layout/uninstall_content_view.xml
index 5666c0e44e0b..434e33323ba1 100644
--- a/packages/PackageInstaller/res/layout/uninstall_content_view.xml
+++ b/packages/PackageInstaller/res/layout/uninstall_content_view.xml
@@ -18,31 +18,36 @@
<!-- Check box that is displayed in the activity resolver UI for the user
to make their selection the preferred activity. -->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:theme="?android:attr/alertDialogTheme"
- android:orientation="vertical"
- android:paddingTop="8dp"
- android:paddingStart="?android:attr/dialogPreferredPadding"
- android:paddingEnd="?android:attr/dialogPreferredPadding"
- android:clipToPadding="false">
+ android:layout_height="wrap_content">
- <TextView
- android:id="@+id/message"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@android:style/TextAppearance.Material.Subhead" />
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:theme="?android:attr/alertDialogTheme"
+ android:orientation="vertical"
+ android:paddingTop="8dp"
+ android:paddingStart="?android:attr/dialogPreferredPadding"
+ android:paddingEnd="?android:attr/dialogPreferredPadding"
+ android:clipToPadding="false">
- <CheckBox
- android:id="@+id/keepData"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:layout_marginStart="-8dp"
- android:paddingLeft="8sp"
- android:visibility="gone"
- style="@android:style/TextAppearance.Material.Subhead" />
+ <TextView
+ android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@android:style/TextAppearance.Material.Subhead" />
-</LinearLayout> \ No newline at end of file
+ <CheckBox
+ android:id="@+id/keepData"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginStart="-8dp"
+ android:paddingLeft="8sp"
+ android:visibility="gone"
+ style="@android:style/TextAppearance.Material.Subhead" />
+
+ </LinearLayout>
+</ScrollView>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index cf6aab641fc9..e95a8e63d644 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -51,6 +51,7 @@ import android.provider.Settings;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
+import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.Button;
@@ -174,6 +175,7 @@ public class PackageInstallerActivity extends Activity {
}
viewToEnable.setVisibility(View.VISIBLE);
+ viewToEnable.setMovementMethod(new ScrollingMovementMethod());
mEnableOk = true;
mOk.setEnabled(true);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
index dbe32cc42d1a..0a4aa48fc126 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.Html;
+import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -94,6 +95,7 @@ public class InstallConfirmationFragment extends DialogFragment {
viewToEnable = dialogView.requireViewById(R.id.install_confirm_question);
}
viewToEnable.setVisibility(View.VISIBLE);
+ viewToEnable.setMovementMethod(new ScrollingMovementMethod());
return mDialog;
}
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 463e9be391b6..335725cdad5e 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.7.0-alpha02"
+ extra["jetpackComposeVersion"] = "1.7.0-alpha03"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index fe378c27523c..609a82e79827 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -16,7 +16,7 @@
[versions]
agp = "8.2.2"
-compose-compiler = "1.5.9"
+compose-compiler = "1.5.10"
dexmaker-mockito = "2.28.3"
jvm = "17"
kotlin = "1.9.22"
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png
index a038779b0e79..e1f5c742dab7 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_landscape_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
index 03db688f6799..928e9268292b 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/phone/light_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
index 1345c379cfb0..05213688b0a8 100644
--- a/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
+++ b/packages/SettingsLib/Spa/screenshot/robotests/assets/tablet/dark_portrait_slider.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 2259bd74d56e..a193a2f9970d 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,7 +57,7 @@ dependencies {
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.2.0")
+ api("androidx.compose.material3:material3:1.3.0-alpha01")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/RadioPreferences.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/RadioPreferences.kt
new file mode 100644
index 000000000000..8300ce855988
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/RadioPreferences.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.settingslib.spa.widget.preference
+
+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.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.selectable
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.material3.RadioButton
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.widget.ui.CategoryTitle
+import com.android.settingslib.spa.widget.ui.SettingsListItem
+
+@Composable
+fun RadioPreferences(model: ListPreferenceModel) {
+ CategoryTitle(title = model.title)
+ Spacer(modifier = Modifier.width(SettingsDimension.itemDividerHeight))
+ Column(modifier = Modifier.selectableGroup()) {
+ for (option in model.options) {
+ Radio2(option, model.selectedId.intValue, model.enabled()) {
+ model.onIdSelected(it)
+ }
+ }
+ }
+}
+
+@Composable
+fun Radio2(
+ option: ListPreferenceOption,
+ selectedId: Int,
+ enabled: Boolean,
+ onIdSelected: (id: Int) -> Unit,
+) {
+ val selected = option.id == selectedId
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .selectable(
+ selected = selected,
+ enabled = enabled,
+ onClick = { onIdSelected(option.id) },
+ role = Role.RadioButton,
+ )
+ .padding(SettingsDimension.dialogItemPadding),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ RadioButton(selected = selected, onClick = null, enabled = enabled)
+ Spacer(modifier = Modifier.width(SettingsDimension.itemDividerHeight))
+ SettingsListItem(text = option.text, enabled = enabled)
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 4f6196648634..56534f41c3df 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -180,7 +180,7 @@ private class TopAppBarColors(
* @param colorTransitionFraction a `0.0` to `1.0` value that represents a color transition
* percentage
*/
- @Composable
+ @Stable
fun containerColor(colorTransitionFraction: Float): Color {
return lerp(
containerColor,
@@ -519,7 +519,7 @@ private fun TopAppBarLayout(
0
}
- val layoutHeight = if (heightPx.isNaN()) 0 else heightPx.roundToInt()
+ val layoutHeight = if (heightPx > 0) heightPx.roundToInt() else 0
layout(constraints.maxWidth, layoutHeight) {
// Navigation icon
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
index b4a6a0d00720..a59b95a60879 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt
@@ -66,6 +66,19 @@ fun SettingsDialogItem(text: String, enabled: Boolean = true) {
}
@Composable
+fun SettingsListItem(text: String, enabled: Boolean = true) {
+ Text(
+ text = text,
+ modifier = Modifier
+ .alphaForEnabled(enabled)
+ .padding(vertical = SettingsDimension.paddingTiny),
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.titleMedium,
+ overflow = TextOverflow.Ellipsis,
+ )
+}
+
+@Composable
fun SettingsBody(
body: String,
maxLines: Int = Int.MAX_VALUE,
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/RadioPreferencesTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/RadioPreferencesTest.kt
new file mode 100644
index 000000000000..2f98b02b8dd5
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/RadioPreferencesTest.kt
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.preference
+
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsNotSelected
+import androidx.compose.ui.test.assertIsSelectable
+import androidx.compose.ui.test.assertIsSelected
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RadioPreferencesTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ RadioPreferences(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val options = emptyList<ListPreferenceOption>()
+ override val selectedId = mutableIntStateOf(0)
+ override val onIdSelected: (Int) -> Unit = {}
+ }
+ })
+ }
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun item_displayed() {
+ val selectedId = mutableIntStateOf(1)
+ composeTestRule.setContent {
+ RadioPreferences(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val options = listOf(
+ ListPreferenceOption(id = 1, text = "A"),
+ ListPreferenceOption(id = 2, text = "B"),
+ )
+ override val selectedId = selectedId
+ override val onIdSelected = { id: Int -> selectedId.intValue = id }
+ }
+ })
+ }
+ composeTestRule.onNodeWithText("A").assertIsDisplayed()
+ composeTestRule.onNodeWithText("B").assertIsDisplayed()
+ }
+
+ @Test
+ fun item_selectable() {
+ val selectedId = mutableIntStateOf(1)
+ val enabledState = mutableStateOf(true)
+ composeTestRule.setContent {
+ RadioPreferences(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val enabled = { enabledState.value }
+ override val options = listOf(
+ ListPreferenceOption(id = 1, text = "A"),
+ ListPreferenceOption(id = 2, text = "B"),
+ )
+ override val selectedId = selectedId
+ override val onIdSelected = { id: Int -> selectedId.intValue = id }
+ }
+ })
+ }
+ composeTestRule.onNodeWithText("A").assertIsSelectable()
+ composeTestRule.onNodeWithText("B").assertIsSelectable()
+ }
+
+ @Test
+ fun item_single_selected() {
+ val selectedId = mutableIntStateOf(1)
+ val enabledState = mutableStateOf(true)
+ composeTestRule.setContent {
+ RadioPreferences(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val enabled = { enabledState.value }
+ override val options = listOf(
+ ListPreferenceOption(id = 1, text = "A"),
+ ListPreferenceOption(id = 2, text = "B"),
+ )
+ override val selectedId = selectedId
+ override val onIdSelected = { id: Int -> selectedId.intValue = id }
+ }
+ })
+ }
+ composeTestRule.onNodeWithText("B").assertIsSelectable()
+ composeTestRule.onNodeWithText("B").performClick()
+ composeTestRule.onNodeWithText("B").assertIsSelected()
+ composeTestRule.onNodeWithText("A").assertIsNotSelected()
+ composeTestRule.onNodeWithText("A").performClick()
+ composeTestRule.onNodeWithText("A").assertIsSelected()
+ composeTestRule.onNodeWithText("B").assertIsNotSelected()
+ }
+
+ @Test
+ fun select_itemDisabled() {
+ val selectedId = mutableIntStateOf(1)
+ val enabledState = mutableStateOf(true)
+ composeTestRule.setContent {
+ RadioPreferences(remember {
+ object : ListPreferenceModel {
+ override val title = TITLE
+ override val enabled = { enabledState.value }
+ override val options = listOf(
+ ListPreferenceOption(id = 1, text = "A"),
+ ListPreferenceOption(id = 2, text = "B"),
+ )
+ override val selectedId = selectedId
+ override val onIdSelected = { id: Int -> selectedId.intValue = id }
+ }
+ })
+ }
+ enabledState.value = false
+ composeTestRule.onNodeWithText("A").assertIsDisplayed().assertIsNotEnabled()
+ composeTestRule.onNodeWithText("B").assertIsDisplayed().assertIsNotEnabled()
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index 988afd70aaed..a395266ba5f9 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -26,6 +26,7 @@ import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.ResolveInfo
import android.os.SystemProperties
+import android.util.Log
import com.android.internal.R
import com.android.settingslib.spaprivileged.framework.common.userManager
import kotlinx.coroutines.async
@@ -85,19 +86,24 @@ class AppListRepositoryImpl(
userId: Int,
loadInstantApps: Boolean,
matchAnyUserForAdmin: Boolean,
- ): List<ApplicationInfo> = coroutineScope {
- val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() }
- val hideWhenDisabledPackagesDeferred = async {
- context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
- }
- val installedApplicationsAsUser =
- getInstalledApplications(userId, matchAnyUserForAdmin)
+ ): List<ApplicationInfo> = try {
+ coroutineScope {
+ val hiddenSystemModulesDeferred = async { packageManager.getHiddenSystemModules() }
+ val hideWhenDisabledPackagesDeferred = async {
+ context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
+ }
+ val installedApplicationsAsUser =
+ getInstalledApplications(userId, matchAnyUserForAdmin)
- val hiddenSystemModules = hiddenSystemModulesDeferred.await()
- val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
- installedApplicationsAsUser.filter { app ->
- app.isInAppList(loadInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
+ val hiddenSystemModules = hiddenSystemModulesDeferred.await()
+ val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
+ installedApplicationsAsUser.filter { app ->
+ app.isInAppList(loadInstantApps, hiddenSystemModules, hideWhenDisabledPackages)
+ }
}
+ } catch (e: Exception) {
+ Log.e(TAG, "loadApps failed", e)
+ emptyList()
}
private suspend fun getInstalledApplications(
@@ -210,6 +216,8 @@ class AppListRepositoryImpl(
}
companion object {
+ private const val TAG = "AppListRepository"
+
private fun ApplicationInfo.isInAppList(
showInstantApps: Boolean,
hiddenSystemModules: Set<String>,
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index efd53a4c9c23..c60ce41be87e 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -28,6 +28,8 @@ import android.content.pm.PackageManager.ResolveInfoFlags
import android.content.pm.ResolveInfo
import android.content.pm.UserInfo
import android.content.res.Resources
+import android.os.BadParcelableException
+import android.os.DeadObjectException
import android.os.UserManager
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.core.app.ApplicationProvider
@@ -44,6 +46,7 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
@@ -311,6 +314,19 @@ class AppListRepositoryTest {
}
@Test
+ fun loadApps_hasException_returnEmptyList() = runTest {
+ packageManager.stub {
+ on {
+ getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(ADMIN_USER_ID))
+ } doThrow BadParcelableException(DeadObjectException())
+ }
+
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
+
+ assertThat(appList).isEmpty()
+ }
+
+ @Test
fun showSystemPredicate_showSystem() = runTest {
val app = SYSTEM_APP
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 6a1ee3a3c623..54c5a14702f6 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -43,4 +43,11 @@ flag {
namespace: "pixel_cross_device_control"
description: "Gates whether to enable LE audio private broadcast sharing via QR code"
bug: "308368124"
-} \ No newline at end of file
+}
+
+flag {
+ name: "enable_hide_exclusively_managed_bluetooth_device"
+ namespace: "dck_framework"
+ description: "Hide exclusively managed Bluetooth devices in BT settings menu."
+ bug: "324475542"
+}
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml
new file mode 100644
index 000000000000..d9a417f1ea99
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar.xml
@@ -0,0 +1,37 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14dp"
+ android:height="14dp"
+ android:viewportWidth="14.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
new file mode 100644
index 000000000000..facc285a45ca
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_4_bar_error.xml
@@ -0,0 +1,28 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="15dp"
+ android:height="14dp"
+ android:viewportWidth="15.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml
new file mode 100644
index 000000000000..2c05a938c2cf
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar.xml
@@ -0,0 +1,41 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="16dp"
+ android:height="14dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml
new file mode 100644
index 000000000000..328e45ec7e19
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_0_5_bar_error.xml
@@ -0,0 +1,32 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="14dp"
+ android:viewportWidth="18.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M14,0.5C14,0.224 14.224,0 14.5,0H15.5C15.776,0 16,0.224 16,0.5V3H14V0.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0.5,11C0.224,11 0,11.224 0,11.5V13.5C0,13.776 0.224,14 0.5,14H1.5C1.776,14 2,13.776 2,13.5V11.5C2,11.224 1.776,11 1.5,11H0.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M4,8C3.724,8 3.5,8.224 3.5,8.5V13.5C3.5,13.776 3.724,14 4,14H5C5.276,14 5.5,13.776 5.5,13.5V8.5C5.5,8.224 5.276,8 5,8H4Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml
new file mode 100644
index 000000000000..b9054ba7a4e3
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar.xml
@@ -0,0 +1,36 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14dp"
+ android:height="14dp"
+ android:viewportWidth="14.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
new file mode 100644
index 000000000000..03a93491491c
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_4_bar_error.xml
@@ -0,0 +1,27 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="15dp"
+ android:height="14dp"
+ android:viewportWidth="15.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml
new file mode 100644
index 000000000000..774e91794df3
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar.xml
@@ -0,0 +1,40 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="16dp"
+ android:height="14dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml
new file mode 100644
index 000000000000..343ec1b2e50f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_1_5_bar_error.xml
@@ -0,0 +1,31 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="14dp"
+ android:viewportWidth="18.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml
new file mode 100644
index 000000000000..b699203dd652
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar.xml
@@ -0,0 +1,35 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14dp"
+ android:height="14dp"
+ android:viewportWidth="14.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml
new file mode 100644
index 000000000000..ba8649b23b38
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_2_4_bar_error.xml
@@ -0,0 +1,26 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="15dp"
+ android:height="14dp"
+ android:viewportWidth="15.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml
new file mode 100644
index 000000000000..43fa734c0c8d
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar.xml
@@ -0,0 +1,39 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="16dp"
+ android:height="14dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml
new file mode 100644
index 000000000000..6309e1772d4a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_2_5_bar_error.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="14dp"
+ android:viewportWidth="18.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml
new file mode 100644
index 000000000000..6a218b310b3a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar.xml
@@ -0,0 +1,34 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14dp"
+ android:height="14dp"
+ android:viewportWidth="14.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
new file mode 100644
index 000000000000..27433c79e8bb
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_4_bar_error.xml
@@ -0,0 +1,25 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="15dp"
+ android:height="14dp"
+ android:viewportWidth="15.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml
new file mode 100644
index 000000000000..158ae016ffb5
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar.xml
@@ -0,0 +1,38 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="16dp"
+ android:height="14dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml
new file mode 100644
index 000000000000..e0517cfdfeee
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_3_5_bar_error.xml
@@ -0,0 +1,29 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="14dp"
+ android:viewportWidth="18.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml
new file mode 100644
index 000000000000..1ebd3965f36f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14dp"
+ android:height="14dp"
+ android:viewportWidth="14.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M8.25,3L9.25,3A0.5,0.5 0,0 1,9.75 3.5L9.75,13.5A0.5,0.5 0,0 1,9.25 14L8.25,14A0.5,0.5 0,0 1,7.75 13.5L7.75,3.5A0.5,0.5 0,0 1,8.25 3z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11.75,0L12.75,0A0.5,0.5 0,0 1,13.25 0.5L13.25,13.5A0.5,0.5 0,0 1,12.75 14L11.75,14A0.5,0.5 0,0 1,11.25 13.5L11.25,0.5A0.5,0.5 0,0 1,11.75 0z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M1.25,10L2.25,10A0.5,0.5 0,0 1,2.75 10.5L2.75,13.5A0.5,0.5 0,0 1,2.25 14L1.25,14A0.5,0.5 0,0 1,0.75 13.5L0.75,10.5A0.5,0.5 0,0 1,1.25 10z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M4.75,6.5L5.75,6.5A0.5,0.5 0,0 1,6.25 7L6.25,13.5A0.5,0.5 0,0 1,5.75 14L4.75,14A0.5,0.5 0,0 1,4.25 13.5L4.25,7A0.5,0.5 0,0 1,4.75 6.5z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
new file mode 100644
index 000000000000..4473c29d0866
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_4_bar_error.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="15dp"
+ android:height="14dp"
+ android:viewportWidth="15.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7,3.5C7,3.224 7.224,3 7.5,3H8.5C8.776,3 9,3.224 9,3.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V3.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0,10.5C0,10.224 0.224,10 0.5,10H1.5C1.776,10 2,10.224 2,10.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V10.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M3.5,7C3.5,6.724 3.724,6.5 4,6.5H5C5.276,6.5 5.5,6.724 5.5,7V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V7Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11,0C10.724,0 10.5,0.224 10.5,0.5V3H12.5V0.5C12.5,0.224 12.276,0 12,0H11Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.25,13C12.25,12.448 12.698,12 13.25,12C13.802,12 14.25,12.448 14.25,13C14.25,13.552 13.802,14 13.25,14C12.698,14 12.25,13.552 12.25,13Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.25,5C12.25,4.724 12.474,4.5 12.75,4.5H13.75C14.026,4.5 14.25,4.724 14.25,5V10C14.25,10.276 14.026,10.5 13.75,10.5H12.75C12.474,10.5 12.25,10.276 12.25,10V5Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml
new file mode 100644
index 000000000000..1ed6ac86b21a
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar.xml
@@ -0,0 +1,37 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="16dp"
+ android:height="14dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml
new file mode 100644
index 000000000000..703e3acd5f75
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_4_5_bar_error.xml
@@ -0,0 +1,28 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="14dp"
+ android:viewportWidth="18.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml
new file mode 100644
index 000000000000..420ffb601e8f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar.xml
@@ -0,0 +1,36 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="16dp"
+ android:height="14dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7.5,5L8.5,5A0.5,0.5 0,0 1,9 5.5L9,13.5A0.5,0.5 0,0 1,8.5 14L7.5,14A0.5,0.5 0,0 1,7 13.5L7,5.5A0.5,0.5 0,0 1,7.5 5z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M11,2L12,2A0.5,0.5 0,0 1,12.5 2.5L12.5,13.5A0.5,0.5 0,0 1,12 14L11,14A0.5,0.5 0,0 1,10.5 13.5L10.5,2.5A0.5,0.5 0,0 1,11 2z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0.5,11L1.5,11A0.5,0.5 0,0 1,2 11.5L2,13.5A0.5,0.5 0,0 1,1.5 14L0.5,14A0.5,0.5 0,0 1,0 13.5L0,11.5A0.5,0.5 0,0 1,0.5 11z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.5,0L15.5,0A0.5,0.5 0,0 1,16 0.5L16,13.5A0.5,0.5 0,0 1,15.5 14L14.5,14A0.5,0.5 0,0 1,14 13.5L14,0.5A0.5,0.5 0,0 1,14.5 0z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M4,8L5,8A0.5,0.5 0,0 1,5.5 8.5L5.5,13.5A0.5,0.5 0,0 1,5 14L4,14A0.5,0.5 0,0 1,3.5 13.5L3.5,8.5A0.5,0.5 0,0 1,4 8z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml
new file mode 100644
index 000000000000..e63ca77e9db1
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_5_5_bar_error.xml
@@ -0,0 +1,27 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="14dp"
+ android:viewportWidth="18.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="M7,5.5C7,5.224 7.224,5 7.5,5H8.5C8.776,5 9,5.224 9,5.5V13.5C9,13.776 8.776,14 8.5,14H7.5C7.224,14 7,13.776 7,13.5V5.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.5,2.5C10.5,2.224 10.724,2 11,2H12C12.276,2 12.5,2.224 12.5,2.5V13.5C12.5,13.776 12.276,14 12,14H11C10.724,14 10.5,13.776 10.5,13.5V2.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M0,11.5C0,11.224 0.224,11 0.5,11H1.5C1.776,11 2,11.224 2,11.5V13.5C2,13.776 1.776,14 1.5,14H0.5C0.224,14 0,13.776 0,13.5V11.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M3.5,8.5C3.5,8.224 3.724,8 4,8H5C5.276,8 5.5,8.224 5.5,8.5V13.5C5.5,13.776 5.276,14 5,14H4C3.724,14 3.5,13.776 3.5,13.5V8.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.5,0C14.224,0 14,0.224 14,0.5V3H16V0.5C16,0.224 15.776,0 15.5,0H14.5Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M16,13C16,12.448 16.448,12 17,12C17.552,12 18,12.448 18,13C18,13.552 17.552,14 17,14C16.448,14 16,13.552 16,13Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M16,5C16,4.724 16.224,4.5 16.5,4.5H17.5C17.776,4.5 18,4.724 18,5V10C18,10.276 17.776,10.5 17.5,10.5H16.5C16.224,10.5 16,10.276 16,10V5Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_mobile_level_list.xml b/packages/SettingsLib/res/drawable/ic_mobile_level_list.xml
new file mode 100644
index 000000000000..6ec6793ea233
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_mobile_level_list.xml
@@ -0,0 +1,46 @@
+<?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
+ -->
+
+<!-- In order to pack the 0-4 and 0-5 ranges into a single element, we use a range offset. See
+SignalDrawable.java for usage. -->
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:minLevel="0" android:maxLevel="0" android:drawable="@drawable/ic_mobile_0_4_bar" />
+ <item android:minLevel="1" android:maxLevel="1" android:drawable="@drawable/ic_mobile_1_4_bar" />
+ <item android:minLevel="2" android:maxLevel="2" android:drawable="@drawable/ic_mobile_2_4_bar" />
+ <item android:minLevel="3" android:maxLevel="3" android:drawable="@drawable/ic_mobile_3_4_bar" />
+ <item android:minLevel="4" android:maxLevel="4" android:drawable="@drawable/ic_mobile_4_4_bar" />
+ <item android:minLevel="10" android:maxLevel="10" android:drawable="@drawable/ic_mobile_0_5_bar" />
+ <item android:minLevel="11" android:maxLevel="11" android:drawable="@drawable/ic_mobile_1_5_bar" />
+ <item android:minLevel="12" android:maxLevel="12" android:drawable="@drawable/ic_mobile_2_5_bar" />
+ <item android:minLevel="13" android:maxLevel="13" android:drawable="@drawable/ic_mobile_3_5_bar" />
+ <item android:minLevel="14" android:maxLevel="14" android:drawable="@drawable/ic_mobile_4_5_bar" />
+ <item android:minLevel="15" android:maxLevel="15" android:drawable="@drawable/ic_mobile_5_5_bar" />
+
+ <!-- Error states, so we don't have to draw them manually anymore -->
+ <item android:minLevel="20" android:maxLevel="20" android:drawable="@drawable/ic_mobile_0_4_bar_error" />
+ <item android:minLevel="21" android:maxLevel="21" android:drawable="@drawable/ic_mobile_1_4_bar_error" />
+ <item android:minLevel="22" android:maxLevel="22" android:drawable="@drawable/ic_mobile_2_4_bar_error" />
+ <item android:minLevel="23" android:maxLevel="23" android:drawable="@drawable/ic_mobile_3_4_bar_error" />
+ <item android:minLevel="24" android:maxLevel="24" android:drawable="@drawable/ic_mobile_4_4_bar_error" />
+
+ <item android:minLevel="30" android:maxLevel="30" android:drawable="@drawable/ic_mobile_0_5_bar_error" />
+ <item android:minLevel="31" android:maxLevel="31" android:drawable="@drawable/ic_mobile_1_5_bar_error" />
+ <item android:minLevel="32" android:maxLevel="32" android:drawable="@drawable/ic_mobile_2_5_bar_error" />
+ <item android:minLevel="33" android:maxLevel="33" android:drawable="@drawable/ic_mobile_3_5_bar_error" />
+ <item android:minLevel="34" android:maxLevel="34" android:drawable="@drawable/ic_mobile_4_5_bar_error" />
+ <item android:minLevel="35" android:maxLevel="35" android:drawable="@drawable/ic_mobile_5_5_bar_error" />
+</level-list>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_0.xml b/packages/SettingsLib/res/drawable/ic_wifi_0.xml
new file mode 100644
index 000000000000..8ff65540c505
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_0.xml
@@ -0,0 +1,37 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="13dp"
+ android:viewportWidth="18.0"
+ android:viewportHeight="13.0">
+ <path
+ android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_0_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_0_error.xml
new file mode 100644
index 000000000000..db31b9dd0fcd
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_0_error.xml
@@ -0,0 +1,28 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="13dp"
+ android:viewportWidth="17.0"
+ android:viewportHeight="13.0">
+ <path
+ android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_1.xml b/packages/SettingsLib/res/drawable/ic_wifi_1.xml
new file mode 100644
index 000000000000..e170f1dadc94
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_1.xml
@@ -0,0 +1,36 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="13dp"
+ android:viewportWidth="18.0"
+ android:viewportHeight="13.0">
+ <path
+ android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_1_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_1_error.xml
new file mode 100644
index 000000000000..a4d6a5c00b15
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_1_error.xml
@@ -0,0 +1,27 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="13dp"
+ android:viewportWidth="17.0"
+ android:viewportHeight="13.0">
+ <path
+ android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_2.xml b/packages/SettingsLib/res/drawable/ic_wifi_2.xml
new file mode 100644
index 000000000000..fc62267ad5b0
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_2.xml
@@ -0,0 +1,35 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="13dp"
+ android:viewportWidth="18.0"
+ android:viewportHeight="13.0">
+ <path
+ android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_2_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_2_error.xml
new file mode 100644
index 000000000000..65f40eff1ca8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_2_error.xml
@@ -0,0 +1,26 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="13dp"
+ android:viewportWidth="17.0"
+ android:viewportHeight="13.0">
+ <path
+ android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_3.xml b/packages/SettingsLib/res/drawable/ic_wifi_3.xml
new file mode 100644
index 000000000000..9079daf922b8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_3.xml
@@ -0,0 +1,34 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="13dp"
+ android:viewportWidth="18.0"
+ android:viewportHeight="13.0">
+ <path
+ android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z"
+ android:fillAlpha="0.24"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_3_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_3_error.xml
new file mode 100644
index 000000000000..940781bbb1ca
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_3_error.xml
@@ -0,0 +1,25 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="13dp"
+ android:viewportWidth="17.0"
+ android:viewportHeight="13.0">
+ <path
+ android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z"
+ android:fillAlpha="0.3"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_4.xml b/packages/SettingsLib/res/drawable/ic_wifi_4.xml
new file mode 100644
index 000000000000..6185e4a83332
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_4.xml
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="13dp"
+ android:viewportWidth="18.0"
+ android:viewportHeight="13.0">
+ <path
+ android:pathData="M0.523,3.314C0.32,3.502 0.32,3.819 0.516,4.015L1.223,4.722C1.418,4.917 1.734,4.916 1.938,4.73C5.936,1.09 12.066,1.09 16.064,4.73C16.268,4.916 16.584,4.917 16.779,4.722L17.486,4.015C17.682,3.819 17.682,3.502 17.479,3.314C12.698,-1.105 5.304,-1.105 0.523,3.314Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M15.011,6.49C15.207,6.294 15.207,5.976 15.002,5.792C11.592,2.736 6.411,2.736 3,5.792C2.795,5.976 2.795,6.294 2.991,6.49L3.698,7.197C3.893,7.392 4.209,7.39 4.417,7.209C7.042,4.93 10.96,4.93 13.585,7.209C13.793,7.39 14.109,7.392 14.304,7.197L15.011,6.49Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.465,8.964C5.27,8.769 5.269,8.45 5.481,8.273C7.515,6.576 10.487,6.576 12.521,8.273C12.733,8.45 12.732,8.769 12.537,8.964L11.83,9.672C11.634,9.867 11.319,9.863 11.099,9.698C9.859,8.767 8.143,8.767 6.904,9.698C6.683,9.863 6.368,9.867 6.173,9.672L5.465,8.964Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M10.062,11.439C10.257,11.244 10.259,10.92 10.022,10.779C9.395,10.407 8.608,10.407 7.98,10.779C7.743,10.92 7.745,11.244 7.94,11.439L8.647,12.146C8.843,12.342 9.159,12.342 9.355,12.146L10.062,11.439Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/drawable/ic_wifi_4_error.xml b/packages/SettingsLib/res/drawable/ic_wifi_4_error.xml
new file mode 100644
index 000000000000..715aaa0982e9
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_wifi_4_error.xml
@@ -0,0 +1,24 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="17dp"
+ android:height="13dp"
+ android:viewportWidth="17.0"
+ android:viewportHeight="13.0">
+ <path
+ android:pathData="M0.146,4.015C-0.05,3.819 -0.05,3.502 0.153,3.314C4.002,-0.244 9.545,-0.937 14.055,1.234C13.339,1.449 12.792,2.053 12.66,2.801C8.998,1.281 4.65,1.924 1.568,4.73C1.364,4.916 1.048,4.917 0.853,4.722L0.146,4.015Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M12.63,4.435C9.406,2.836 5.424,3.288 2.63,5.792C2.424,5.976 2.425,6.294 2.621,6.49L3.328,7.197C3.523,7.392 3.839,7.39 4.047,7.209C6.484,5.094 10.033,4.942 12.63,6.753V4.435Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M5.095,8.964C4.9,8.769 4.899,8.45 5.111,8.273C7.145,6.576 10.117,6.576 12.151,8.273C12.363,8.45 12.362,8.769 12.166,8.964L11.459,9.672C11.264,9.867 10.949,9.863 10.728,9.698C9.489,8.767 7.773,8.767 6.533,9.698C6.313,9.863 5.998,9.867 5.802,9.672L5.095,8.964Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M9.652,10.779C9.889,10.92 9.887,11.244 9.692,11.439L8.984,12.146C8.789,12.342 8.473,12.342 8.277,12.146L7.57,11.439C7.375,11.244 7.373,10.92 7.61,10.779C8.237,10.407 9.024,10.407 9.652,10.779Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.63,11.15C14.63,10.598 15.078,10.15 15.63,10.15C16.182,10.15 16.63,10.598 16.63,11.15C16.63,11.703 16.182,12.15 15.63,12.15C15.078,12.15 14.63,11.703 14.63,11.15Z"
+ android:fillColor="#000"/>
+ <path
+ android:pathData="M14.63,3.15C14.63,2.874 14.854,2.65 15.13,2.65H16.13C16.406,2.65 16.63,2.874 16.63,3.15V8.15C16.63,8.427 16.406,8.65 16.13,8.65H15.13C14.854,8.65 14.63,8.427 14.63,8.15V3.15Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 9588e502f28e..a4bc235aaa7a 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1085,7 +1085,7 @@
<!-- [CHAR_LIMIT=NONE] Label for charging on hold on main page of settings -->
<string name="power_charging_on_hold_settings_home_page"><xliff:g id="level">%1$s</xliff:g> - Charging on hold to protect battery</string>
<!-- [CHAR_LIMIT=NONE] Label for incompatible charging accessory on main page of settings -->
- <string name="power_incompatible_charging_settings_home_page"><xliff:g id="level">%1$s</xliff:g> - Checking charging accessory</string>
+ <string name="power_incompatible_charging_settings_home_page"><xliff:g id="level">%1$s</xliff:g> - Check charging accessory</string>
<!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging -->
<string name="power_remaining_duration_only">About <xliff:g id="time_remaining">%1$s</xliff:g> left</string>
<!-- [CHAR_LIMIT=40] Label for battery level chart when discharging with duration -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 6ee403d50751..bd27c896a3c4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -19,6 +19,7 @@ package com.android.settingslib.bluetooth;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCsipSetCoordinator;
@@ -35,6 +36,7 @@ import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProfile.ServiceListener;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Build;
@@ -52,6 +54,8 @@ import com.android.settingslib.R;
import com.google.common.collect.ImmutableList;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -71,6 +75,18 @@ import java.util.stream.Collectors;
* result callback.
*/
public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
+ public static final String ACTION_LE_AUDIO_SHARING_STATE_CHANGE =
+ "com.android.settings.action.BLUETOOTH_LE_AUDIO_SHARING_STATE_CHANGE";
+ public static final String EXTRA_LE_AUDIO_SHARING_STATE = "BLUETOOTH_LE_AUDIO_SHARING_STATE";
+ public static final int BROADCAST_STATE_UNKNOWN = 0;
+ public static final int BROADCAST_STATE_ON = 1;
+ public static final int BROADCAST_STATE_OFF = 2;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"BROADCAST_STATE_"},
+ value = {BROADCAST_STATE_UNKNOWN, BROADCAST_STATE_ON, BROADCAST_STATE_OFF})
+ public @interface BroadcastState {}
+ private static final String SETTINGS_PKG = "com.android.settings";
private static final String TAG = "LocalBluetoothLeBroadcast";
private static final boolean DEBUG = BluetoothUtils.D;
@@ -89,7 +105,6 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Settings.Secure.getUriFor(
Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY),
};
-
private final Context mContext;
private final CachedBluetoothDeviceManager mDeviceManager;
private BluetoothLeBroadcast mServiceBroadcast;
@@ -200,6 +215,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId);
}
setLatestBluetoothLeBroadcastMetadata(metadata);
+ notifyBroadcastStateChange(BROADCAST_STATE_ON);
}
@Override
@@ -212,7 +228,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
+ ", broadcastId = "
+ broadcastId);
}
-
+ notifyBroadcastStateChange(BROADCAST_STATE_OFF);
stopLocalSourceReceivers();
resetCacheInfo();
}
@@ -1005,10 +1021,6 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
/** Update fallback active device if needed. */
public void updateFallbackActiveDeviceIfNeeded() {
- if (!isEnabled(null)) {
- Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no ongoing broadcast");
- return;
- }
if (mServiceBroadcastAssistant == null) {
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null");
return;
@@ -1078,4 +1090,15 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
"bluetooth_le_broadcast_fallback_active_group_id",
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
}
+
+ private void notifyBroadcastStateChange(@BroadcastState int state) {
+ if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
+ Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
+ return;
+ }
+ Intent intent = new Intent(ACTION_LE_AUDIO_SHARING_STATE_CHANGE);
+ intent.putExtra(EXTRA_LE_AUDIO_SHARING_STATE, state);
+ intent.setPackage(mContext.getPackageName());
+ mContext.sendBroadcast(intent);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index 9a19f9368449..ef0f6cbc6ed9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -14,6 +14,8 @@
package com.android.settingslib.graph;
+import static com.android.settingslib.flags.Flags.newStatusBarIcons;
+
import android.animation.ArgbEvaluator;
import android.annotation.IntRange;
import android.content.Context;
@@ -67,6 +69,9 @@ public class SignalDrawable extends DrawableWrapper {
private static final long DOT_DELAY = 1000;
+ // Check the config for which icon we want to use
+ private static final int ICON_RES = SignalDrawable.getIconRes();
+
private final Paint mForegroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint mTransparentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final int mDarkModeFillColor;
@@ -85,7 +90,7 @@ public class SignalDrawable extends DrawableWrapper {
private int mCurrentDot;
public SignalDrawable(Context context) {
- super(context.getDrawable(com.android.internal.R.drawable.ic_signal_cellular));
+ super(context.getDrawable(ICON_RES));
final String attributionPathString = context.getString(
com.android.internal.R.string.config_signalAttributionPath);
mAttributionPath.set(PathParser.createPathFromPathData(attributionPathString));
@@ -147,9 +152,17 @@ public class SignalDrawable extends DrawableWrapper {
private int unpackLevel(int packedState) {
int numBins = (packedState & NUM_LEVEL_MASK) >> NUM_LEVEL_SHIFT;
+ int cutOutOffset = 0;
int levelOffset = numBins == (CellSignalStrength.getNumSignalStrengthLevels() + 1) ? 10 : 0;
int level = (packedState & LEVEL_MASK);
- return level + levelOffset;
+
+ if (newStatusBarIcons()) {
+ if (isInState(STATE_CUT)) {
+ cutOutOffset = 20;
+ }
+ }
+
+ return level + levelOffset + cutOutOffset;
}
public void setDarkIntensity(float darkIntensity) {
@@ -214,7 +227,7 @@ public class SignalDrawable extends DrawableWrapper {
drawDotAndPadding(x - dotSpacing * 2, y, dotPadding, dotSize, 0);
canvas.drawPath(mCutoutPath, mTransparentPaint);
canvas.drawPath(mForegroundPath, mForegroundPaint);
- } else if (isInState(STATE_CUT)) {
+ } else if (!newStatusBarIcons() && isInState(STATE_CUT)) {
float cutX = (mCutoutWidthFraction * width / VIEWPORT);
float cutY = (mCutoutHeightFraction * height / VIEWPORT);
mCutoutPath.moveTo(width, height);
@@ -300,4 +313,12 @@ public class SignalDrawable extends DrawableWrapper {
public static int getCarrierChangeState(int numLevels) {
return (STATE_CARRIER_CHANGE << STATE_SHIFT) | (numLevels << NUM_LEVEL_SHIFT);
}
+
+ private static int getIconRes() {
+ if (newStatusBarIcons()) {
+ return R.drawable.ic_mobile_level_list;
+ } else {
+ return com.android.internal.R.drawable.ic_signal_cellular;
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 50e2f9cb13f7..bdb58719b1a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -139,7 +139,6 @@ public abstract class InfoMediaManager extends MediaManager {
}
}
- @Override
public void startScan() {
mMediaDevices.clear();
startScanOnRouter();
@@ -156,7 +155,6 @@ public abstract class InfoMediaManager extends MediaManager {
}
}
- @Override
public abstract void stopScan();
protected abstract void startScanOnRouter();
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
index dfbf23f426fb..8bebd6ee3448 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
@@ -54,16 +54,6 @@ public abstract class MediaManager {
}
}
- /**
- * Start scan connected MediaDevice
- */
- public abstract void startScan();
-
- /**
- * Stop scan MediaDevice
- */
- public abstract void stopScan();
-
protected MediaDevice findMediaDevice(String id) {
for (MediaDevice mediaDevice : mMediaDevices) {
if (mediaDevice.getId().equals(id)) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
index 2a4658bc69a1..a5c63be3c987 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
@@ -18,33 +18,71 @@ package com.android.settingslib.media.data.repository
import android.media.AudioDeviceAttributes
import android.media.Spatializer
+import androidx.concurrent.futures.DirectExecutor
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
interface SpatializerRepository {
+ /** Returns true when head tracking is enabled and false the otherwise. */
+ val isHeadTrackingAvailable: StateFlow<Boolean>
+
/**
* Returns true when Spatial audio feature is supported for the [audioDeviceAttributes] and
* false the otherwise.
*/
- suspend fun isAvailableForDevice(audioDeviceAttributes: AudioDeviceAttributes): Boolean
+ suspend fun isSpatialAudioAvailableForDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ): Boolean
/** Returns a list [AudioDeviceAttributes] that are compatible with spatial audio. */
- suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes>
+ suspend fun getSpatialAudioCompatibleDevices(): Collection<AudioDeviceAttributes>
+
+ /** Adds a [audioDeviceAttributes] to [getSpatialAudioCompatibleDevices] list. */
+ suspend fun addSpatialAudioCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes)
+
+ /** Removes a [audioDeviceAttributes] from [getSpatialAudioCompatibleDevices] list. */
+ suspend fun removeSpatialAudioCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes)
- /** Adds a [audioDeviceAttributes] to [getCompatibleDevices] list. */
- suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes)
+ /** Checks if the head tracking is enabled for the [audioDeviceAttributes]. */
+ suspend fun isHeadTrackingEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean
- /** Removes a [audioDeviceAttributes] to [getCompatibleDevices] list. */
- suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes)
+ /** Sets head tracking [isEnabled] for the [audioDeviceAttributes]. */
+ suspend fun setHeadTrackingEnabled(
+ audioDeviceAttributes: AudioDeviceAttributes,
+ isEnabled: Boolean,
+ )
}
class SpatializerRepositoryImpl(
private val spatializer: Spatializer,
+ coroutineScope: CoroutineScope,
private val backgroundContext: CoroutineContext,
) : SpatializerRepository {
- override suspend fun isAvailableForDevice(
+ override val isHeadTrackingAvailable: StateFlow<Boolean> =
+ callbackFlow {
+ val listener =
+ Spatializer.OnHeadTrackerAvailableListener { _, available ->
+ launch { send(available) }
+ }
+ spatializer.addOnHeadTrackerAvailableListener(DirectExecutor.INSTANCE, listener)
+ awaitClose { spatializer.removeOnHeadTrackerAvailableListener(listener) }
+ }
+ .onStart { emit(spatializer.isHeadTrackerAvailable) }
+ .flowOn(backgroundContext)
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false)
+
+ override suspend fun isSpatialAudioAvailableForDevice(
audioDeviceAttributes: AudioDeviceAttributes
): Boolean {
return withContext(backgroundContext) {
@@ -52,18 +90,36 @@ class SpatializerRepositoryImpl(
}
}
- override suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes> =
+ override suspend fun getSpatialAudioCompatibleDevices(): Collection<AudioDeviceAttributes> =
withContext(backgroundContext) { spatializer.compatibleAudioDevices }
- override suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
+ override suspend fun addSpatialAudioCompatibleDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ) {
withContext(backgroundContext) {
spatializer.addCompatibleAudioDevice(audioDeviceAttributes)
}
}
- override suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
+ override suspend fun removeSpatialAudioCompatibleDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ) {
withContext(backgroundContext) {
spatializer.removeCompatibleAudioDevice(audioDeviceAttributes)
}
}
+
+ override suspend fun isHeadTrackingEnabled(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ): Boolean =
+ withContext(backgroundContext) { spatializer.isHeadTrackerEnabled(audioDeviceAttributes) }
+
+ override suspend fun setHeadTrackingEnabled(
+ audioDeviceAttributes: AudioDeviceAttributes,
+ isEnabled: Boolean,
+ ) {
+ withContext(backgroundContext) {
+ spatializer.setHeadTrackerEnabled(isEnabled, audioDeviceAttributes)
+ }
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
index c3cc340d9cd8..0347403cb385 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
@@ -18,22 +18,40 @@ package com.android.settingslib.media.domain.interactor
import android.media.AudioDeviceAttributes
import com.android.settingslib.media.data.repository.SpatializerRepository
+import kotlinx.coroutines.flow.StateFlow
class SpatializerInteractor(private val repository: SpatializerRepository) {
- suspend fun isAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
- repository.isAvailableForDevice(audioDeviceAttributes)
+ /** Checks if head tracking is available. */
+ val isHeadTrackingAvailable: StateFlow<Boolean>
+ get() = repository.isHeadTrackingAvailable
+
+ suspend fun isSpatialAudioAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+ repository.isSpatialAudioAvailableForDevice(audioDeviceAttributes)
/** Checks if spatial audio is enabled for the [audioDeviceAttributes]. */
- suspend fun isEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
- repository.getCompatibleDevices().contains(audioDeviceAttributes)
+ suspend fun isSpatialAudioEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+ repository.getSpatialAudioCompatibleDevices().contains(audioDeviceAttributes)
- /** Enblaes or disables spatial audio for [audioDeviceAttributes]. */
- suspend fun setEnabled(audioDeviceAttributes: AudioDeviceAttributes, isEnabled: Boolean) {
+ /** Enables or disables spatial audio for [audioDeviceAttributes]. */
+ suspend fun setSpatialAudioEnabled(
+ audioDeviceAttributes: AudioDeviceAttributes,
+ isEnabled: Boolean
+ ) {
if (isEnabled) {
- repository.addCompatibleDevice(audioDeviceAttributes)
+ repository.addSpatialAudioCompatibleDevice(audioDeviceAttributes)
} else {
- repository.removeCompatibleDevice(audioDeviceAttributes)
+ repository.removeSpatialAudioCompatibleDevice(audioDeviceAttributes)
}
}
+
+ /** Checks if head tracking is enabled for the [audioDeviceAttributes]. */
+ suspend fun isHeadTrackingEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+ repository.isHeadTrackingEnabled(audioDeviceAttributes)
+
+ /** Enables or disables head tracking for the [audioDeviceAttributes]. */
+ suspend fun setHeadTrackingEnabled(
+ audioDeviceAttributes: AudioDeviceAttributes,
+ isEnabled: Boolean,
+ ) = repository.setHeadTrackingEnabled(audioDeviceAttributes, isEnabled)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index b9a464752824..69f83a4dfa3c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -19,6 +19,8 @@ package com.android.settingslib.wifi;
import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason;
+import static com.android.settingslib.flags.Flags.newStatusBarIcons;
+
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
@@ -88,21 +90,49 @@ public class WifiUtils {
public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key";
public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
- static final int[] WIFI_PIE = {
- com.android.internal.R.drawable.ic_wifi_signal_0,
- com.android.internal.R.drawable.ic_wifi_signal_1,
- com.android.internal.R.drawable.ic_wifi_signal_2,
- com.android.internal.R.drawable.ic_wifi_signal_3,
- com.android.internal.R.drawable.ic_wifi_signal_4
- };
-
- static final int[] NO_INTERNET_WIFI_PIE = {
- R.drawable.ic_no_internet_wifi_signal_0,
- R.drawable.ic_no_internet_wifi_signal_1,
- R.drawable.ic_no_internet_wifi_signal_2,
- R.drawable.ic_no_internet_wifi_signal_3,
- R.drawable.ic_no_internet_wifi_signal_4
- };
+ static final int[] WIFI_PIE = getIconsBasedOnFlag();
+
+ private static int[] getIconsBasedOnFlag() {
+ if (newStatusBarIcons()) {
+ return new int[] {
+ R.drawable.ic_wifi_0,
+ R.drawable.ic_wifi_1,
+ R.drawable.ic_wifi_2,
+ R.drawable.ic_wifi_3,
+ R.drawable.ic_wifi_4
+ };
+ } else {
+ return new int[] {
+ com.android.internal.R.drawable.ic_wifi_signal_0,
+ com.android.internal.R.drawable.ic_wifi_signal_1,
+ com.android.internal.R.drawable.ic_wifi_signal_2,
+ com.android.internal.R.drawable.ic_wifi_signal_3,
+ com.android.internal.R.drawable.ic_wifi_signal_4
+ };
+ }
+ }
+
+ static final int[] NO_INTERNET_WIFI_PIE = getErrorIconsBasedOnFlag();
+
+ private static int [] getErrorIconsBasedOnFlag() {
+ if (newStatusBarIcons()) {
+ return new int[] {
+ R.drawable.ic_wifi_0_error,
+ R.drawable.ic_wifi_1_error,
+ R.drawable.ic_wifi_2_error,
+ R.drawable.ic_wifi_3_error,
+ R.drawable.ic_wifi_4_error
+ };
+ } else {
+ return new int[] {
+ R.drawable.ic_no_internet_wifi_signal_0,
+ R.drawable.ic_no_internet_wifi_signal_1,
+ R.drawable.ic_no_internet_wifi_signal_2,
+ R.drawable.ic_no_internet_wifi_signal_3,
+ R.drawable.ic_no_internet_wifi_signal_4
+ };
+ }
+ }
public static String buildLoggingSummary(AccessPoint accessPoint, WifiConfiguration config) {
final StringBuilder summary = new StringBuilder();
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt
deleted file mode 100644
index 3f52f2494dfc..000000000000
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/FakeSpatializerRepository.kt
+++ /dev/null
@@ -1,45 +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.settingslib.media.domain.interactor
-
-import android.media.AudioDeviceAttributes
-import com.android.settingslib.media.data.repository.SpatializerRepository
-
-class FakeSpatializerRepository : SpatializerRepository {
-
- private val availabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> = mutableMapOf()
- private val compatibleDevices: MutableList<AudioDeviceAttributes> = mutableListOf()
-
- override suspend fun isAvailableForDevice(
- audioDeviceAttributes: AudioDeviceAttributes
- ): Boolean = availabilityByDevice.getOrDefault(audioDeviceAttributes, false)
-
- override suspend fun getCompatibleDevices(): Collection<AudioDeviceAttributes> =
- compatibleDevices
-
- override suspend fun addCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
- compatibleDevices.add(audioDeviceAttributes)
- }
-
- override suspend fun removeCompatibleDevice(audioDeviceAttributes: AudioDeviceAttributes) {
- compatibleDevices.remove(audioDeviceAttributes)
- }
-
- fun setIsAvailable(audioDeviceAttributes: AudioDeviceAttributes, isAvailable: Boolean) {
- availabilityByDevice[audioDeviceAttributes] = isAvailable
- }
-}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt
deleted file mode 100644
index a44baeb174bf..000000000000
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/domain/interactor/SpatializerInteractorTest.kt
+++ /dev/null
@@ -1,56 +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.settingslib.media.domain.interactor
-
-import android.media.AudioDeviceAttributes
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class SpatializerInteractorTest {
-
- private val testScope = TestScope()
- private val underTest = SpatializerInteractor(FakeSpatializerRepository())
-
- @Test
- fun setEnabledFalse_isEnabled_false() {
- testScope.runTest {
- underTest.setEnabled(deviceAttributes, false)
-
- assertThat(underTest.isEnabled(deviceAttributes)).isFalse()
- }
- }
-
- @Test
- fun setEnabledTrue_isEnabled_true() {
- testScope.runTest {
- underTest.setEnabled(deviceAttributes, true)
-
- assertThat(underTest.isEnabled(deviceAttributes)).isTrue()
- }
- }
-
- private companion object {
- val deviceAttributes = AudioDeviceAttributes(0, 0, "test_device")
- }
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
index 37052673eb4d..70ba415abde5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/WifiMacAddressPreferenceControllerTest.java
@@ -20,9 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
import android.annotation.SuppressLint;
import android.content.Context;
@@ -94,19 +92,6 @@ public class WifiMacAddressPreferenceControllerTest {
}
@Test
- public void updateConnectivity_notAvailable_notCalled() {
- boolean mCalled = false;
- mController = spy(new ConcreteWifiMacAddressPreferenceController(mContext, mLifecycle) {
- @Override
- public boolean isAvailable() {
- return false;
- }
- });
- mController.displayPreference(mScreen);
- verify(mController, never()).updateConnectivity();
- }
-
- @Test
public void updateConnectivity_null_setMacUnavailable() {
doReturn(null).when(mWifiManager).getFactoryMacAddresses();
mController.displayPreference(mScreen);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
index 3b731921f201..46e724d245f5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
@@ -52,17 +52,7 @@ public class MediaManagerTest {
when(mDevice.getId()).thenReturn(TEST_ID);
- mMediaManager = new MediaManager(mContext, null) {
- @Override
- public void startScan() {
-
- }
-
- @Override
- public void stopScan() {
-
- }
- };
+ mMediaManager = new MediaManager(mContext, null) {};
}
@Test
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index d5814e3a9b79..94ea01607714 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -60,6 +60,7 @@ android_test {
// because this test is not an instrumentation test. (because the target runs in the system process.)
"SettingsProviderLib",
"androidx.test.rules",
+ "device_config_service_flags_java",
"flag-junit",
"junit",
"libaconfig_java_proto_lite",
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 1c9e748c5f3a..ce0257f6c85b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -359,6 +359,15 @@ final class SettingsState {
@VisibleForTesting
@GuardedBy("mLock")
+ public void addAconfigDefaultValuesFromMap(
+ @NonNull Map<String, Map<String, String>> defaultMap) {
+ if (mNamespaceDefaults != null) {
+ mNamespaceDefaults.putAll(defaultMap);
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
public static void loadAconfigDefaultValues(byte[] fileContents,
@NonNull Map<String, Map<String, String>> defaultMap) {
try {
@@ -510,6 +519,28 @@ final class SettingsState {
return false;
}
+ // Aconfig flags are always boot stable, so we anytime we write one, we staged it to be
+ // applied on reboot.
+ if (Flags.stageAllAconfigFlags() && mNamespaceDefaults != null) {
+ int slashIndex = name.indexOf("/");
+ boolean stageFlag = isConfigSettingsKey(mKey)
+ && slashIndex != -1
+ && slashIndex != 0
+ && slashIndex != name.length();
+
+ if (stageFlag) {
+ String namespace = name.substring(0, slashIndex);
+ String flag = name.substring(slashIndex + 1);
+
+ boolean isAconfig = mNamespaceDefaults.containsKey(namespace)
+ && mNamespaceDefaults.get(namespace).containsKey(name);
+
+ if (isAconfig) {
+ name = "staged/" + namespace + "*" + flag;
+ }
+ }
+ }
+
final boolean isNameTooLong = name.length() > SettingsState.MAX_LENGTH_PER_STRING;
final boolean isValueTooLong =
value != null && value.length() > SettingsState.MAX_LENGTH_PER_STRING;
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 ecac5ee18582..e5086e87173a 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
@@ -14,3 +14,14 @@ flag {
bug: "311155098"
is_fixed_read_only: true
}
+
+flag {
+ name: "stage_all_aconfig_flags"
+ namespace: "core_experiments_team_internal"
+ description: "Stage _all_ aconfig flags on writes, even local ones."
+ bug: "326598713"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 9ecbd50fc566..ea30c69b1c45 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -15,13 +15,25 @@
*/
package com.android.providers.settings;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
import android.aconfig.Aconfig;
import android.aconfig.Aconfig.parsed_flag;
import android.aconfig.Aconfig.parsed_flags;
import android.os.Looper;
-import android.test.AndroidTestCase;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Xml;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.modules.utils.TypedXmlSerializer;
import com.google.common.base.Strings;
@@ -34,7 +46,18 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-public class SettingsStateTest extends AndroidTestCase {
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SettingsStateTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
public static final String CRAZY_STRING =
"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000b\u000c\r" +
"\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a" +
@@ -76,25 +99,25 @@ public class SettingsStateTest extends AndroidTestCase {
private File mSettingsFile;
- @Override
- protected void setUp() {
- mSettingsFile = new File(getContext().getCacheDir(), "setting.xml");
+ @Before
+ public void setUp() {
+ mSettingsFile = new File(InstrumentationRegistry.getContext().getCacheDir(), "setting.xml");
mSettingsFile.delete();
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
if (mSettingsFile != null) {
mSettingsFile.delete();
}
- super.tearDown();
}
+ @Test
public void testLoadValidAconfigProto() {
int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
Object lock = new Object();
SettingsState settingsState = new SettingsState(
- getContext(), lock, mSettingsFile, configKey,
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
parsed_flags flags = parsed_flags
.newBuilder()
@@ -129,11 +152,12 @@ public class SettingsStateTest extends AndroidTestCase {
}
}
+ @Test
public void testSkipLoadingAconfigFlagWithMissingFields() {
int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
Object lock = new Object();
SettingsState settingsState = new SettingsState(
- getContext(), lock, mSettingsFile, configKey,
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
parsed_flags flags = parsed_flags
@@ -155,12 +179,97 @@ public class SettingsStateTest extends AndroidTestCase {
}
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_STAGE_ALL_ACONFIG_FLAGS)
+ public void testWritingAconfigFlagStages() {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+ Object lock = new Object();
+ SettingsState settingsState = new SettingsState(
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ parsed_flags flags = parsed_flags
+ .newBuilder()
+ .addParsedFlag(parsed_flag
+ .newBuilder()
+ .setPackage("com.android.flags")
+ .setName("flag5")
+ .setNamespace("test_namespace")
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .build();
+
+ synchronized (lock) {
+ Map<String, Map<String, String>> defaults = new HashMap<>();
+ settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
+ settingsState.addAconfigDefaultValuesFromMap(defaults);
+
+ settingsState.insertSettingLocked("test_namespace/com.android.flags.flag5",
+ "true", null, false, "com.android.flags");
+ settingsState.insertSettingLocked("test_namespace/com.android.flags.flag6",
+ "true", null, false, "com.android.flags");
+
+ assertEquals("true",
+ settingsState
+ .getSettingLocked("staged/test_namespace*com.android.flags.flag5")
+ .getValue());
+ assertEquals(null,
+ settingsState
+ .getSettingLocked("test_namespace/com.android.flags.flag5")
+ .getValue());
+
+ assertEquals(null,
+ settingsState
+ .getSettingLocked("staged/test_namespace*com.android.flags.flag6")
+ .getValue());
+ assertEquals("true",
+ settingsState
+ .getSettingLocked("test_namespace/com.android.flags.flag6")
+ .getValue());
+ }
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_LOAD_ACONFIG_DEFAULTS)
+ public void testAddingAconfigMapOnNullIsNoOp() {
+ int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
+ Object lock = new Object();
+ SettingsState settingsState = new SettingsState(
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+
+ parsed_flags flags = parsed_flags
+ .newBuilder()
+ .addParsedFlag(parsed_flag
+ .newBuilder()
+ .setPackage("com.android.flags")
+ .setName("flag5")
+ .setNamespace("test_namespace")
+ .setDescription("test flag")
+ .addBug("12345678")
+ .setState(Aconfig.flag_state.DISABLED)
+ .setPermission(Aconfig.flag_permission.READ_WRITE))
+ .build();
+
+ synchronized (lock) {
+ Map<String, Map<String, String>> defaults = new HashMap<>();
+ settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
+ settingsState.addAconfigDefaultValuesFromMap(defaults);
+
+ assertEquals(null, settingsState.getAconfigDefaultValues());
+ }
+
+ }
+
+ @Test
public void testInvalidAconfigProtoDoesNotCrash() {
Map<String, Map<String, String>> defaults = new HashMap<>();
SettingsState settingsState = getSettingStateObject();
settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes(), defaults);
}
+ @Test
public void testIsBinary() {
assertFalse(SettingsState.isBinary(" abc 日本語"));
@@ -191,6 +300,7 @@ public class SettingsStateTest extends AndroidTestCase {
}
/** Make sure we won't pass invalid characters to XML serializer. */
+ @Test
public void testWriteReadNoCrash() throws Exception {
ByteArrayOutputStream os = new ByteArrayOutputStream();
@@ -233,12 +343,15 @@ public class SettingsStateTest extends AndroidTestCase {
/**
* Make sure settings can be written to a file and also can be read.
*/
+ @Test
public void testReadWrite() {
final Object lock = new Object();
assertFalse(mSettingsFile.exists());
- final SettingsState ssWriter = new SettingsState(getContext(), lock, mSettingsFile, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ final SettingsState ssWriter =
+ new SettingsState(
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
ssWriter.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
ssWriter.insertSettingLocked("k1", "\u0000", null, false, "package");
@@ -250,8 +363,10 @@ public class SettingsStateTest extends AndroidTestCase {
}
ssWriter.waitForHandler();
assertTrue(mSettingsFile.exists());
- final SettingsState ssReader = new SettingsState(getContext(), lock, mSettingsFile, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ final SettingsState ssReader =
+ new SettingsState(
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
synchronized (lock) {
assertEquals("\u0000", ssReader.getSettingLocked("k1").getValue());
@@ -264,6 +379,7 @@ public class SettingsStateTest extends AndroidTestCase {
/**
* In version 120, value "null" meant {code NULL}.
*/
+ @Test
public void testUpgrade() throws Exception {
final Object lock = new Object();
final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
@@ -276,8 +392,10 @@ public class SettingsStateTest extends AndroidTestCase {
"</settings>");
os.close();
- final SettingsState ss = new SettingsState(getContext(), lock, mSettingsFile, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ final SettingsState ss =
+ new SettingsState(
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
synchronized (lock) {
SettingsState.Setting s;
s = ss.getSettingLocked("k0");
@@ -294,6 +412,7 @@ public class SettingsStateTest extends AndroidTestCase {
}
}
+ @Test
public void testInitializeSetting_preserveFlagNotSet() {
SettingsState settingsWriter = getSettingStateObject();
settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
@@ -304,6 +423,7 @@ public class SettingsStateTest extends AndroidTestCase {
assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
}
+ @Test
public void testModifySetting_preserveFlagSet() {
SettingsState settingsWriter = getSettingStateObject();
settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
@@ -315,6 +435,7 @@ public class SettingsStateTest extends AndroidTestCase {
assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
}
+ @Test
public void testModifySettingOverrideableByRestore_preserveFlagNotSet() {
SettingsState settingsWriter = getSettingStateObject();
settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
@@ -327,6 +448,7 @@ public class SettingsStateTest extends AndroidTestCase {
assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
}
+ @Test
public void testModifySettingOverrideableByRestore_preserveFlagAlreadySet_flagValueUnchanged() {
SettingsState settingsWriter = getSettingStateObject();
// Init the setting.
@@ -344,6 +466,7 @@ public class SettingsStateTest extends AndroidTestCase {
assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
}
+ @Test
public void testResetSetting_preservedFlagIsReset() {
SettingsState settingsState = getSettingStateObject();
// Initialize the setting.
@@ -356,6 +479,7 @@ public class SettingsStateTest extends AndroidTestCase {
}
+ @Test
public void testModifySettingBySystemPackage_sameValue_preserveFlagNotSet() {
SettingsState settingsState = getSettingStateObject();
// Initialize the setting.
@@ -366,6 +490,7 @@ public class SettingsStateTest extends AndroidTestCase {
assertFalse(settingsState.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
}
+ @Test
public void testModifySettingBySystemPackage_newValue_preserveFlagSet() {
SettingsState settingsState = getSettingStateObject();
// Initialize the setting.
@@ -377,12 +502,15 @@ public class SettingsStateTest extends AndroidTestCase {
}
private SettingsState getSettingStateObject() {
- SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ SettingsState settingsState =
+ new SettingsState(
+ InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
return settingsState;
}
+ @Test
public void testInsertSetting_memoryUsage() {
SettingsState settingsState = getSettingStateObject();
// No exception should be thrown when there is no cap
@@ -390,8 +518,10 @@ public class SettingsStateTest extends AndroidTestCase {
null, false, "p1");
settingsState.deleteSettingLocked(SETTING_NAME);
- settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+ settingsState =
+ new SettingsState(
+ InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
// System package doesn't have memory usage limit
settingsState.insertSettingLocked(SETTING_NAME, Strings.repeat("A", 20001),
null, false, SYSTEM_PACKAGE);
@@ -425,9 +555,12 @@ public class SettingsStateTest extends AndroidTestCase {
}
}
+ @Test
public void testMemoryUsagePerPackage() {
- SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+ SettingsState settingsState =
+ new SettingsState(
+ InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
// Test inserting one key with default
final String testKey1 = SETTING_NAME;
@@ -512,9 +645,12 @@ public class SettingsStateTest extends AndroidTestCase {
assertEquals(expectedMemUsage, settingsState.getMemoryUsage(TEST_PACKAGE));
}
+ @Test
public void testLargeSettingKey() {
- SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
+ SettingsState settingsState =
+ new SettingsState(
+ InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_LIMITED, Looper.getMainLooper());
final String largeKey = Strings.repeat("A", SettingsState.MAX_LENGTH_PER_STRING + 1);
final String testValue = "testValue";
synchronized (mLock) {
@@ -535,9 +671,12 @@ public class SettingsStateTest extends AndroidTestCase {
}
}
+ @Test
public void testLargeSettingValue() {
- SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ SettingsState settingsState =
+ new SettingsState(
+ InstrumentationRegistry.getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
final String testKey = "testKey";
final String largeValue = Strings.repeat("A", SettingsState.MAX_LENGTH_PER_STRING + 1);
synchronized (mLock) {
@@ -558,11 +697,12 @@ public class SettingsStateTest extends AndroidTestCase {
}
}
+ @Test
public void testApplyStagedConfigValues() {
int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
Object lock = new Object();
SettingsState settingsState = new SettingsState(
- getContext(), lock, mSettingsFile, configKey,
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
synchronized (lock) {
@@ -578,7 +718,8 @@ public class SettingsStateTest extends AndroidTestCase {
assertEquals(VALUE2, settingsState.getSettingLocked(FLAG_NAME_2).getValue());
}
- settingsState = new SettingsState(getContext(), lock, mSettingsFile, configKey,
+ settingsState = new SettingsState(
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
synchronized (lock) {
@@ -589,6 +730,7 @@ public class SettingsStateTest extends AndroidTestCase {
}
}
+ @Test
public void testStagingTransformation() {
assertEquals(INVALID_STAGED_FLAG_1,
SettingsState.createRealFlagName(INVALID_STAGED_FLAG_1));
@@ -603,11 +745,12 @@ public class SettingsStateTest extends AndroidTestCase {
SettingsState.createRealFlagName(VALID_STAGED_FLAG_1));
}
+ @Test
public void testInvalidStagedFlagsUnaffectedByReboot() {
int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
Object lock = new Object();
SettingsState settingsState = new SettingsState(
- getContext(), lock, mSettingsFile, configKey,
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
synchronized (lock) {
@@ -620,7 +763,8 @@ public class SettingsStateTest extends AndroidTestCase {
assertEquals(VALUE2, settingsState.getSettingLocked(INVALID_STAGED_FLAG_1).getValue());
}
- settingsState = new SettingsState(getContext(), lock, mSettingsFile, configKey,
+ settingsState = new SettingsState(
+ InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
synchronized (lock) {
@@ -628,6 +772,7 @@ public class SettingsStateTest extends AndroidTestCase {
}
}
+ @Test
public void testsetSettingsLockedKeepTrunkDefault() throws Exception {
final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
os.print(
@@ -648,7 +793,7 @@ public class SettingsStateTest extends AndroidTestCase {
int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
SettingsState settingsState = new SettingsState(
- getContext(), mLock, mSettingsFile, configKey,
+ InstrumentationRegistry.getContext(), mLock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
String prefix = "test_namespace";
@@ -705,6 +850,7 @@ public class SettingsStateTest extends AndroidTestCase {
}
}
+ @Test
public void testsetSettingsLockedNoTrunkDefault() throws Exception {
final PrintStream os = new PrintStream(new FileOutputStream(mSettingsFile));
os.print(
@@ -720,7 +866,7 @@ public class SettingsStateTest extends AndroidTestCase {
int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
SettingsState settingsState = new SettingsState(
- getContext(), mLock, mSettingsFile, configKey,
+ InstrumentationRegistry.getContext(), mLock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
Map<String, String> keyValues =
diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
index ea46c0cee6b9..ee81813b4245 100644
--- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
+++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java
@@ -300,6 +300,8 @@ public final class RingtonePickerActivity extends AlertActivity implements
}
};
installTask.execute(data.getData());
+ } else if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_CANCELED) {
+ setupAlert();
}
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index bed95a584c27..e63232a87499 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -230,6 +230,7 @@ android_library {
extra_check_modules: ["SystemUILintChecker"],
warning_checks: ["MissingApacheLicenseDetector"],
},
+ skip_jarjar_repackage: true,
}
filegroup {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a69a2a64dccb..ba7738005de2 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -542,6 +542,23 @@ flag {
namespace: "systemui"
description: "Binds Keyguard Media Controller Visibility to MediaContainerView"
bug: "298213983"
+}
+
+flag {
+ name: "delayed_wakelock_release_on_background_thread"
+ namespace: "systemui"
+ description: "Released delayed wakelocks on background threads to avoid janking screen transitions."
+ bug: "316128516"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "notify_power_manager_user_activity_background"
+ namespace: "systemui"
+ description: "Decide whether to notify the user activity to power manager in the background thread."
+ bug: "325203885"
metadata {
purpose: PURPOSE_BUGFIX
}
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index c1125f0b9e92..99b7c36d6fb9 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -45,6 +45,7 @@ android_library {
"androidx.core_core-ktx",
"androidx.annotation_annotation",
"SystemUIShaderLib",
+ "WindowManager-Shell-shared",
"animationlib",
],
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
index a78080fdabf2..e20425d4b98c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 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.
@@ -11,10 +11,10 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.shared.system;
+package com.android.systemui.animation;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.WindowManager.TRANSIT_CLOSE;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationTargetCompat.java
index e4d924323bbd..e251af44727e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationTargetCompat.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 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.shared.system;
+package com.android.systemui.animation;
import android.util.ArrayMap;
import android.view.RemoteAnimationTarget;
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
index 62dd4ac8c230..ef15c8461b95 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformSlider.kt
@@ -152,7 +152,10 @@ fun PlatformSlider(
modifier =
Modifier.fillMaxHeight()
.weight(1f)
- .padding(start = { paddingStart.roundToPx() }),
+ .padding(
+ start = { paddingStart.roundToPx() },
+ end = { sliderHeight.roundToPx() / 2 },
+ ),
contentAlignment = Alignment.CenterStart,
) {
labelComposable(isDragging)
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index c12084dac9a9..dfb3a55b0966 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -44,4 +44,5 @@ android_library {
],
kotlincflags: ["-Xjvm-default=all"],
+ skip_jarjar_repackage: true,
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index be5aa8a4c3b9..7535a51675e3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -9,7 +9,7 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
+import androidx.compose.ui.res.dimensionResource
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.FixedSizeEdgeDetector
@@ -29,6 +29,7 @@ import com.android.systemui.communal.shared.model.ObservableCommunalTransitionSt
import com.android.systemui.communal.ui.compose.extensions.allowGestures
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.res.R
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.transform
@@ -91,7 +92,10 @@ fun CommunalContainer(
SceneTransitionLayout(
state = sceneTransitionLayoutState,
modifier = modifier.fillMaxSize().allowGestures(allowed = touchesAllowed),
- swipeSourceDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
+ swipeSourceDetector =
+ FixedSizeEdgeDetector(
+ dimensionResource(id = R.dimen.communal_gesture_initiation_width)
+ ),
) {
scene(
TransitionSceneKey.Blank,
@@ -167,7 +171,3 @@ fun ObservableTransitionState.toModel(): ObservableCommunalTransitionState {
)
}
}
-
-object ContainerDimensions {
- val EdgeSwipeSize = 40.dp
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 622a4f04ee0d..078da1c863ce 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,6 +17,7 @@
package com.android.systemui.communal.ui.compose
import android.appwidget.AppWidgetHostView
+import android.graphics.drawable.Icon
import android.os.Bundle
import android.util.SizeF
import android.widget.FrameLayout
@@ -26,6 +27,7 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -77,6 +79,8 @@ import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
@@ -85,8 +89,10 @@ 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
+import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
@@ -101,6 +107,8 @@ import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+import com.android.internal.R.dimen.system_app_widget_background_radius
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
@@ -178,7 +186,7 @@ fun CommunalHub(
// not display this button.
if (
index == null ||
- communalContent[index].isWidget() ||
+ communalContent[index].isWidgetContent() ||
communalContent[index] is CommunalContentModel.CtaTileInViewMode
) {
isButtonToEditWidgetsShowing = true
@@ -274,7 +282,7 @@ private fun BoxScope.CommunalHubLazyGrid(
widgetConfigurator: WidgetConfigurator?,
) {
var gridModifier =
- Modifier.align(Alignment.CenterStart).onGloballyPositioned { setGridCoordinates(it) }
+ Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
@@ -309,8 +317,8 @@ private fun BoxScope.CommunalHubLazyGrid(
state = gridState,
rows = GridCells.Fixed(CommunalContentSize.FULL.span),
contentPadding = contentPadding,
- horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
- verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
+ horizontalArrangement = Arrangement.spacedBy(32.dp),
+ verticalArrangement = Arrangement.spacedBy(32.dp),
) {
items(
count = list.size,
@@ -330,7 +338,7 @@ private fun BoxScope.CommunalHubLazyGrid(
DraggableItem(
dragDropState = dragDropState,
selected = selected,
- enabled = list[index] is CommunalContentModel.Widget,
+ enabled = list[index].isWidgetContent(),
index = index,
) { isDragging ->
CommunalContent(
@@ -539,9 +547,11 @@ private fun CommunalContent(
widgetConfigurator: WidgetConfigurator? = null,
) {
when (model) {
- is CommunalContentModel.Widget ->
+ is CommunalContentModel.WidgetContent.Widget ->
WidgetContent(viewModel, model, size, selected, widgetConfigurator, modifier)
is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier)
+ is CommunalContentModel.WidgetContent.DisabledWidget ->
+ DisabledWidgetPlaceholder(model, modifier)
is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier)
is CommunalContentModel.CtaTileInEditMode ->
CtaTileInEditModeContent(modifier, onOpenWidgetPicker)
@@ -672,7 +682,7 @@ private fun CtaTileInEditModeContent(
@Composable
private fun WidgetContent(
viewModel: BaseCommunalViewModel,
- model: CommunalContentModel.Widget,
+ model: CommunalContentModel.WidgetContent.Widget,
size: SizeF,
selected: Boolean,
widgetConfigurator: WidgetConfigurator?,
@@ -681,19 +691,21 @@ private fun WidgetContent(
Box(
modifier = modifier,
) {
- val paddingInPx = with(LocalDensity.current) { CardOutlineWidth.toPx().toInt() }
+ val paddingInPx =
+ if (selected) with(LocalDensity.current) { CardOutlineWidth.toPx().toInt() } else 0
AndroidView(
modifier = Modifier.fillMaxSize().allowGestures(allowed = !viewModel.isEditMode),
factory = { context ->
- val view =
- model.appWidgetHost
- .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
- .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
+ model.appWidgetHost
+ .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
+ .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
+ },
+ update = { view ->
// Remove the extra padding applied to AppWidgetHostView to allow widgets to
- // occupy the entire box. The added padding is now adjusted to leave only sufficient
- // space for displaying the outline around the box when the widget is selected.
+ // occupy the entire box. The added padding is now adjusted to leave only
+ // sufficient space for displaying the outline around the box when the widget
+ // is selected.
view.setPadding(paddingInPx)
- view
},
// For reusing composition in lazy lists.
onReset = {},
@@ -716,7 +728,7 @@ private fun WidgetContent(
@Composable
fun WidgetConfigureButton(
visible: Boolean,
- model: CommunalContentModel.Widget,
+ model: CommunalContentModel.WidgetContent.Widget,
modifier: Modifier = Modifier,
widgetConfigurator: WidgetConfigurator,
) {
@@ -751,6 +763,38 @@ fun WidgetConfigureButton(
}
@Composable
+fun DisabledWidgetPlaceholder(
+ model: CommunalContentModel.WidgetContent.DisabledWidget,
+ modifier: Modifier = Modifier,
+) {
+ val context = LocalContext.current
+ val appInfo = model.appInfo
+ val icon: Icon =
+ if (appInfo == null || appInfo.icon == 0) {
+ Icon.createWithResource(context, android.R.drawable.sym_def_app_icon)
+ } else {
+ Icon.createWithResource(appInfo.packageName, appInfo.icon)
+ }
+
+ Column(
+ modifier =
+ modifier.background(
+ MaterialTheme.colorScheme.surfaceVariant,
+ RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+ ),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Image(
+ painter = rememberDrawablePainter(icon.loadDrawable(context)),
+ contentDescription = stringResource(R.string.icon_description_for_disabled_widget),
+ modifier = Modifier.size(48.dp),
+ colorFilter = ColorFilter.colorMatrix(Colors.DisabledColorFilter),
+ )
+ }
+}
+
+@Composable
private fun SmartspaceContent(
model: CommunalContentModel.Smartspace,
modifier: Modifier = Modifier,
@@ -795,7 +839,7 @@ private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier)
@Composable
private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues {
if (!isEditMode || toolbarSize == null) {
- return PaddingValues(horizontal = Dimensions.Spacing)
+ return PaddingValues(start = 48.dp, end = 48.dp, top = Dimensions.GridTopSpacing)
}
val configuration = LocalConfiguration.current
val density = LocalDensity.current
@@ -851,19 +895,20 @@ private fun firstIndexAtOffset(gridState: LazyGridState, offset: Offset): Int? =
/** Returns the key of item if it's editable at the given index. Only widget is editable. */
private fun keyAtIndexIfEditable(list: List<CommunalContentModel>, index: Int): String? =
- if (index in list.indices && list[index].isWidget()) list[index].key else null
+ if (index in list.indices && list[index].isWidgetContent()) list[index].key else null
data class ContentPaddingInPx(val start: Float, val top: Float) {
fun toOffset(): Offset = Offset(start, top)
}
object Dimensions {
- val CardWidth = 464.dp
- val CardHeightFull = 630.dp
- val CardHeightHalf = 307.dp
- val CardHeightThird = 199.dp
+ val CardWidth = 424.dp
+ val CardHeightFull = 596.dp
+ val CardHeightHalf = 282.dp
+ val CardHeightThird = 177.33.dp
val CardOutlineWidth = 3.dp
- val GridHeight = CardHeightFull
+ val GridTopSpacing = 72.dp
+ val GridHeight = CardHeightFull + GridTopSpacing
val Spacing = 16.dp
// The sizing/padding of the toolbar in glanceable hub edit mode
@@ -880,5 +925,30 @@ object Dimensions {
val IconSize = 48.dp
}
+private object Colors {
+ val DisabledColorFilter by lazy { disabledColorMatrix() }
+
+ /** Returns the disabled image filter. Ported over from [DisableImageView]. */
+ private fun disabledColorMatrix(): ColorMatrix {
+ val brightnessMatrix = ColorMatrix()
+ val brightnessAmount = 0.5f
+ val brightnessRgb = (255 * brightnessAmount).toInt().toFloat()
+ // Brightness: C-new = C-old*(1-amount) + amount
+ val scale = 1f - brightnessAmount
+ val mat = brightnessMatrix.values
+ mat[0] = scale
+ mat[6] = scale
+ mat[12] = scale
+ mat[4] = brightnessRgb
+ mat[9] = brightnessRgb
+ mat[14] = brightnessRgb
+
+ return ColorMatrix().apply {
+ setToSaturation(0F)
+ timesAssign(brightnessMatrix)
+ }
+ }
+}
+
/** The resource id of communal hub accessible from UiAutomator. */
private const val COMMUNAL_HUB_TEST_TAG = "communal_hub"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 9b8c9d0ab616..c5dab3347e5f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -71,8 +71,8 @@ internal constructor(
/** Remove widget from the list and the database. */
fun onRemove(indexToRemove: Int) {
- if (list[indexToRemove] is CommunalContentModel.Widget) {
- val widget = list[indexToRemove] as CommunalContentModel.Widget
+ if (list[indexToRemove].isWidgetContent()) {
+ val widget = list[indexToRemove] as CommunalContentModel.WidgetContent
list.apply { removeAt(indexToRemove) }
onDeleteWidget(widget.appWidgetId)
}
@@ -100,7 +100,7 @@ internal constructor(
val widgetIdToPriorityMap: Map<Int, Int> =
list
.mapIndexedNotNull { index, item ->
- if (item is CommunalContentModel.Widget) {
+ if (item is CommunalContentModel.WidgetContent) {
item.appWidgetId to list.size - index
} else {
null
@@ -115,5 +115,5 @@ internal constructor(
}
/** Returns true if the item at given index is editable. */
- fun isItemEditable(index: Int) = list[index] is CommunalContentModel.Widget
+ fun isItemEditable(index: Int) = list[index].isWidgetContent()
}
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 452dc03facfd..d23cd0c06aab 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
@@ -35,11 +35,13 @@ import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSect
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
@@ -63,6 +65,7 @@ constructor(
private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
+ private val mediaCarouselSection: MediaCarouselSection,
private val clockInteractor: KeyguardClockInteractor,
) : ComposableLockscreenSceneBlueprint {
@@ -112,10 +115,16 @@ constructor(
if (viewModel.isLargeClockVisible) {
Spacer(modifier = Modifier.weight(weight = 1f))
- with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) {
+ LargeClock(
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
}
- if (viewModel.areNotificationsVisible) {
+ with(mediaCarouselSection) { MediaCarousel() }
+
+ if (viewModel.areNotificationsVisible(resources = resources)) {
with(notificationSection) {
Notifications(
modifier = Modifier.fillMaxWidth().weight(weight = 1f)
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 71c60c70a655..c422c4b58b55 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
@@ -35,6 +35,7 @@ import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSect
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
@@ -63,6 +64,7 @@ constructor(
private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
+ private val mediaCarouselSection: MediaCarouselSection,
private val clockInteractor: KeyguardClockInteractor,
) : ComposableLockscreenSceneBlueprint {
@@ -115,7 +117,9 @@ constructor(
with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
}
- if (viewModel.areNotificationsVisible) {
+ with(mediaCarouselSection) { MediaCarousel() }
+
+ if (viewModel.areNotificationsVisible(resources = resources)) {
with(notificationSection) {
Notifications(
modifier = Modifier.fillMaxWidth().weight(weight = 1f)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index af836b68544c..d2184252102b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -41,6 +41,7 @@ import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSect
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
@@ -70,6 +71,7 @@ constructor(
private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
+ private val mediaCarouselSection: MediaCarouselSection,
private val clockInteractor: KeyguardClockInteractor,
private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
) : ComposableLockscreenSceneBlueprint {
@@ -100,6 +102,14 @@ constructor(
modifier = Modifier.fillMaxHeight().weight(weight = 1f),
horizontalAlignment = Alignment.CenterHorizontally,
) {
+ with(clockSection) {
+ SmallClock(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmallClockTopChanged,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+
with(smartSpaceSection) {
SmartSpace(
burnInParams = burnIn.parameters,
@@ -121,9 +131,13 @@ constructor(
)
}
- Spacer(modifier = Modifier.weight(weight = 1f))
- with(clockSection) { LargeClock() }
- Spacer(modifier = Modifier.weight(weight = 1f))
+ if (viewModel.isLargeClockVisible) {
+ Spacer(modifier = Modifier.weight(weight = 1f))
+ with(clockSection) { LargeClock() }
+ Spacer(modifier = Modifier.weight(weight = 1f))
+ }
+
+ with(mediaCarouselSection) { MediaCarousel() }
}
with(notificationSection) {
val splitShadeTopMargin: Dp =
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 e2e7a950cdfd..f86623fe935c 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
@@ -41,12 +41,14 @@ import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.MediaCarouselSection
import com.android.systemui.keyguard.ui.composable.section.NotificationSection
import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.composable.section.WeatherClockSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.res.R
import com.android.systemui.shade.LargeScreenHeaderHelper
import dagger.Binds
@@ -68,6 +70,7 @@ constructor(
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
private val clockInteractor: KeyguardClockInteractor,
+ private val mediaCarouselSection: MediaCarouselSection,
) : ComposableLockscreenSceneBlueprint {
override val id: String = WEATHER_CLOCK_BLUEPRINT_ID
@@ -107,7 +110,9 @@ constructor(
)
}
- if (viewModel.areNotificationsVisible) {
+ with(mediaCarouselSection) { MediaCarousel() }
+
+ if (viewModel.areNotificationsVisible(resources = resources)) {
with(notificationSection) {
Notifications(
modifier = Modifier.fillMaxWidth().weight(weight = 1f)
@@ -228,6 +233,7 @@ constructor(
private val clockInteractor: KeyguardClockInteractor,
private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
private val weatherClockSection: WeatherClockSection,
+ private val mediaCarouselSection: MediaCarouselSection,
) : ComposableLockscreenSceneBlueprint {
override val id: String = SPLIT_SHADE_WEATHER_CLOCK_BLUEPRINT_ID
@@ -276,6 +282,8 @@ constructor(
),
)
}
+
+ with(mediaCarouselSection) { MediaCarousel() }
}
with(notificationSection) {
val splitShadeTopMargin: Dp =
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 335c915411ee..152cc67f6c9e 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
@@ -39,7 +39,6 @@ import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChange
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
/** Provides small clock and large clock composables for the default clock face. */
@@ -49,7 +48,6 @@ constructor(
private val viewModel: KeyguardClockViewModel,
private val clockInteractor: KeyguardClockInteractor,
private val aodBurnInViewModel: AodBurnInViewModel,
- private val lockscreenSmartspaceController: LockscreenSmartspaceController,
) {
@Composable
@@ -62,15 +60,11 @@ constructor(
val currentClock by viewModel.currentClock.collectAsState()
viewModel.clock = currentClock
- if (clockSize != KeyguardClockSwitch.SMALL) {
+ if (clockSize != KeyguardClockSwitch.SMALL || currentClock?.smallClock?.view == null) {
onTopChanged(null)
return
}
- if (currentClock?.smallClock?.view == null) {
- return
- }
-
val view = LocalView.current
DisposableEffect(view) {
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 be6f022d8d52..31d3fa0be163 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
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.composable.section
import android.content.Context
import android.util.DisplayMetrics
+import android.view.View
import android.view.WindowManager
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@@ -45,6 +46,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
+import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import dagger.Lazy
import javax.inject.Inject
@@ -63,6 +65,7 @@ constructor(
private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
private val falsingManager: Lazy<FalsingManager>,
private val vibratorHelper: Lazy<VibratorHelper>,
+ private val notificationPanelView: NotificationPanelView,
) {
@Composable
fun SceneScope.LockIcon(modifier: Modifier = Modifier) {
@@ -70,6 +73,10 @@ constructor(
return
}
+ notificationPanelView.findViewById<View?>(R.id.lock_icon_view)?.let {
+ notificationPanelView.removeView(it)
+ }
+
val context = LocalContext.current
AndroidView(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt
new file mode 100644
index 000000000000..dae120cca981
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.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.keyguard.ui.composable.section
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.dimensionResource
+import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.viewmodel.MediaCarouselViewModel
+import com.android.systemui.media.controls.ui.composable.MediaCarousel
+import com.android.systemui.media.controls.ui.controller.MediaCarouselController
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.dagger.MediaModule
+import com.android.systemui.res.R
+import com.android.systemui.util.animation.MeasurementInput
+import javax.inject.Inject
+import javax.inject.Named
+
+class MediaCarouselSection
+@Inject
+constructor(
+ private val mediaCarouselController: MediaCarouselController,
+ @param:Named(MediaModule.KEYGUARD) private val mediaHost: MediaHost,
+ private val mediaCarouselViewModel: MediaCarouselViewModel,
+) {
+
+ @Composable
+ fun SceneScope.MediaCarousel(modifier: Modifier = Modifier) {
+ if (!mediaCarouselViewModel.isMediaVisible) {
+ return
+ }
+
+ if (mediaCarouselController.mediaFrame == null) {
+ return
+ }
+
+ val mediaHeight = dimensionResource(R.dimen.qs_media_session_height_expanded)
+ // TODO(b/312714128): MediaPlayer background size is not as expected.
+ MediaCarousel(
+ modifier =
+ modifier.height(mediaHeight).fillMaxWidth().onSizeChanged { size ->
+ // Notify controller to size the carousel for the
+ // current space
+ mediaHost.measurementInput = MeasurementInput(size.width, size.height)
+ mediaCarouselController.setSceneContainerSize(size.width, size.height)
+ },
+ mediaHost = mediaHost,
+ layoutWidth = 0, // Layout width is not used.
+ layoutHeight = with(LocalDensity.current) { mediaHeight.toPx() }.toInt(),
+ carouselController = mediaCarouselController,
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
index 61b2d4e26097..d3e4553be209 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt
@@ -16,9 +16,12 @@
package com.android.systemui.media.controls.ui.composable
+import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.view.contains
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.media.controls.ui.controller.MediaCarouselController
@@ -45,6 +48,20 @@ fun SceneScope.MediaCarousel(
AndroidView(
modifier = modifier.element(MediaCarousel.Elements.Content),
- factory = { _ -> carouselController.mediaFrame },
+ factory = { context ->
+ FrameLayout(context).apply {
+ val mediaFrame = carouselController.mediaFrame
+ (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame)
+ addView(mediaFrame)
+ }
+ },
+ update = {
+ if (it.contains(carouselController.mediaFrame)) {
+ return@AndroidView
+ }
+ val mediaFrame = carouselController.mediaFrame
+ (mediaFrame.parent as? ViewGroup)?.removeView(mediaFrame)
+ it.addView(mediaFrame)
+ },
)
}
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 66cef86fb773..6875bc544a55 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
@@ -40,7 +40,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -51,6 +50,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.TransitionState
@@ -62,6 +62,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
+import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.composable.ComposableScene
import com.android.systemui.scene.ui.composable.asComposeAware
@@ -168,7 +169,7 @@ private fun SceneScope.QuickSettingsScene(
modifier =
Modifier.element(Shade.Elements.BackgroundScrim)
.fillMaxSize()
- .background(MaterialTheme.colorScheme.scrim)
+ .background(colorResource(R.color.shade_scrim_background_dark))
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
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 2e0ce42ee713..8484b7f5273f 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
@@ -26,7 +26,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -37,6 +36,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
@@ -171,7 +171,7 @@ private fun SceneScope.ShadeScene(
modifier =
modifier
.element(Shade.Elements.BackgroundScrim)
- .background(MaterialTheme.colorScheme.scrim),
+ .background(colorResource(R.color.shade_scrim_background_dark)),
)
Box {
Layout(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
index d40126198c33..c08eb94f25c0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponent.kt
@@ -19,17 +19,17 @@ package com.android.systemui.volume.panel.component.bottombar.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import com.android.compose.PlatformButton
-import com.android.compose.PlatformOutlinedButton
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.bottombar.ui.viewmodel.BottomBarViewModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
@@ -47,11 +47,11 @@ constructor(
@Composable
override fun VolumePanelComposeScope.Content(modifier: Modifier) {
Row(
- modifier = modifier.height(if (isLargeScreen) 54.dp else 48.dp).fillMaxWidth(),
+ modifier = modifier.heightIn(min = if (isLargeScreen) 54.dp else 48.dp).fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
- PlatformOutlinedButton(
+ OutlinedButton(
onClick = viewModel::onSettingsClicked,
colors =
ButtonDefaults.outlinedButtonColors(
@@ -60,8 +60,8 @@ constructor(
) {
Text(text = stringResource(R.string.volume_panel_dialog_settings_button))
}
- PlatformButton(onClick = viewModel::onDoneClicked) {
- Text(text = stringResource(R.string.inline_done_button))
+ Button(onClick = viewModel::onDoneClicked) {
+ Text(stringResource(R.string.inline_done_button))
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index d49fed5d6e10..b3fcc305e6b5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -27,6 +27,7 @@ import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -46,7 +47,6 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.android.compose.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
@@ -78,8 +78,8 @@ constructor(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(28.dp),
onClick = { viewModel.onBarClick(it) },
- ) {
- Row {
+ ) { _ ->
+ Row(verticalAlignment = Alignment.CenterVertically) {
connectedDeviceViewModel?.let { ConnectedDeviceText(it) }
deviceIconViewModel?.let { ConnectedDeviceIcon(it) }
@@ -90,26 +90,23 @@ constructor(
@Composable
private fun RowScope.ConnectedDeviceText(connectedDeviceViewModel: ConnectedDeviceViewModel) {
Column(
- modifier =
- Modifier.weight(1f)
- .padding(start = 24.dp, top = 20.dp, bottom = 20.dp)
- .fillMaxHeight(),
+ modifier = Modifier.weight(1f).padding(start = 24.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
- connectedDeviceViewModel.label.toString(),
+ modifier = Modifier.basicMarquee(),
+ text = connectedDeviceViewModel.label.toString(),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
- overflow = TextOverflow.Ellipsis,
)
connectedDeviceViewModel.deviceName?.let {
Text(
- it.toString(),
+ modifier = Modifier.basicMarquee(),
+ text = it.toString(),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
- overflow = TextOverflow.Ellipsis,
)
}
}
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 a197a4b0fd55..4d810dfce89d 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
@@ -66,7 +66,7 @@ fun ColumnVolumeSliders(
modifier: Modifier = Modifier,
) {
require(viewModels.isNotEmpty())
- var isExpanded: Boolean by remember { mutableStateOf(false) }
+ var isExpanded: Boolean by remember(isExpandable) { mutableStateOf(!isExpandable) }
val transition = updateTransition(isExpanded, label = "CollapsableSliders")
Column(modifier = modifier) {
Row(
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 5925b1482e77..18a62dca3769 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
@@ -20,6 +20,7 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
+import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -41,7 +42,7 @@ fun VolumeSlider(
modifier: Modifier = Modifier,
sliderColors: PlatformSliderColors,
) {
- var value by remember { mutableFloatStateOf(state.value) }
+ var value by remember(state.value) { mutableFloatStateOf(state.value) }
PlatformSlider(
modifier = modifier,
value = value,
@@ -59,7 +60,12 @@ fun VolumeSlider(
colors = sliderColors,
label = {
Column(modifier = Modifier.animateContentSize()) {
- Text(state.label, style = MaterialTheme.typography.titleMedium)
+ Text(
+ modifier = Modifier.basicMarquee(),
+ text = state.label,
+ style = MaterialTheme.typography.titleMedium,
+ maxLines = 1,
+ )
state.disabledMessage?.let { message ->
AnimatedVisibility(
@@ -67,7 +73,12 @@ fun VolumeSlider(
enter = expandVertically { it },
exit = shrinkVertically { it },
) {
- Text(text = message, style = MaterialTheme.typography.bodySmall)
+ Text(
+ modifier = Modifier.basicMarquee(),
+ text = message,
+ style = MaterialTheme.typography.bodySmall,
+ maxLines = 1,
+ )
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
index 6213dc5f63c9..1ca18deeaac2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt
@@ -26,6 +26,7 @@ import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.Sl
import com.android.systemui.volume.panel.component.volume.ui.viewmodel.AudioVolumeComponentViewModel
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import com.android.systemui.volume.panel.ui.composable.isPortrait
import javax.inject.Inject
class VolumeSlidersComponent
@@ -50,7 +51,7 @@ constructor(
ColumnVolumeSliders(
viewModels = sliderViewModels,
sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(),
- isExpandable = true,
+ isExpandable = isPortrait,
modifier = modifier.fillMaxWidth(),
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
index 0a651c898aba..ac5004e16a3b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
@@ -21,7 +21,8 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -36,7 +37,7 @@ fun VolumePanelComposeScope.HorizontalVolumePanelContent(
val spacing = 20.dp
Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(space = spacing)) {
Column(
- modifier = Modifier.weight(1f),
+ modifier = Modifier.weight(1f).verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(spacing)
) {
for (component in layout.contentComponents) {
@@ -47,7 +48,7 @@ fun VolumePanelComposeScope.HorizontalVolumePanelContent(
}
Column(
- modifier = Modifier.weight(1f),
+ modifier = Modifier.weight(1f).verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(space = spacing, alignment = Alignment.Top)
) {
for (component in layout.headerComponents) {
@@ -56,7 +57,7 @@ fun VolumePanelComposeScope.HorizontalVolumePanelContent(
}
}
Row(
- modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(spacing),
) {
for (component in layout.footerComponents) {
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 4d073798c70c..dd767817a5ae 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
@@ -22,6 +22,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -33,7 +35,7 @@ fun VolumePanelComposeScope.VerticalVolumePanelContent(
modifier: Modifier = Modifier,
) {
Column(
- modifier = modifier,
+ modifier = modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(20.dp),
) {
for (component in layout.headerComponents) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
index 8a1e6a8b74f4..910cd5ec107b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt
@@ -16,12 +16,13 @@
package com.android.systemui.volume.panel.ui.composable
-import android.content.res.Configuration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
@@ -37,13 +38,17 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.max
import com.android.compose.theme.PlatformTheme
import com.android.systemui.res.R
import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import kotlin.math.max
private val padding = 24.dp
@@ -66,12 +71,12 @@ fun VolumePanelRoot(
val components by viewModel.componentsLayout.collectAsState(null)
with(VolumePanelComposeScope(state)) {
- var boxModifier = modifier.fillMaxSize().clickable(onClick = onDismiss)
- if (!isPortrait) {
- boxModifier = boxModifier.padding(horizontal = 48.dp)
- }
Box(
- modifier = boxModifier,
+ modifier =
+ modifier
+ .fillMaxSize()
+ .clickable(onClick = onDismiss)
+ .volumePanelPaddings(isPortrait = isPortrait),
contentAlignment = Alignment.BottomCenter,
) {
val radius = dimensionResource(R.dimen.volume_panel_corner_radius)
@@ -81,8 +86,8 @@ fun VolumePanelRoot(
interactionSource = null,
indication = null,
onClick = {
- // prevent windowCloseOnTouchOutside from dismissing when tapped on
- // the panel itself.
+ // prevent windowCloseOnTouchOutside from dismissing when tapped
+ // on the panel itself.
},
),
shape = RoundedCornerShape(topStart = radius, topEnd = radius),
@@ -111,17 +116,31 @@ private fun VolumePanelComposeScope.Components(
layout: ComponentsLayout,
modifier: Modifier = Modifier
) {
- var columnModifier = modifier.widthIn(max = 800.dp)
- if (!isLargeScreen && orientation != Configuration.ORIENTATION_PORTRAIT) {
- columnModifier = columnModifier.heightIn(max = 332.dp)
- }
- Column(modifier = columnModifier, verticalArrangement = Arrangement.spacedBy(padding)) {
- if (orientation == Configuration.ORIENTATION_PORTRAIT || isLargeScreen) {
- VerticalVolumePanelContent(layout)
+ val arrangement: Arrangement.Vertical =
+ if (isLargeScreen) {
+ Arrangement.spacedBy(20.dp)
} else {
- HorizontalVolumePanelContent(layout)
+ if (isPortrait) Arrangement.spacedBy(padding) else Arrangement.spacedBy(4.dp)
}
- BottomBar(layout = layout, modifier = Modifier)
+ Column(
+ modifier = modifier.widthIn(max = 800.dp),
+ verticalArrangement = arrangement,
+ ) {
+ if (isPortrait || isLargeScreen) {
+ VerticalVolumePanelContent(
+ modifier = Modifier.weight(weight = 1f, fill = false),
+ layout = layout
+ )
+ } else {
+ HorizontalVolumePanelContent(
+ modifier = Modifier.weight(weight = 1f, fill = false).heightIn(max = 212.dp),
+ layout = layout,
+ )
+ }
+ BottomBar(
+ modifier = Modifier,
+ layout = layout,
+ )
}
}
@@ -141,3 +160,28 @@ private fun VolumePanelComposeScope.BottomBar(
}
}
}
+
+/**
+ * Makes sure volume panel stays symmetrically in the middle of the screen while still avoiding
+ * being under the cutouts.
+ */
+@Composable
+private fun Modifier.volumePanelPaddings(isPortrait: Boolean): Modifier {
+ val cutout = WindowInsets.displayCutout
+ return with(LocalDensity.current) {
+ val horizontalCutout =
+ max(
+ cutout.getLeft(density = this, layoutDirection = LocalLayoutDirection.current),
+ cutout.getRight(density = this, layoutDirection = LocalLayoutDirection.current)
+ )
+ val minHorizontalPadding = if (isPortrait) 0.dp else 48.dp
+ val horizontalPadding = max(horizontalCutout.toDp(), minHorizontalPadding)
+
+ padding(
+ start = horizontalPadding,
+ top = cutout.getTop(this).toDp(),
+ end = horizontalPadding,
+ bottom = cutout.getBottom(this).toDp(),
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index c40856061152..b94e49bb0edc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -36,44 +36,38 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
-internal class SceneGestureHandler(
- internal val layoutImpl: SceneTransitionLayoutImpl,
- internal val orientation: Orientation,
- private val coroutineScope: CoroutineScope,
-) {
- private val layoutState = layoutImpl.state
- val draggable: DraggableHandler = SceneDraggableHandler(this)
-
- private var _swipeTransition: SwipeTransition? = null
- private var swipeTransition: SwipeTransition
- get() = _swipeTransition ?: error("SwipeTransition needs to be initialized")
- set(value) {
- _swipeTransition = value
- }
+interface DraggableHandler {
+ /**
+ * Start a drag in the given [startedPosition], with the given [overSlop] and number of
+ * [pointersDown].
+ *
+ * The returned [DragController] should be used to continue or stop the drag.
+ */
+ fun onDragStarted(startedPosition: Offset?, overSlop: Float, pointersDown: Int): DragController
+}
- private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
- if (isDrivingTransition || force) {
- layoutState.startTransition(newTransition, newTransition.key)
+/**
+ * The [DragController] provides control over the transition between two scenes through the [onDrag]
+ * and [onStop] methods.
+ */
+interface DragController {
+ /** Drag the current scene by [delta] pixels. */
+ fun onDrag(delta: Float)
- // Initialize SwipeTransition.transformationSpec and .swipeSpec. Note that this must be
- // called right after layoutState.startTransition() is called, because it computes the
- // current layoutState.transformationSpec().
- val transformationSpec = layoutState.transformationSpec
- newTransition.transformationSpec = transformationSpec
- newTransition.swipeSpec =
- transformationSpec.swipeSpec ?: layoutState.transitions.defaultSwipeSpec
- } else {
- // We were not driving the transition and we don't force the update, so the specs won't
- // be used and it doesn't matter which ones we set here.
- newTransition.transformationSpec = TransformationSpec.Empty
- newTransition.swipeSpec = SceneTransitions.DefaultSwipeSpec
- }
+ /** Starts a transition to a target scene. */
+ fun onStop(velocity: Float, canChangeScene: Boolean)
+}
- swipeTransition = newTransition
- }
+internal class DraggableHandlerImpl(
+ internal val layoutImpl: SceneTransitionLayoutImpl,
+ internal val orientation: Orientation,
+ internal val coroutineScope: CoroutineScope,
+) : DraggableHandler {
+ /** The [DraggableHandler] can only have one active [DragController] at a time. */
+ private var dragController: DragControllerImpl? = null
- internal val isDrivingTransition
- get() = layoutState.transitionState == _swipeTransition
+ internal val isDrivingTransition: Boolean
+ get() = dragController?.isDrivingTransition == true
/**
* The velocity threshold at which the intent of the user is to swipe up or down. It is the same
@@ -86,14 +80,9 @@ internal class SceneGestureHandler(
* The positional threshold at which the intent of the user is to swipe to the next scene. It is
* the same as SwipeableV2Defaults.PositionalThreshold.
*/
- private val positionalThreshold
+ internal val positionalThreshold
get() = with(layoutImpl.density) { 56.dp.toPx() }
- internal var currentSource: Any? = null
-
- /** The [Swipes] associated to the current gesture. */
- private var swipes: Swipes? = null
-
/**
* Whether we should immediately intercept a gesture.
*
@@ -102,35 +91,52 @@ internal class SceneGestureHandler(
*/
internal fun shouldImmediatelyIntercept(startedPosition: Offset?): Boolean {
// We don't intercept the touch if we are not currently driving the transition.
- if (!isDrivingTransition) {
+ val dragController = dragController
+ if (dragController?.isDrivingTransition != true) {
return false
}
// Only intercept the current transition if one of the 2 swipes results is also a transition
// between the same pair of scenes.
+ val swipeTransition = dragController.swipeTransition
val fromScene = swipeTransition._currentScene
val swipes = computeSwipes(fromScene, startedPosition, pointersDown = 1)
- val (upOrLeft, downOrRight) = computeSwipesResults(fromScene, swipes)
+ val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromScene)
return (upOrLeft != null &&
swipeTransition.isTransitioningBetween(fromScene.key, upOrLeft.toScene)) ||
(downOrRight != null &&
swipeTransition.isTransitioningBetween(fromScene.key, downOrRight.toScene))
}
- internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) {
+ override fun onDragStarted(
+ startedPosition: Offset?,
+ overSlop: Float,
+ pointersDown: Int,
+ ): DragController {
if (overSlop == 0f) {
- check(isDrivingTransition) {
- "onDragStarted() called while isDrivingTransition=false overSlop=0f"
+ val oldDragController = dragController
+ check(oldDragController != null && oldDragController.isDrivingTransition) {
+ val isActive = oldDragController?.isDrivingTransition
+ "onDragStarted(overSlop=0f) requires an active dragController, but was $isActive"
}
// This [transition] was already driving the animation: simply take over it.
// Stop animating and start from where the current offset.
- swipeTransition.cancelOffsetAnimation()
- swipes!!.updateSwipesResults(swipeTransition._fromScene)
- return
+ oldDragController.swipeTransition.cancelOffsetAnimation()
+
+ // We need to recompute the swipe results since this is a new gesture, and the
+ // fromScene.userActions may have changed.
+ val swipes = oldDragController.swipes
+ swipes.updateSwipesResults(oldDragController.swipeTransition._fromScene)
+
+ // A new gesture should always create a new SwipeTransition. This way there cannot be
+ // different gestures controlling the same transition.
+ val swipeTransition = SwipeTransition(oldDragController.swipeTransition)
+ swipes.updateSwipesResults(fromScene = swipeTransition._fromScene)
+ return updateDragController(swipes, swipeTransition)
}
- val transitionState = layoutState.transitionState
+ val transitionState = layoutImpl.state.transitionState
if (transitionState is TransitionState.Transition) {
// TODO(b/290184746): Better handle interruptions here if state != idle.
Log.w(
@@ -142,24 +148,27 @@ internal class SceneGestureHandler(
}
val fromScene = layoutImpl.scene(transitionState.currentScene)
- val newSwipes = computeSwipes(fromScene, startedPosition, pointersDown)
- swipes = newSwipes
- val result = newSwipes.findUserActionResult(fromScene, overSlop, true)
+ val swipes = computeSwipes(fromScene, startedPosition, pointersDown)
+ val result = swipes.findUserActionResult(fromScene, overSlop, true)
// As we were unable to locate a valid target scene, the initial SwipeTransition cannot be
- // defined.
- if (result == null) return
+ // defined. Consequently, a simple NoOp Controller will be returned.
+ if (result == null) return NoOpDragController
- val newSwipeTransition =
- SwipeTransition(
- fromScene = fromScene,
- result = result,
- swipes = newSwipes,
- layoutImpl = layoutImpl,
- orientation = orientation
- )
+ return updateDragController(
+ swipes = swipes,
+ swipeTransition = SwipeTransition(fromScene, result, swipes, layoutImpl, orientation)
+ )
+ }
- updateTransition(newSwipeTransition, force = true)
+ private fun updateDragController(
+ swipes: Swipes,
+ swipeTransition: SwipeTransition
+ ): DragController {
+ val newDragController = DragControllerImpl(this, swipes, swipeTransition)
+ newDragController.updateTransition(swipeTransition, force = true)
+ dragController = newDragController
+ return newDragController
}
private fun computeSwipes(
@@ -216,7 +225,58 @@ internal class SceneGestureHandler(
}
}
- internal fun onDrag(delta: Float) {
+ companion object {
+ private const val TAG = "DraggableHandlerImpl"
+ }
+}
+
+/** @param swipes The [Swipes] associated to the current gesture. */
+private class DragControllerImpl(
+ private val draggableHandler: DraggableHandlerImpl,
+ val swipes: Swipes,
+ var swipeTransition: SwipeTransition,
+) : DragController {
+ val layoutState = draggableHandler.layoutImpl.state
+
+ /**
+ * Whether this handle is active. If this returns false, calling [onDrag] and [onStop] will do
+ * nothing. We should have only one active controller at a time
+ */
+ val isDrivingTransition: Boolean
+ get() = layoutState.transitionState == swipeTransition
+
+ init {
+ check(!isDrivingTransition) { "Multiple controllers with the same SwipeTransition" }
+ }
+
+ fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
+ if (isDrivingTransition || force) {
+ layoutState.startTransition(newTransition, newTransition.key)
+
+ // Initialize SwipeTransition.transformationSpec and .swipeSpec. Note that this must be
+ // called right after layoutState.startTransition() is called, because it computes the
+ // current layoutState.transformationSpec().
+ val transformationSpec = layoutState.transformationSpec
+ newTransition.transformationSpec = transformationSpec
+ newTransition.swipeSpec =
+ transformationSpec.swipeSpec ?: layoutState.transitions.defaultSwipeSpec
+ } else {
+ // We were not driving the transition and we don't force the update, so the specs won't
+ // be used and it doesn't matter which ones we set here.
+ newTransition.transformationSpec = TransformationSpec.Empty
+ newTransition.swipeSpec = SceneTransitions.DefaultSwipeSpec
+ }
+
+ swipeTransition = newTransition
+ }
+
+ /**
+ * We receive a [delta] that can be consumed to change the offset of the current
+ * [SwipeTransition].
+ *
+ * @return the consumed delta
+ */
+ override fun onDrag(delta: Float) {
if (delta == 0f || !isDrivingTransition) return
swipeTransition.dragOffset += delta
@@ -225,14 +285,14 @@ internal class SceneGestureHandler(
val isNewFromScene = fromScene.key != swipeTransition.fromScene
val result =
- swipes!!.findUserActionResult(
+ swipes.findUserActionResult(
fromScene = fromScene,
directionOffset = swipeTransition.dragOffset,
updateSwipesResults = isNewFromScene
)
if (result == null) {
- onDragStopped(velocity = delta, canChangeScene = true)
+ onStop(velocity = delta, canChangeScene = true)
return
}
@@ -243,36 +303,20 @@ internal class SceneGestureHandler(
result.toScene != swipeTransition.toScene ||
result.transitionKey != swipeTransition.key
) {
- val newSwipeTransition =
+ val swipeTransition =
SwipeTransition(
fromScene = fromScene,
result = result,
- swipes = swipes!!,
- layoutImpl = layoutImpl,
- orientation = orientation
+ swipes = swipes,
+ layoutImpl = draggableHandler.layoutImpl,
+ orientation = draggableHandler.orientation,
)
.apply { dragOffset = swipeTransition.dragOffset }
- updateTransition(newSwipeTransition)
+ updateTransition(swipeTransition)
}
}
- private fun computeSwipesResults(
- fromScene: Scene,
- swipes: Swipes
- ): Pair<UserActionResult?, UserActionResult?> {
- val userActions = fromScene.userActions
- fun sceneToSwipePair(swipe: Swipe?): UserActionResult? {
- return userActions[swipe ?: return null]
- }
-
- val upOrLeftResult =
- sceneToSwipePair(swipes.upOrLeft) ?: sceneToSwipePair(swipes.upOrLeftNoSource)
- val downOrRightResult =
- sceneToSwipePair(swipes.downOrRight) ?: sceneToSwipePair(swipes.downOrRightNoSource)
- return Pair(upOrLeftResult, downOrRightResult)
- }
-
/**
* Change fromScene in the case where the user quickly swiped multiple times in the same
* direction to accelerate the transition from A => B then B => C.
@@ -302,18 +346,22 @@ internal class SceneGestureHandler(
// to the next screen or go back to the previous one.
val offset = swipeTransition.dragOffset
val absoluteDistance = distance.absoluteValue
- return if (offset <= -absoluteDistance && swipes!!.upOrLeftResult?.toScene == toScene.key) {
+ return if (offset <= -absoluteDistance && swipes.upOrLeftResult?.toScene == toScene.key) {
toScene to absoluteDistance
- } else if (
- offset >= absoluteDistance && swipes!!.downOrRightResult?.toScene == toScene.key
- ) {
+ } else if (offset >= absoluteDistance && swipes.downOrRightResult?.toScene == toScene.key) {
toScene to -absoluteDistance
} else {
fromScene to 0f
}
}
- internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
+ private fun snapToScene(scene: SceneKey) {
+ if (!isDrivingTransition) return
+ swipeTransition.cancelOffsetAnimation()
+ layoutState.finishTransition(swipeTransition, idleScene = scene)
+ }
+
+ override fun onStop(velocity: Float, canChangeScene: Boolean) {
// The state was changed since the drag started; don't do anything.
if (!isDrivingTransition) {
return
@@ -332,16 +380,16 @@ internal class SceneGestureHandler(
// immediately go back B => A.
if (targetScene != swipeTransition._currentScene) {
swipeTransition._currentScene = targetScene
- with(layoutImpl.state) { coroutineScope.onChangeScene(targetScene.key) }
+ with(draggableHandler.layoutImpl.state) {
+ draggableHandler.coroutineScope.onChangeScene(targetScene.key)
+ }
}
swipeTransition.animateOffset(
- coroutineScope = coroutineScope,
+ coroutineScope = draggableHandler.coroutineScope,
initialVelocity = velocity,
targetOffset = targetOffset,
- onAnimationCompleted = {
- layoutState.finishTransition(swipeTransition, idleScene = targetScene.key)
- }
+ onAnimationCompleted = { snapToScene(targetScene.key) }
)
}
@@ -400,10 +448,10 @@ internal class SceneGestureHandler(
if (startFromIdlePosition) {
// If there is a target scene, we start the overscroll animation.
- val result = swipes!!.findUserActionResultStrict(velocity)
+ val result = swipes.findUserActionResultStrict(velocity)
if (result == null) {
// We will not animate
- layoutState.finishTransition(swipeTransition, idleScene = fromScene.key)
+ snapToScene(fromScene.key)
return
}
@@ -411,9 +459,9 @@ internal class SceneGestureHandler(
SwipeTransition(
fromScene = fromScene,
result = result,
- swipes = swipes!!,
- layoutImpl = layoutImpl,
- orientation = orientation
+ swipes = swipes,
+ layoutImpl = draggableHandler.layoutImpl,
+ orientation = draggableHandler.orientation,
)
.apply { _currentScene = swipeTransition._currentScene }
@@ -440,6 +488,9 @@ internal class SceneGestureHandler(
return (offset - distance).absoluteValue < offset.absoluteValue
}
+ val velocityThreshold = draggableHandler.velocityThreshold
+ val positionalThreshold = draggableHandler.positionalThreshold
+
// Swiping up or left.
if (distance < 0f) {
return if (offset > 0f || velocity >= velocityThreshold) {
@@ -460,10 +511,6 @@ internal class SceneGestureHandler(
isCloserToTarget()
}
}
-
- companion object {
- private const val TAG = "SceneGestureHandler"
- }
}
private fun SwipeTransition(
@@ -492,14 +539,31 @@ private fun SwipeTransition(
)
}
+private fun SwipeTransition(old: SwipeTransition): SwipeTransition {
+ return SwipeTransition(
+ key = old.key,
+ _fromScene = old._fromScene,
+ _toScene = old._toScene,
+ userActionDistanceScope = old.userActionDistanceScope,
+ orientation = old.orientation,
+ isUpOrLeft = old.isUpOrLeft
+ )
+ .apply {
+ _currentScene = old._currentScene
+ dragOffset = old.dragOffset
+ }
+}
+
private class SwipeTransition(
val key: TransitionKey?,
val _fromScene: Scene,
val _toScene: Scene,
- private val userActionDistanceScope: UserActionDistanceScope,
- private val orientation: Orientation,
- private val isUpOrLeft: Boolean,
-) : TransitionState.Transition(_fromScene.key, _toScene.key) {
+ val userActionDistanceScope: UserActionDistanceScope,
+ override val orientation: Orientation,
+ override val isUpOrLeft: Boolean,
+) :
+ TransitionState.Transition(_fromScene.key, _toScene.key),
+ TransitionState.HasOverscrollProperties {
var _currentScene by mutableStateOf(_fromScene)
override val currentScene: SceneKey
get() = _currentScene.key
@@ -728,40 +792,16 @@ private class Swipes(
}
}
-private class SceneDraggableHandler(
- private val gestureHandler: SceneGestureHandler,
-) : DraggableHandler {
- private val source = this
-
- override fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int) {
- gestureHandler.currentSource = source
- gestureHandler.onDragStarted(pointersDown, startedPosition, overSlop)
- }
-
- override fun onDelta(pixels: Float) {
- if (gestureHandler.currentSource == source) {
- gestureHandler.onDrag(delta = pixels)
- }
- }
-
- override fun onDragStopped(velocity: Float) {
- if (gestureHandler.currentSource == source) {
- gestureHandler.currentSource = null
- gestureHandler.onDragStopped(velocity = velocity, canChangeScene = true)
- }
- }
-}
-
-internal class SceneNestedScrollHandler(
+internal class NestedScrollHandlerImpl(
private val layoutImpl: SceneTransitionLayoutImpl,
private val orientation: Orientation,
private val topOrLeftBehavior: NestedScrollBehavior,
private val bottomOrRightBehavior: NestedScrollBehavior,
-) : NestedScrollHandler {
+) {
private val layoutState = layoutImpl.state
- private val gestureHandler = layoutImpl.gestureHandler(orientation)
+ private val draggableHandler = layoutImpl.draggableHandler(orientation)
- override val connection: PriorityNestedScrollConnection = nestedScrollConnection()
+ val connection: PriorityNestedScrollConnection = nestedScrollConnection()
private fun nestedScrollConnection(): PriorityNestedScrollConnection {
// If we performed a long gesture before entering priority mode, we would have to avoid
@@ -789,17 +829,24 @@ internal class SceneNestedScrollHandler(
)
fun hasNextScene(amount: Float): Boolean {
- val fromScene = layoutImpl.scene(layoutState.transitionState.currentScene)
+ val transitionState = layoutState.transitionState
+ val scene = transitionState.currentScene
+ val fromScene = layoutImpl.scene(scene)
val nextScene =
when {
amount < 0f -> fromScene.userActions[actionUpOrLeft]
amount > 0f -> fromScene.userActions[actionDownOrRight]
else -> null
}
- return nextScene != null
+ if (nextScene != null) return true
+
+ if (transitionState !is TransitionState.Idle) return false
+
+ val overscrollSpec = layoutImpl.state.transitions.overscrollSpec(scene, orientation)
+ return overscrollSpec != null
}
- val source = this
+ var dragController: DragController? = null
var isIntercepting = false
return PriorityNestedScrollConnection(
@@ -810,7 +857,7 @@ internal class SceneNestedScrollHandler(
val canInterceptSwipeTransition =
canChangeScene &&
offsetAvailable != 0f &&
- gestureHandler.shouldImmediatelyIntercept(startedPosition = null)
+ draggableHandler.shouldImmediatelyIntercept(startedPosition = null)
if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
val threshold = layoutImpl.transitionInterceptionThreshold
@@ -884,34 +931,28 @@ internal class SceneNestedScrollHandler(
canContinueScroll = { true },
canScrollOnFling = false,
onStart = { offsetAvailable ->
- gestureHandler.currentSource = source
- gestureHandler.onDragStarted(
- pointersDown = 1,
- startedPosition = null,
- overSlop = if (isIntercepting) 0f else offsetAvailable,
- )
+ dragController =
+ draggableHandler.onDragStarted(
+ pointersDown = 1,
+ startedPosition = null,
+ overSlop = if (isIntercepting) 0f else offsetAvailable,
+ )
},
onScroll = { offsetAvailable ->
- if (gestureHandler.currentSource != source) {
- return@PriorityNestedScrollConnection 0f
- }
+ val controller = dragController ?: error("Should be called after onStart")
// TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is
// initiated in a nested child.
- gestureHandler.onDrag(offsetAvailable)
+ controller.onDrag(delta = offsetAvailable)
offsetAvailable
},
onStop = { velocityAvailable ->
- if (gestureHandler.currentSource != source) {
- return@PriorityNestedScrollConnection 0f
- }
+ val controller = dragController ?: error("Should be called after onStart")
- gestureHandler.onDragStopped(
- velocity = velocityAvailable,
- canChangeScene = canChangeScene
- )
+ controller.onStop(velocity = velocityAvailable, canChangeScene = canChangeScene)
+ dragController = null
// The onDragStopped animation consumes any remaining velocity.
velocityAvailable
},
@@ -926,3 +967,9 @@ internal class SceneNestedScrollHandler(
// TODO(b/290184746): Have a better default visibility threshold which takes the swipe distance into
// account instead.
internal const val OffsetVisibilityThreshold = 0.5f
+
+private object NoOpDragController : DragController {
+ override fun onDrag(delta: Float) {}
+
+ override fun onStop(velocity: Float, canChangeScene: Boolean) {}
+}
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 2e781e69bb4a..c7186da6b961 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
@@ -39,7 +39,6 @@ import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.round
@@ -204,6 +203,17 @@ internal class ElementNode(
measurable: Measurable,
constraints: Constraints,
): MeasureResult {
+ val overscrollScene = layoutImpl.state.currentOverscrollSpec?.scene
+ if (overscrollScene != null && overscrollScene != scene.key) {
+ // There is an overscroll in progress on another scene
+ // By measuring composable elements, Compose can cache relevant information.
+ // This reduces the need for re-measure when users return from an overscroll animation.
+ val placeable = measurable.measure(constraints)
+ return layout(placeable.width, placeable.height) {
+ // We don't want to draw it, no need to place the element.
+ }
+ }
+
val placeable = measure(layoutImpl, scene, element, sceneState, measurable, constraints)
return layout(placeable.width, placeable.height) {
place(layoutImpl, scene, element, sceneState, placeable, placementScope = this)
@@ -253,11 +263,13 @@ private fun shouldDrawElement(
): Boolean {
val transition = layoutImpl.state.currentTransition
- // Always draw the element if there is no ongoing transition or if the element is not shared.
+ // 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.toScene !in element.sceneStates ||
+ layoutImpl.state.currentOverscrollSpec?.scene == scene.key
) {
return true
}
@@ -286,12 +298,14 @@ internal fun shouldDrawOrComposeSharedElement(
val fromScene = transition.fromScene
val toScene = transition.toScene
- return scenePicker.sceneDuringTransition(
- element = element,
- transition = transition,
- fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex,
- toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
- ) == scene
+ val chosenByPicker =
+ scenePicker.sceneDuringTransition(
+ element = element,
+ transition = transition,
+ fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex,
+ toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
+ ) == scene
+ return chosenByPicker || layoutImpl.state.currentOverscrollSpec?.scene == scene
}
private fun isSharedElementEnabled(
@@ -549,6 +563,40 @@ private inline fun <T> computeValue(
return idleValue
}
+ if (transition is TransitionState.HasOverscrollProperties) {
+ val overscroll = layoutImpl.state.currentOverscrollSpec
+ if (overscroll?.scene == scene.key) {
+ val elementSpec = overscroll.transformationSpec.transformations(element.key, scene.key)
+ val propertySpec = transformation(elementSpec) ?: return currentValue()
+ val overscrollState = checkNotNull(if (scene.key == toScene) toState else fromState)
+ val targetValue =
+ propertySpec.transform(
+ layoutImpl,
+ scene,
+ element,
+ overscrollState,
+ transition,
+ idleValue,
+ )
+
+ // Make sure we don't read progress if values are the same and we don't need to
+ // interpolate, so we don't invalidate the phase where this is read.
+ if (targetValue == idleValue) {
+ return targetValue
+ }
+
+ // TODO(b/290184746): Make sure that we don't overflow transformations associated to a
+ // range.
+ val directionSign = if (transition.isUpOrLeft) -1 else 1
+ val overscrollProgress = transition.progress.let { if (it > 1f) it - 1f else it }
+ val progress = directionSign * overscrollProgress
+ val rangeProgress = propertySpec.range?.progress(progress) ?: progress
+
+ // Interpolate between the value at rest and the over scrolled value.
+ return lerp(idleValue, targetValue, rangeProgress)
+ }
+ }
+
// The element is shared: interpolate between the value in fromScene and the value in toScene.
// TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared
// elements follow the finger direction.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
deleted file mode 100644
index 58052cd60f39..000000000000
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.android.compose.animation.scene
-
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-
-interface DraggableHandler {
- fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int = 1)
- fun onDelta(pixels: Float)
- fun onDragStopped(velocity: Float)
-}
-
-interface NestedScrollHandler {
- val connection: NestedScrollConnection
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 3ff869b5fdad..05dd5cc09dbf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -24,7 +24,6 @@ import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
import androidx.compose.foundation.gestures.horizontalDrag
import androidx.compose.foundation.gestures.verticalDrag
import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerEvent
@@ -33,7 +32,6 @@ import androidx.compose.ui.input.pointer.PointerId
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
-import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.input.pointer.util.addPointerInputChange
@@ -69,9 +67,7 @@ internal fun Modifier.multiPointerDraggable(
orientation: Orientation,
enabled: () -> Boolean,
startDragImmediately: (startedPosition: Offset) -> Boolean,
- onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
- onDragDelta: (delta: Float) -> Unit,
- onDragStopped: (velocity: Float) -> Unit,
+ onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
): Modifier =
this.then(
MultiPointerDraggableElement(
@@ -79,8 +75,6 @@ internal fun Modifier.multiPointerDraggable(
enabled,
startDragImmediately,
onDragStarted,
- onDragDelta,
- onDragStopped,
)
)
@@ -89,9 +83,7 @@ private data class MultiPointerDraggableElement(
private val enabled: () -> Boolean,
private val startDragImmediately: (startedPosition: Offset) -> Boolean,
private val onDragStarted:
- (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
- private val onDragDelta: (Float) -> Unit,
- private val onDragStopped: (velocity: Float) -> Unit,
+ (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
) : ModifierNodeElement<MultiPointerDraggableNode>() {
override fun create(): MultiPointerDraggableNode =
MultiPointerDraggableNode(
@@ -99,8 +91,6 @@ private data class MultiPointerDraggableElement(
enabled = enabled,
startDragImmediately = startDragImmediately,
onDragStarted = onDragStarted,
- onDragDelta = onDragDelta,
- onDragStopped = onDragStopped,
)
override fun update(node: MultiPointerDraggableNode) {
@@ -108,8 +98,6 @@ private data class MultiPointerDraggableElement(
node.enabled = enabled
node.startDragImmediately = startDragImmediately
node.onDragStarted = onDragStarted
- node.onDragDelta = onDragDelta
- node.onDragStopped = onDragStopped
}
}
@@ -117,9 +105,8 @@ internal class MultiPointerDraggableNode(
orientation: Orientation,
enabled: () -> Boolean,
var startDragImmediately: (startedPosition: Offset) -> Boolean,
- var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
- var onDragDelta: (Float) -> Unit,
- var onDragStopped: (velocity: Float) -> Unit,
+ var onDragStarted:
+ (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
) :
PointerInputModifierNode,
DelegatingNode(),
@@ -176,40 +163,33 @@ internal class MultiPointerDraggableNode(
return
}
- val onDragStart: (Offset, Float, Int) -> Unit = { startedPosition, overSlop, pointersDown ->
- velocityTracker.resetTracking()
- onDragStarted(startedPosition, overSlop, pointersDown)
- }
-
- val onDragCancel: () -> Unit = { onDragStopped(/* velocity= */ 0f) }
-
- val onDragEnd: () -> Unit = {
- val maxFlingVelocity =
- currentValueOf(LocalViewConfiguration).maximumFlingVelocity.let { max ->
- Velocity(max, max)
- }
-
- val velocity = velocityTracker.calculateVelocity(maxFlingVelocity)
- onDragStopped(
- when (orientation) {
- Orientation.Horizontal -> velocity.x
- Orientation.Vertical -> velocity.y
- }
- )
- }
-
- val onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit = { change, amount ->
- velocityTracker.addPointerInputChange(change)
- onDragDelta(amount)
- }
-
detectDragGestures(
orientation = orientation,
startDragImmediately = startDragImmediately,
- onDragStart = onDragStart,
- onDragEnd = onDragEnd,
- onDragCancel = onDragCancel,
- onDrag = onDrag,
+ onDragStart = { startedPosition, overSlop, pointersDown ->
+ velocityTracker.resetTracking()
+ onDragStarted(startedPosition, overSlop, pointersDown)
+ },
+ onDrag = { controller, change, amount ->
+ velocityTracker.addPointerInputChange(change)
+ controller.onDrag(amount)
+ },
+ onDragEnd = { controller ->
+ val viewConfiguration = currentValueOf(LocalViewConfiguration)
+ val maxVelocity = viewConfiguration.maximumFlingVelocity.let { Velocity(it, it) }
+ val velocity = velocityTracker.calculateVelocity(maxVelocity)
+ controller.onStop(
+ velocity =
+ when (orientation) {
+ Orientation.Horizontal -> velocity.x
+ Orientation.Vertical -> velocity.y
+ },
+ canChangeScene = true,
+ )
+ },
+ onDragCancel = { controller ->
+ controller.onStop(velocity = 0f, canChangeScene = true)
+ },
)
}
}
@@ -225,10 +205,10 @@ internal class MultiPointerDraggableNode(
private suspend fun PointerInputScope.detectDragGestures(
orientation: Orientation,
startDragImmediately: (startedPosition: Offset) -> Boolean,
- onDragStart: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
- onDragEnd: () -> Unit,
- onDragCancel: () -> Unit,
- onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit,
+ onDragStart: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ onDragEnd: (controller: DragController) -> Unit,
+ onDragCancel: (controller: DragController) -> Unit,
+ onDrag: (controller: DragController, change: PointerInputChange, dragAmount: Float) -> Unit,
) {
awaitEachGesture {
val initialDown = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
@@ -282,34 +262,34 @@ private suspend fun PointerInputScope.detectDragGestures(
}
}
- onDragStart(drag.position, overSlop, pressed.size)
+ val controller = onDragStart(drag.position, overSlop, pressed.size)
val successful: Boolean
try {
- onDrag(drag, overSlop)
+ onDrag(controller, drag, overSlop)
successful =
when (orientation) {
Orientation.Horizontal ->
horizontalDrag(drag.id) {
- onDrag(it, it.positionChange().x)
+ onDrag(controller, it, it.positionChange().x)
it.consume()
}
Orientation.Vertical ->
verticalDrag(drag.id) {
- onDrag(it, it.positionChange().y)
+ onDrag(controller, it, it.positionChange().y)
it.consume()
}
}
} catch (t: Throwable) {
- onDragCancel()
+ onDragCancel(controller)
throw t
}
if (successful) {
- onDragEnd()
+ onDragEnd(controller)
} else {
- onDragCancel()
+ onDragCancel(controller)
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index e78f3266d664..5a2f85ad163c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -178,7 +178,7 @@ private fun scenePriorityNestedScrollConnection(
topOrLeftBehavior: NestedScrollBehavior,
bottomOrRightBehavior: NestedScrollBehavior,
) =
- SceneNestedScrollHandler(
+ NestedScrollHandlerImpl(
layoutImpl = layoutImpl,
orientation = orientation,
topOrLeftBehavior = topOrLeftBehavior,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 039a5b0c9523..1670e9cee731 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -25,8 +25,13 @@ import androidx.compose.runtime.key
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ApproachLayoutModifierNode
+import androidx.compose.ui.layout.ApproachMeasureScope
import androidx.compose.ui.layout.LookaheadScope
-import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
@@ -97,8 +102,8 @@ internal class SceneTransitionLayoutImpl(
.also { _sharedValues = it }
// TODO(b/317958526): Lazily allocate scene gesture handlers the first time they are needed.
- private val horizontalGestureHandler: SceneGestureHandler
- private val verticalGestureHandler: SceneGestureHandler
+ private val horizontalDraggableHandler: DraggableHandlerImpl
+ private val verticalDraggableHandler: DraggableHandlerImpl
private var _userActionDistanceScope: UserActionDistanceScope? = null
internal val userActionDistanceScope: UserActionDistanceScope
@@ -111,27 +116,27 @@ internal class SceneTransitionLayoutImpl(
init {
updateScenes(builder)
- // SceneGestureHandler must wait for the scenes to be initialized, in order to access the
+ // DraggableHandlerImpl must wait for the scenes to be initialized, in order to access the
// current scene (required for SwipeTransition).
- horizontalGestureHandler =
- SceneGestureHandler(
+ horizontalDraggableHandler =
+ DraggableHandlerImpl(
layoutImpl = this,
orientation = Orientation.Horizontal,
coroutineScope = coroutineScope,
)
- verticalGestureHandler =
- SceneGestureHandler(
+ verticalDraggableHandler =
+ DraggableHandlerImpl(
layoutImpl = this,
orientation = Orientation.Vertical,
coroutineScope = coroutineScope,
)
}
- internal fun gestureHandler(orientation: Orientation): SceneGestureHandler =
+ internal fun draggableHandler(orientation: Orientation): DraggableHandlerImpl =
when (orientation) {
- Orientation.Vertical -> verticalGestureHandler
- Orientation.Horizontal -> horizontalGestureHandler
+ Orientation.Vertical -> verticalDraggableHandler
+ Orientation.Horizontal -> horizontalDraggableHandler
}
internal fun scene(key: SceneKey): Scene {
@@ -181,46 +186,15 @@ internal class SceneTransitionLayoutImpl(
}
@Composable
- @OptIn(ExperimentalComposeUiApi::class)
internal fun Content(modifier: Modifier) {
Box(
modifier
// Handle horizontal and vertical swipes on this layout.
// Note: order here is important and will give a slight priority to the vertical
// swipes.
- .swipeToScene(horizontalGestureHandler)
- .swipeToScene(verticalGestureHandler)
- // Animate the size of this layout.
- .intermediateLayout { measurable, constraints ->
- // Measure content normally.
- val placeable = measurable.measure(constraints)
-
- val width: Int
- val height: Int
- val transition = state.currentTransition
- if (transition == null) {
- width = placeable.width
- height = placeable.height
- } else {
- // Interpolate the size.
- val fromSize = scene(transition.fromScene).targetSize
- val toSize = scene(transition.toScene).targetSize
-
- // Optimization: make sure we don't read state.progress if fromSize ==
- // toSize to avoid running this code every frame when the layout size does
- // not change.
- if (fromSize == toSize) {
- width = fromSize.width
- height = fromSize.height
- } else {
- val size = lerp(fromSize, toSize, transition.progress)
- width = size.width.coerceAtLeast(0)
- height = size.height.coerceAtLeast(0)
- }
- }
-
- layout(width, height) { placeable.place(0, 0) }
- }
+ .swipeToScene(horizontalDraggableHandler)
+ .swipeToScene(verticalDraggableHandler)
+ .then(LayoutElement(layoutImpl = this))
) {
LookaheadScope {
val scenesToCompose =
@@ -263,3 +237,54 @@ internal class SceneTransitionLayoutImpl(
scenes.values.forEach { it.targetSize = size }
}
}
+
+private data class LayoutElement(private val layoutImpl: SceneTransitionLayoutImpl) :
+ ModifierNodeElement<LayoutNode>() {
+ override fun create(): LayoutNode = LayoutNode(layoutImpl)
+
+ override fun update(node: LayoutNode) {
+ node.layoutImpl = layoutImpl
+ }
+}
+
+private class LayoutNode(var layoutImpl: SceneTransitionLayoutImpl) :
+ Modifier.Node(), ApproachLayoutModifierNode {
+ override fun isMeasurementApproachComplete(lookaheadSize: IntSize): Boolean {
+ return layoutImpl.state.currentTransition == null
+ }
+
+ @ExperimentalComposeUiApi
+ override fun ApproachMeasureScope.approachMeasure(
+ measurable: Measurable,
+ constraints: Constraints,
+ ): MeasureResult {
+ // Measure content normally.
+ val placeable = measurable.measure(constraints)
+
+ val width: Int
+ val height: Int
+ val transition = layoutImpl.state.currentTransition
+ if (transition == null) {
+ width = placeable.width
+ height = placeable.height
+ } else {
+ // Interpolate the size.
+ val fromSize = layoutImpl.scene(transition.fromScene).targetSize
+ val toSize = layoutImpl.scene(transition.toScene).targetSize
+
+ // Optimization: make sure we don't read state.progress if fromSize ==
+ // toSize to avoid running this code every frame when the layout size does
+ // not change.
+ if (fromSize == toSize) {
+ width = fromSize.width
+ height = fromSize.height
+ } else {
+ val size = lerp(fromSize, toSize, transition.progress)
+ width = size.width.coerceAtLeast(0)
+ height = size.height.coerceAtLeast(0)
+ }
+ }
+
+ return layout(width, height) { placeable.place(0, 0) }
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 662f33f3e88b..0fa19bb33818 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -16,6 +16,7 @@
package com.android.compose.animation.scene
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
@@ -221,6 +222,23 @@ sealed interface TransitionState {
isTransitioning(from = other, to = scene)
}
}
+
+ interface HasOverscrollProperties {
+ /**
+ * The position of the [TransitionState.Transition.toScene].
+ *
+ * Used to understand the direction of the overscroll.
+ */
+ val isUpOrLeft: Boolean
+
+ /**
+ * The relative orientation between [TransitionState.Transition.fromScene] and
+ * [TransitionState.Transition.toScene].
+ *
+ * Used to understand the orientation of the overscroll.
+ */
+ val orientation: Orientation
+ }
}
internal abstract class BaseSceneTransitionLayoutState(
@@ -237,6 +255,25 @@ internal abstract class BaseSceneTransitionLayoutState(
*/
internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
+ private var fromOverscrollSpec: OverscrollSpecImpl? = null
+ private var toOverscrollSpec: OverscrollSpecImpl? = null
+
+ /**
+ * @return the overscroll [OverscrollSpecImpl] if it is defined for the current
+ * [transitionState] and we are currently over scrolling.
+ */
+ internal val currentOverscrollSpec: OverscrollSpecImpl?
+ get() {
+ val transition = currentTransition ?: return null
+ if (transition !is TransitionState.HasOverscrollProperties) return null
+ val progress = transition.progress
+ return when {
+ progress < 0f -> fromOverscrollSpec
+ progress > 1f -> toOverscrollSpec
+ else -> null
+ }
+ }
+
private val activeTransitionLinks = mutableMapOf<StateLink, LinkedTransition>()
/** Whether we can transition to the given [scene]. */
@@ -266,10 +303,13 @@ internal abstract class BaseSceneTransitionLayoutState(
transitionKey: TransitionKey?,
) {
// Compute the [TransformationSpec] when the transition starts.
+ val fromScene = transition.fromScene
+ val toScene = transition.toScene
+ val orientation = (transition as? TransitionState.HasOverscrollProperties)?.orientation
transformationSpec =
- transitions
- .transitionSpec(transition.fromScene, transition.toScene, key = transitionKey)
- .transformationSpec()
+ transitions.transitionSpec(fromScene, toScene, key = transitionKey).transformationSpec()
+ fromOverscrollSpec = orientation?.let { transitions.overscrollSpec(fromScene, it) }
+ toOverscrollSpec = orientation?.let { transitions.overscrollSpec(toScene, it) }
cancelActiveTransitionLinks()
setupTransitionLinks(transition)
transitionState = transition
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 8ee23b6ffe8e..2dd41cd329a2 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -21,6 +21,7 @@ import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.spring
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
@@ -41,18 +42,22 @@ class SceneTransitions
internal constructor(
internal val defaultSwipeSpec: SpringSpec<Float>,
internal val transitionSpecs: List<TransitionSpecImpl>,
+ internal val overscrollSpecs: List<OverscrollSpecImpl>,
) {
- private val cache =
+ private val transitionCache =
mutableMapOf<
SceneKey, MutableMap<SceneKey, MutableMap<TransitionKey?, TransitionSpecImpl>>
>()
+ private val overscrollCache =
+ mutableMapOf<SceneKey, MutableMap<Orientation, OverscrollSpecImpl?>>()
+
internal fun transitionSpec(
from: SceneKey,
to: SceneKey,
key: TransitionKey?,
): TransitionSpecImpl {
- return cache
+ return transitionCache
.getOrPut(from) { mutableMapOf() }
.getOrPut(to) { mutableMapOf() }
.getOrPut(key) { findSpec(from, to, key) }
@@ -105,6 +110,28 @@ internal constructor(
private fun defaultTransition(from: SceneKey, to: SceneKey) =
TransitionSpecImpl(key = null, from, to, TransformationSpec.EmptyProvider)
+ internal fun overscrollSpec(scene: SceneKey, orientation: Orientation): OverscrollSpecImpl? =
+ overscrollCache
+ .getOrPut(scene) { mutableMapOf() }
+ .getOrPut(orientation) { overscroll(scene, orientation) { it.scene == scene } }
+
+ private fun overscroll(
+ scene: SceneKey,
+ orientation: Orientation,
+ filter: (OverscrollSpecImpl) -> Boolean,
+ ): OverscrollSpecImpl? {
+ var match: OverscrollSpecImpl? = null
+ overscrollSpecs.fastForEach { spec ->
+ if (spec.orientation == orientation && filter(spec)) {
+ if (match != null) {
+ error("Found multiple transition specs for transition $scene")
+ }
+ match = spec
+ }
+ }
+ return match
+ }
+
companion object {
internal val DefaultSwipeSpec =
spring(
@@ -112,7 +139,12 @@ internal constructor(
visibilityThreshold = OffsetVisibilityThreshold,
)
- val Empty = SceneTransitions(DefaultSwipeSpec, transitionSpecs = emptyList())
+ val Empty =
+ SceneTransitions(
+ defaultSwipeSpec = DefaultSwipeSpec,
+ transitionSpecs = emptyList(),
+ overscrollSpecs = emptyList(),
+ )
}
}
@@ -139,7 +171,7 @@ interface TransitionSpec {
*/
fun reversed(): TransitionSpec
- /*
+ /**
* The [TransformationSpec] associated to this [TransitionSpec].
*
* Note that this is called once every a transition associated to this [TransitionSpec] is
@@ -212,6 +244,24 @@ internal class TransitionSpecImpl(
override fun transformationSpec(): TransformationSpecImpl = this.transformationSpec.invoke()
}
+/** The definition of the overscroll behavior of the [scene]. */
+interface OverscrollSpec {
+ /** The scene we are over scrolling. */
+ val scene: SceneKey
+
+ /** The orientation of this [OverscrollSpec]. */
+ val orientation: Orientation
+
+ /** The [TransformationSpec] associated to this [OverscrollSpec]. */
+ val transformationSpec: TransformationSpec
+}
+
+internal class OverscrollSpecImpl(
+ override val scene: SceneKey,
+ override val orientation: Orientation,
+ override val transformationSpec: TransformationSpecImpl,
+) : OverscrollSpec
+
/**
* An implementation of [TransformationSpec] that allows the quick retrieval of an element
* [ElementTransformations].
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 61f497818c89..b618369c2369 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -31,39 +31,39 @@ import androidx.compose.ui.unit.IntSize
* Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
*/
@Stable
-internal fun Modifier.swipeToScene(gestureHandler: SceneGestureHandler): Modifier {
- return this.then(SwipeToSceneElement(gestureHandler))
+internal fun Modifier.swipeToScene(draggableHandler: DraggableHandlerImpl): Modifier {
+ return this.then(SwipeToSceneElement(draggableHandler))
}
private data class SwipeToSceneElement(
- val gestureHandler: SceneGestureHandler,
+ val draggableHandler: DraggableHandlerImpl,
) : ModifierNodeElement<SwipeToSceneNode>() {
- override fun create(): SwipeToSceneNode = SwipeToSceneNode(gestureHandler)
+ override fun create(): SwipeToSceneNode = SwipeToSceneNode(draggableHandler)
override fun update(node: SwipeToSceneNode) {
- node.gestureHandler = gestureHandler
+ node.draggableHandler = draggableHandler
}
}
private class SwipeToSceneNode(
- gestureHandler: SceneGestureHandler,
+ draggableHandler: DraggableHandlerImpl,
) : DelegatingNode(), PointerInputModifierNode {
private val delegate =
delegate(
MultiPointerDraggableNode(
- orientation = gestureHandler.orientation,
+ orientation = draggableHandler.orientation,
enabled = ::enabled,
startDragImmediately = ::startDragImmediately,
- onDragStarted = gestureHandler.draggable::onDragStarted,
- onDragDelta = gestureHandler.draggable::onDelta,
- onDragStopped = gestureHandler.draggable::onDragStopped,
+ onDragStarted = draggableHandler::onDragStarted,
)
)
- var gestureHandler: SceneGestureHandler = gestureHandler
+ private var _draggableHandler = draggableHandler
+ var draggableHandler: DraggableHandlerImpl
+ get() = _draggableHandler
set(value) {
- if (value != field) {
- field = value
+ if (_draggableHandler != value) {
+ _draggableHandler = value
// Make sure to update the delegate orientation. Note that this will automatically
// reset the underlying pointer input handler, so previous gestures will be
@@ -81,12 +81,12 @@ private class SwipeToSceneNode(
override fun onCancelPointerInput() = delegate.onCancelPointerInput()
private fun enabled(): Boolean {
- return gestureHandler.isDrivingTransition ||
- currentScene().shouldEnableSwipes(gestureHandler.orientation)
+ return draggableHandler.isDrivingTransition ||
+ currentScene().shouldEnableSwipes(delegate.orientation)
}
private fun currentScene(): Scene {
- val layoutImpl = gestureHandler.layoutImpl
+ val layoutImpl = draggableHandler.layoutImpl
return layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
}
@@ -98,12 +98,12 @@ private class SwipeToSceneNode(
private fun startDragImmediately(startedPosition: Offset): Boolean {
// Immediately start the drag if the user can't swipe in the other direction and the gesture
// handler can intercept it.
- return !canOppositeSwipe() && gestureHandler.shouldImmediatelyIntercept(startedPosition)
+ return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(startedPosition)
}
private fun canOppositeSwipe(): Boolean {
val oppositeOrientation =
- when (gestureHandler.orientation) {
+ when (draggableHandler.orientation) {
Orientation.Vertical -> Orientation.Horizontal
Orientation.Horizontal -> Orientation.Vertical
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 8a09b00a63ae..bc52a28279dc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -18,6 +18,7 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.SpringSpec
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -72,24 +73,23 @@ interface SceneTransitionsBuilder {
key: TransitionKey? = null,
builder: TransitionBuilder.() -> Unit = {},
): TransitionSpec
-}
-
-@TransitionDsl
-interface TransitionBuilder : PropertyTransformationBuilder {
- /**
- * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
- * the transition is triggered (i.e. it is not gesture-based).
- */
- var spec: AnimationSpec<Float>
/**
- * The [SpringSpec] used to animate the associated transition progress when the transition was
- * started by a swipe and is now animating back to a scene because the user lifted their finger.
+ * Define the animation to be played when the [scene] is overscrolled in the given
+ * [orientation].
*
- * If `null`, then the [SceneTransitionsBuilder.defaultSwipeSpec] will be used.
+ * The overscroll animation always starts from a progress of 0f, and reaches 1f when moving the
+ * [distance] down/right, -1f when moving in the opposite direction.
*/
- var swipeSpec: SpringSpec<Float>?
+ fun overscroll(
+ scene: SceneKey,
+ orientation: Orientation,
+ builder: OverscrollBuilder.() -> Unit = {},
+ ): OverscrollSpec
+}
+@TransitionDsl
+interface OverscrollBuilder : PropertyTransformationBuilder {
/**
* The distance it takes for this transition to animate from 0% to 100% when it is driven by a
* [UserAction].
@@ -117,6 +117,23 @@ interface TransitionBuilder : PropertyTransformationBuilder {
end: Float? = null,
builder: PropertyTransformationBuilder.() -> Unit,
)
+}
+
+@TransitionDsl
+interface TransitionBuilder : OverscrollBuilder, PropertyTransformationBuilder {
+ /**
+ * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
+ * the transition is triggered (i.e. it is not gesture-based).
+ */
+ var spec: AnimationSpec<Float>
+
+ /**
+ * The [SpringSpec] used to animate the associated transition progress when the transition was
+ * started by a swipe and is now animating back to a scene because the user lifted their finger.
+ *
+ * If `null`, then the [SceneTransitionsBuilder.defaultSwipeSpec] will be used.
+ */
+ var swipeSpec: SpringSpec<Float>?
/**
* Define a timestamp-based range for the transformations inside [builder].
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 78289997885f..65e8ea5cc341 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -21,7 +21,9 @@ import androidx.compose.animation.core.DurationBasedAnimationSpec
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.VectorConverter
+import androidx.compose.animation.core.snap
import androidx.compose.animation.core.spring
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import com.android.compose.animation.scene.transformation.AnchoredSize
@@ -41,13 +43,18 @@ internal fun transitionsImpl(
builder: SceneTransitionsBuilder.() -> Unit,
): SceneTransitions {
val impl = SceneTransitionsBuilderImpl().apply(builder)
- return SceneTransitions(impl.defaultSwipeSpec, impl.transitionSpecs)
+ return SceneTransitions(
+ impl.defaultSwipeSpec,
+ impl.transitionSpecs,
+ impl.transitionOverscrollSpecs
+ )
}
private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
override var defaultSwipeSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec
val transitionSpecs = mutableListOf<TransitionSpecImpl>()
+ val transitionOverscrollSpecs = mutableListOf<OverscrollSpecImpl>()
override fun to(
to: SceneKey,
@@ -66,6 +73,25 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
return transition(from = from, to = to, key = key, builder)
}
+ override fun overscroll(
+ scene: SceneKey,
+ orientation: Orientation,
+ builder: OverscrollBuilder.() -> Unit
+ ): OverscrollSpec {
+ fun transformationSpec(): TransformationSpecImpl {
+ val impl = OverscrollBuilderImpl().apply(builder)
+ return TransformationSpecImpl(
+ progressSpec = snap(),
+ swipeSpec = null,
+ distance = impl.distance,
+ transformations = impl.transformations,
+ )
+ }
+ val spec = OverscrollSpecImpl(scene, orientation, transformationSpec())
+ transitionOverscrollSpecs.add(spec)
+ return spec
+ }
+
private fun transition(
from: SceneKey?,
to: SceneKey?,
@@ -88,28 +114,11 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
}
}
-internal class TransitionBuilderImpl : TransitionBuilder {
+internal open class OverscrollBuilderImpl : OverscrollBuilder {
val transformations = mutableListOf<Transformation>()
- override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
- override var swipeSpec: SpringSpec<Float>? = null
- override var distance: UserActionDistance? = null
-
private var range: TransformationRange? = null
- private var reversed = false
- private val durationMillis: Int by lazy {
- val spec = spec
- if (spec !is DurationBasedAnimationSpec) {
- error("timestampRange {} can only be used with a DurationBasedAnimationSpec")
- }
-
- spec.vectorize(Float.VectorConverter).durationMillis
- }
-
- override fun reversed(builder: TransitionBuilder.() -> Unit) {
- reversed = true
- builder()
- reversed = false
- }
+ protected var reversed = false
+ override var distance: UserActionDistance? = null
override fun fractionRange(
start: Float?,
@@ -121,28 +130,6 @@ internal class TransitionBuilderImpl : TransitionBuilder {
range = null
}
- override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
- transformations.add(SharedElementTransformation(matcher, enabled))
- }
-
- override fun timestampRange(
- startMillis: Int?,
- endMillis: Int?,
- builder: PropertyTransformationBuilder.() -> Unit
- ) {
- if (startMillis != null && (startMillis < 0 || startMillis > durationMillis)) {
- error("invalid start value: startMillis=$startMillis durationMillis=$durationMillis")
- }
-
- if (endMillis != null && (endMillis < 0 || endMillis > durationMillis)) {
- error("invalid end value: endMillis=$startMillis durationMillis=$durationMillis")
- }
-
- val start = startMillis?.let { it.toFloat() / durationMillis }
- val end = endMillis?.let { it.toFloat() / durationMillis }
- fractionRange(start, end, builder)
- }
-
private fun transformation(transformation: PropertyTransformation<*>) {
val transformation =
if (range != null) {
@@ -197,3 +184,45 @@ internal class TransitionBuilderImpl : TransitionBuilder {
transformation(AnchoredSize(matcher, anchor, anchorWidth, anchorHeight))
}
}
+
+internal class TransitionBuilderImpl : OverscrollBuilderImpl(), TransitionBuilder {
+ override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
+ override var swipeSpec: SpringSpec<Float>? = null
+ override var distance: UserActionDistance? = null
+ private val durationMillis: Int by lazy {
+ val spec = spec
+ if (spec !is DurationBasedAnimationSpec) {
+ error("timestampRange {} can only be used with a DurationBasedAnimationSpec")
+ }
+
+ spec.vectorize(Float.VectorConverter).durationMillis
+ }
+
+ override fun reversed(builder: TransitionBuilder.() -> Unit) {
+ reversed = true
+ builder()
+ reversed = false
+ }
+
+ override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
+ transformations.add(SharedElementTransformation(matcher, enabled))
+ }
+
+ override fun timestampRange(
+ startMillis: Int?,
+ endMillis: Int?,
+ builder: PropertyTransformationBuilder.() -> Unit
+ ) {
+ if (startMillis != null && (startMillis < 0 || startMillis > durationMillis)) {
+ error("invalid start value: startMillis=$startMillis durationMillis=$durationMillis")
+ }
+
+ if (endMillis != null && (endMillis < 0 || endMillis > durationMillis)) {
+ error("invalid end value: endMillis=$startMillis durationMillis=$durationMillis")
+ }
+
+ val start = startMillis?.let { it.toFloat() / durationMillis }
+ val end = endMillis?.let { it.toFloat() / durationMillis }
+ fractionRange(start, end, builder)
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index fe53d5b45420..eb9b4280aacb 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -47,12 +47,12 @@ private const val SCREEN_SIZE = 100f
private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt())
@RunWith(AndroidJUnit4::class)
-class SceneGestureHandlerTest {
+class DraggableHandlerTest {
private class TestGestureScope(
private val testScope: MonotonicClockTestScope,
) {
var canChangeScene: (SceneKey) -> Boolean = { true }
- private val layoutState =
+ val layoutState =
MutableSceneTransitionLayoutStateImpl(
SceneA,
EmptyTestTransitions,
@@ -99,19 +99,19 @@ class SceneGestureHandlerTest {
)
.apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
- val sceneGestureHandler = layoutImpl.gestureHandler(Orientation.Vertical)
- val horizontalSceneGestureHandler = layoutImpl.gestureHandler(Orientation.Horizontal)
+ val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical)
+ val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal)
fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
- SceneNestedScrollHandler(
+ NestedScrollHandlerImpl(
layoutImpl = layoutImpl,
- orientation = sceneGestureHandler.orientation,
+ orientation = draggableHandler.orientation,
topOrLeftBehavior = nestedScrollBehavior,
bottomOrRightBehavior = nestedScrollBehavior,
)
.connection
- val velocityThreshold = sceneGestureHandler.velocityThreshold
+ val velocityThreshold = draggableHandler.velocityThreshold
fun down(fractionOfScreen: Float) =
if (fractionOfScreen < 0f) error("use up()") else SCREEN_SIZE * fractionOfScreen
@@ -190,20 +190,18 @@ class SceneGestureHandlerTest {
fun onDragStarted(
startedPosition: Offset = Offset.Zero,
overSlop: Float,
- pointersDown: Int = 1
- ) {
+ pointersDown: Int = 1,
+ ): DragController {
// overSlop should be 0f only if the drag gesture starts with startDragImmediately
if (overSlop == 0f) error("Consider using onDragStartedImmediately()")
- onDragStarted(sceneGestureHandler.draggable, startedPosition, overSlop, pointersDown)
+ return onDragStarted(draggableHandler, startedPosition, overSlop, pointersDown)
}
- fun onDragStartedImmediately(startedPosition: Offset = Offset.Zero, pointersDown: Int = 1) {
- onDragStarted(
- sceneGestureHandler.draggable,
- startedPosition,
- overSlop = 0f,
- pointersDown
- )
+ fun onDragStartedImmediately(
+ startedPosition: Offset = Offset.Zero,
+ pointersDown: Int = 1,
+ ): DragController {
+ return onDragStarted(draggableHandler, startedPosition, overSlop = 0f, pointersDown)
}
fun onDragStarted(
@@ -211,24 +209,26 @@ class SceneGestureHandlerTest {
startedPosition: Offset = Offset.Zero,
overSlop: Float = 0f,
pointersDown: Int = 1
- ) {
- draggableHandler.onDragStarted(
- startedPosition = startedPosition,
- overSlop = overSlop,
- pointersDown = pointersDown,
- )
+ ): DragController {
+ val dragController =
+ draggableHandler.onDragStarted(
+ startedPosition = startedPosition,
+ overSlop = overSlop,
+ pointersDown = pointersDown,
+ )
// MultiPointerDraggable will always call onDelta with the initial overSlop right after
- onDelta(pixels = overSlop)
+ dragController.onDragDelta(pixels = overSlop)
+
+ return dragController
}
- fun onDelta(pixels: Float) {
- sceneGestureHandler.draggable.onDelta(pixels = pixels)
+ fun DragController.onDragDelta(pixels: Float) {
+ onDrag(delta = pixels)
}
- fun onDragStopped(velocity: Float) {
- sceneGestureHandler.draggable.onDragStopped(velocity = velocity)
- runCurrent()
+ fun DragController.onDragStopped(velocity: Float, canChangeScene: Boolean = true) {
+ onStop(velocity, canChangeScene)
}
fun NestedScrollConnection.scroll(
@@ -281,20 +281,20 @@ class SceneGestureHandlerTest {
@Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
- onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
+ val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
- onDelta(pixels = down(fractionOfScreen = 0.1f))
+ dragController.onDragDelta(pixels = down(fractionOfScreen = 0.1f))
assertThat(progress).isEqualTo(0.2f)
}
@Test
fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
- onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
+ val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- onDragStopped(velocity = velocityThreshold - 0.01f)
+ dragController.onDragStopped(velocity = velocityThreshold - 0.01f)
assertTransition(currentScene = SceneA)
// wait for the stop animation
@@ -304,10 +304,10 @@ class SceneGestureHandlerTest {
@Test
fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
- onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
+ val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- onDragStopped(velocity = velocityThreshold)
+ dragController.onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC)
// wait for the stop animation
@@ -317,10 +317,10 @@ class SceneGestureHandlerTest {
@Test
fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
- onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
+ val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- onDragStopped(velocity = 0f)
+ dragController.onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(currentScene = SceneA)
}
@@ -328,7 +328,7 @@ class SceneGestureHandlerTest {
@Test
fun onDragReversedDirection_changeToScene() = runGestureTest {
// Drag A -> B with progress 0.6
- onDragStarted(overSlop = -60f)
+ val dragController = onDragStarted(overSlop = -60f)
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -337,7 +337,7 @@ class SceneGestureHandlerTest {
)
// Reverse direction such that A -> C now with 0.4
- onDelta(pixels = 100f)
+ dragController.onDragDelta(pixels = 100f)
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -346,7 +346,7 @@ class SceneGestureHandlerTest {
)
// After the drag stopped scene C should be committed
- onDragStopped(velocity = velocityThreshold)
+ dragController.onDragStopped(velocity = velocityThreshold)
assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC)
// wait for the stop animation
@@ -356,8 +356,6 @@ class SceneGestureHandlerTest {
@Test
fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest {
- val horizontalDraggableHandler = horizontalSceneGestureHandler.draggable
-
onDragStarted(horizontalDraggableHandler, overSlop = up(fractionOfScreen = 0.3f))
assertIdle(currentScene = SceneA)
@@ -370,7 +368,7 @@ class SceneGestureHandlerTest {
navigateToSceneC()
// We are on SceneC which has no action in Down direction
- onDragStarted(overSlop = 10f)
+ val dragController = onDragStarted(overSlop = 10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -379,7 +377,7 @@ class SceneGestureHandlerTest {
)
// Reverse drag direction, it will consume the previous drag
- onDelta(pixels = -10f)
+ dragController.onDragDelta(pixels = -10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -388,7 +386,7 @@ class SceneGestureHandlerTest {
)
// Continue reverse drag direction, it should record progress to Scene B
- onDelta(pixels = -10f)
+ dragController.onDragDelta(pixels = -10f)
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -416,14 +414,14 @@ class SceneGestureHandlerTest {
@Test
fun onDragToExactlyZero_toSceneIsSet() = runGestureTest {
- onDragStarted(overSlop = down(fractionOfScreen = 0.3f))
+ val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.3f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
toScene = SceneC,
progress = 0.3f
)
- onDelta(pixels = up(fractionOfScreen = 0.3f))
+ dragController.onDragDelta(pixels = up(fractionOfScreen = 0.3f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -434,8 +432,8 @@ class SceneGestureHandlerTest {
private fun TestGestureScope.navigateToSceneC() {
assertIdle(currentScene = SceneA)
- onDragStarted(overSlop = down(fractionOfScreen = 1f))
- onDragStopped(velocity = 0f)
+ val dragController = onDragStarted(overSlop = down(fractionOfScreen = 1f))
+ dragController.onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(currentScene = SceneC)
}
@@ -443,7 +441,7 @@ class SceneGestureHandlerTest {
@Test
fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
// Drag A -> B with progress 0.2
- onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+ val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -452,13 +450,13 @@ class SceneGestureHandlerTest {
)
// Start animation A -> B with progress 0.2 -> 1.0
- onDragStopped(velocity = -velocityThreshold)
+ dragController1.onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
// While at A -> B do a 100% screen drag (progress 1.2). This should go past B and change
// the transition to B -> C with progress 0.2
- onDragStartedImmediately()
- onDelta(pixels = up(fractionOfScreen = 1f))
+ val dragController2 = onDragStartedImmediately()
+ dragController2.onDragDelta(pixels = up(fractionOfScreen = 1f))
assertTransition(
currentScene = SceneB,
fromScene = SceneB,
@@ -467,7 +465,7 @@ class SceneGestureHandlerTest {
)
// After the drag stopped scene C should be committed
- onDragStopped(velocity = -velocityThreshold)
+ dragController2.onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC)
// wait for the stop animation
@@ -477,9 +475,9 @@ class SceneGestureHandlerTest {
@Test
fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
- onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
- onDelta(pixels = up(fractionOfScreen = 0.2f))
- onDragStopped(velocity = -velocityThreshold)
+ val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.2f))
+ dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.2f))
+ dragController1.onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
mutableUserActionsA.remove(Swipe.Up)
@@ -488,34 +486,34 @@ class SceneGestureHandlerTest {
mutableUserActionsB.remove(Swipe.Down)
// start accelaratedScroll and scroll over to B -> null
- onDragStartedImmediately()
- onDelta(pixels = up(fractionOfScreen = 0.5f))
- onDelta(pixels = up(fractionOfScreen = 0.5f))
+ val dragController2 = onDragStartedImmediately()
+ dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
+ dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
// here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
// still be called. Make sure that they don't crash or change the scene
- onDelta(pixels = up(fractionOfScreen = 0.5f))
- onDragStopped(velocity = 0f)
+ dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
+ dragController2.onDragStopped(velocity = 0f)
advanceUntilIdle()
assertIdle(SceneB)
// These events can still come in after the animation has settled
- onDelta(pixels = up(fractionOfScreen = 0.5f))
- onDragStopped(velocity = 0f)
+ dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.5f))
+ dragController2.onDragStopped(velocity = 0f)
assertIdle(SceneB)
}
@Test
fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
- onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
+ val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
- onDelta(pixels = up(fractionOfScreen = 0.1f))
+ dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
// target stays B even though UserActions changed
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
- onDragStopped(velocity = down(fractionOfScreen = 0.1f))
+ dragController1.onDragStopped(velocity = down(fractionOfScreen = 0.1f))
advanceUntilIdle()
// now target changed to C for new drag
@@ -525,25 +523,26 @@ class SceneGestureHandlerTest {
@Test
fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest {
- onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
+ val dragController1 = onDragStarted(overSlop = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
mutableUserActionsA[Swipe.Up] = UserActionResult(SceneC)
- onDelta(pixels = up(fractionOfScreen = 0.1f))
- onDragStopped(velocity = down(fractionOfScreen = 0.1f))
+ dragController1.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
+ dragController1.onDragStopped(velocity = down(fractionOfScreen = 0.1f))
// now target changed to C for new drag that started before previous drag settled to Idle
- onDragStartedImmediately()
- onDelta(pixels = up(fractionOfScreen = 0.1f))
+ val dragController2 = onDragStartedImmediately()
+ dragController2.onDragDelta(pixels = up(fractionOfScreen = 0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
}
@Test
fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
- onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
+ val dragController = onDragStarted(overSlop = down(fractionOfScreen = 0.1f))
assertTransition(currentScene = SceneA)
- onDragStopped(velocity = velocityThreshold)
+ dragController.onDragStopped(velocity = velocityThreshold)
+ runCurrent()
assertTransition(currentScene = SceneC)
assertThat(isUserInputOngoing).isFalse()
@@ -632,7 +631,7 @@ class SceneGestureHandlerTest {
// stop scene transition (start the "stop animation")
nestedScroll.preFling(available = Velocity.Zero)
- // a pre scroll event, that could be intercepted by SceneGestureHandler
+ // a pre scroll event, that could be intercepted by DraggableHandlerImpl
nestedScroll.onPreScroll(
available = Offset(0f, secondScroll),
source = NestedScrollSource.Drag
@@ -801,18 +800,6 @@ class SceneGestureHandlerTest {
}
@Test
- fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest {
- onDelta(pixels = down(fractionOfScreen = 0.1f))
- assertIdle(currentScene = SceneA)
- }
-
- @Test
- fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
- onDragStopped(velocity = velocityThreshold)
- assertIdle(currentScene = SceneA)
- }
-
- @Test
fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview)
nestedScroll.preFling(available = Velocity(0f, velocityThreshold))
@@ -826,7 +813,7 @@ class SceneGestureHandlerTest {
val offsetY10 = downOffset(fractionOfScreen = 0.1f)
// Start a drag and then stop it, given that
- onDragStarted(overSlop = up(0.1f))
+ val dragController = onDragStarted(overSlop = up(0.1f))
assertTransition(currentScene = SceneA)
assertThat(progress).isEqualTo(0.1f)
@@ -836,7 +823,7 @@ class SceneGestureHandlerTest {
assertThat(progress).isEqualTo(0.2f)
// this should be ignored, we are scrolling now!
- onDragStopped(-velocityThreshold)
+ dragController.onDragStopped(-velocityThreshold)
assertTransition(currentScene = SceneA)
nestedScroll.scroll(available = -offsetY10)
@@ -865,6 +852,7 @@ class SceneGestureHandlerTest {
currentScene = SceneC,
fromScene = SceneC,
toScene = SceneB,
+ progress = 0.1f,
isUserInputOngoing = true,
)
@@ -873,18 +861,25 @@ class SceneGestureHandlerTest {
// During the current gesture, start a new gesture, still in the middle of the screen. We
// should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
// should be 0f.
- assertThat(sceneGestureHandler.shouldImmediatelyIntercept(middle)).isTrue()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
onDragStartedImmediately(startedPosition = middle)
// We should have intercepted the transition, so the transition should be the same object.
- assertTransition(currentScene = SceneC, fromScene = SceneC, toScene = SceneB)
- assertThat(transitionState).isSameInstanceAs(firstTransition)
+ assertTransition(
+ currentScene = SceneC,
+ fromScene = SceneC,
+ toScene = SceneB,
+ progress = 0.1f,
+ isUserInputOngoing = true,
+ )
+ // We should have a new transition
+ assertThat(transitionState).isNotSameInstanceAs(firstTransition)
// Start a new gesture from the bottom of the screen. Because swiping up from the bottom of
// C leads to scene A (and not B), the previous transitions is *not* intercepted and we
// instead animate from C to A.
val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
- assertThat(sceneGestureHandler.shouldImmediatelyIntercept(bottom)).isFalse()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse()
onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
assertTransition(
@@ -901,12 +896,12 @@ class SceneGestureHandlerTest {
assertIdle(SceneA)
// Swipe up to scene B.
- onDragStarted(overSlop = up(0.1f))
+ val dragController = onDragStarted(overSlop = up(0.1f))
assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB)
// Block the transition when the user release their finger.
canChangeScene = { false }
- onDragStopped(velocity = -velocityThreshold)
+ dragController.onDragStopped(velocity = -velocityThreshold)
advanceUntilIdle()
assertIdle(SceneA)
}
@@ -916,20 +911,44 @@ class SceneGestureHandlerTest {
assertIdle(SceneA)
// Swipe up to B.
- onDragStarted(overSlop = up(0.1f))
+ val dragController1 = onDragStarted(overSlop = up(0.1f))
assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB)
- onDragStopped(velocity = -velocityThreshold)
+ dragController1.onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
// Intercept the transition and swipe down back to scene A.
- assertThat(sceneGestureHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
- onDragStartedImmediately()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
+ val dragController2 = onDragStartedImmediately()
// Block the transition when the user release their finger.
canChangeScene = { false }
- onDragStopped(velocity = velocityThreshold)
+ dragController2.onDragStopped(velocity = velocityThreshold)
advanceUntilIdle()
assertIdle(SceneB)
}
+
+ @Test
+ fun scrollFromIdleWithNoTargetScene_shouldUseOverscrollSpecIfAvailable() = runGestureTest {
+ layoutState.transitions = transitions {
+ overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
+ }
+ // Start at scene C.
+ navigateToSceneC()
+
+ val scene = layoutState.transitionState.currentScene
+ // We should have overscroll spec for scene C
+ assertThat(layoutState.transitions.overscrollSpec(scene, Orientation.Vertical)).isNotNull()
+ assertThat(layoutState.currentOverscrollSpec).isNull()
+
+ val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
+ nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))
+
+ // We scrolled down, under scene C there is nothing, so we can use the overscroll spec
+ assertThat(layoutState.currentOverscrollSpec).isNotNull()
+ assertThat(layoutState.currentOverscrollSpec?.scene).isEqualTo(SceneC)
+ val transition = layoutState.currentTransition
+ assertThat(transition).isNotNull()
+ assertThat(transition!!.progress).isEqualTo(-0.1f)
+ }
}
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 33be1dc83dc8..059a10e23207 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
@@ -18,9 +18,13 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
@@ -34,8 +38,14 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
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.assertTopPositionInRootIsEqualTo
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -528,4 +538,157 @@ class ElementTest {
after { assertThat(fooCompositions).isEqualTo(1) }
}
}
+
+ @Test
+ fun elementTransitionDuringOverscroll() {
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ val overscrollTranslateY = 10.dp
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+
+ val state =
+ MutableSceneTransitionLayoutState(
+ initialScene = TestScenes.SceneA,
+ transitions =
+ transitions {
+ overscroll(TestScenes.SceneB, Orientation.Vertical) {
+ translate(TestElements.Foo, y = overscrollTranslateY)
+ }
+ }
+ )
+ as MutableSceneTransitionLayoutStateImpl
+
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(
+ state = state,
+ modifier = Modifier.size(layoutWidth, layoutHeight)
+ ) {
+ scene(
+ key = TestScenes.SceneA,
+ userActions = mapOf(Swipe.Down to TestScenes.SceneB)
+ ) {
+ Spacer(Modifier.fillMaxSize())
+ }
+ scene(TestScenes.SceneB) {
+ Spacer(Modifier.element(TestElements.Foo).fillMaxSize())
+ }
+ }
+ }
+
+ assertThat(state.currentTransition).isNull()
+ assertThat(state.currentOverscrollSpec).isNull()
+
+ // Swipe by half of verticalSwipeDistance.
+ rule.onRoot().performTouchInput {
+ val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
+ down(middleTop)
+ // Scroll 50%
+ moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+ }
+
+ val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
+ fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+ val transition = state.currentTransition
+ assertThat(transition).isNotNull()
+ assertThat(transition!!.progress).isEqualTo(0.5f)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 100%
+ moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
+ }
+
+ // Scroll 150% (Scene B overscroll by 50%)
+ assertThat(transition.progress).isEqualTo(1.5f)
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 100%
+ moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
+ }
+
+ // Scroll 250% (Scene B overscroll by 150%)
+ assertThat(transition.progress).isEqualTo(2.5f)
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
+ }
+
+ @Test
+ fun elementTransitionDuringNestedScrollOverscroll() {
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ val overscrollTranslateY = 10.dp
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+
+ val state =
+ MutableSceneTransitionLayoutState(
+ initialScene = TestScenes.SceneB,
+ transitions =
+ transitions {
+ overscroll(TestScenes.SceneB, Orientation.Vertical) {
+ translate(TestElements.Foo, y = overscrollTranslateY)
+ }
+ }
+ )
+ as MutableSceneTransitionLayoutStateImpl
+
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(
+ state = state,
+ modifier = Modifier.size(layoutWidth, layoutHeight)
+ ) {
+ scene(TestScenes.SceneA) { Spacer(Modifier.fillMaxSize()) }
+ scene(TestScenes.SceneB, userActions = mapOf(Swipe.Up to TestScenes.SceneA)) {
+ Box(
+ Modifier
+ // Unconsumed scroll gesture will be intercepted by STL
+ .verticalNestedScrollToScene()
+ // A scrollable that does not consume the scroll gesture
+ .scrollable(
+ rememberScrollableState(consumeScrollDelta = { 0f }),
+ Orientation.Vertical
+ )
+ .fillMaxSize()
+ ) {
+ Spacer(Modifier.element(TestElements.Foo).fillMaxSize())
+ }
+ }
+ }
+ }
+
+ assertThat(state.currentTransition).isNull()
+ assertThat(state.currentOverscrollSpec).isNull()
+ val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
+ fooElement.assertTopPositionInRootIsEqualTo(0.dp)
+
+ // Swipe by half of verticalSwipeDistance.
+ rule.onRoot().performTouchInput {
+ val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
+ down(middleTop)
+ // Scroll 50%
+ moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000)
+ }
+
+ val transition = state.currentTransition
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ assertThat(transition).isNotNull()
+ assertThat(transition!!.progress).isEqualTo(-0.5f)
+ fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f)
+
+ rule.onRoot().performTouchInput {
+ // Scroll another 100%
+ moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
+ }
+
+ // Scroll 150% (Scene B overscroll by 50%)
+ assertThat(transition.progress).isEqualTo(-1.5f)
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index cd99d05158cd..d8cf1c12989b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -59,9 +59,18 @@ class MultiPointerDraggableTest {
orientation = Orientation.Vertical,
enabled = { enabled },
startDragImmediately = { false },
- onDragStarted = { _, _, _ -> started = true },
- onDragDelta = { _ -> dragged = true },
- onDragStopped = { stopped = true },
+ onDragStarted = { _, _, _ ->
+ started = true
+ object : DragController {
+ override fun onDrag(delta: Float) {
+ dragged = true
+ }
+
+ override fun onStop(velocity: Float, canChangeScene: Boolean) {
+ stopped = true
+ }
+ }
+ },
)
)
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index f81a7f2908e8..3cbcd73a0adb 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -16,6 +16,8 @@
package com.android.compose.animation.scene
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestScenes.SceneA
@@ -389,4 +391,118 @@ class SceneTransitionLayoutStateTest {
assertThat(childState.isTransitioning(SceneA, SceneB)).isTrue()
assertThat(parentState.isTransitioning(SceneC, SceneD)).isFalse()
}
+
+ private fun startOverscrollableTransistionFromAtoB(
+ progress: () -> Float,
+ sceneTransitions: SceneTransitions,
+ ): MutableSceneTransitionLayoutStateImpl {
+ val state =
+ MutableSceneTransitionLayoutStateImpl(
+ SceneA,
+ sceneTransitions,
+ )
+ state.startTransition(
+ object :
+ TransitionState.Transition(SceneA, SceneB),
+ TransitionState.HasOverscrollProperties {
+ override val currentScene: SceneKey = SceneA
+ override val progress: Float
+ get() = progress()
+
+ override val isInitiatedByUserInput: Boolean = false
+ override val isUserInputOngoing: Boolean = false
+ override val isUpOrLeft: Boolean = false
+ override val orientation: Orientation = Orientation.Vertical
+ },
+ transitionKey = null
+ )
+ assertThat(state.isTransitioning()).isTrue()
+ return state
+ }
+
+ @Test
+ fun overscrollDsl_definedForToScene() = runMonotonicClockTest {
+ val progress = mutableStateOf(0f)
+ val state =
+ startOverscrollableTransistionFromAtoB(
+ progress = { progress.value },
+ sceneTransitions =
+ transitions {
+ overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
+ }
+ )
+ assertThat(state.currentOverscrollSpec).isNull()
+
+ // overscroll for SceneA is NOT defined
+ progress.value = -0.1f
+ assertThat(state.currentOverscrollSpec).isNull()
+
+ // scroll from SceneA to SceneB
+ progress.value = 0.5f
+ assertThat(state.currentOverscrollSpec).isNull()
+
+ progress.value = 1f
+ assertThat(state.currentOverscrollSpec).isNull()
+
+ // overscroll for SceneB is defined
+ progress.value = 1.1f
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ assertThat(state.currentOverscrollSpec?.scene).isEqualTo(SceneB)
+ }
+
+ @Test
+ fun overscrollDsl_definedForFromScene() = runMonotonicClockTest {
+ val progress = mutableStateOf(0f)
+ val state =
+ startOverscrollableTransistionFromAtoB(
+ progress = { progress.value },
+ sceneTransitions =
+ transitions {
+ overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) }
+ }
+ )
+ assertThat(state.currentOverscrollSpec).isNull()
+
+ // overscroll for SceneA is defined
+ progress.value = -0.1f
+ assertThat(state.currentOverscrollSpec).isNotNull()
+ assertThat(state.currentOverscrollSpec?.scene).isEqualTo(SceneA)
+
+ // scroll from SceneA to SceneB
+ progress.value = 0.5f
+ assertThat(state.currentOverscrollSpec).isNull()
+
+ progress.value = 1f
+ assertThat(state.currentOverscrollSpec).isNull()
+
+ // overscroll for SceneB is NOT defined
+ progress.value = 1.1f
+ assertThat(state.currentOverscrollSpec).isNull()
+ }
+
+ @Test
+ fun overscrollDsl_notDefinedScenes() = runMonotonicClockTest {
+ val progress = mutableStateOf(0f)
+ val state =
+ startOverscrollableTransistionFromAtoB(
+ progress = { progress.value },
+ sceneTransitions = transitions {}
+ )
+ assertThat(state.currentOverscrollSpec).isNull()
+
+ // overscroll for SceneA is NOT defined
+ progress.value = -0.1f
+ assertThat(state.currentOverscrollSpec).isNull()
+
+ // scroll from SceneA to SceneB
+ progress.value = 0.5f
+ assertThat(state.currentOverscrollSpec).isNull()
+
+ progress.value = 1f
+ assertThat(state.currentOverscrollSpec).isNull()
+
+ // overscroll for SceneB is NOT defined
+ progress.value = 1.1f
+ assertThat(state.currentOverscrollSpec).isNull()
+ }
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index 1beafcc2cbb2..c9c3eccdedfc 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -20,9 +20,11 @@ import androidx.compose.animation.core.SpringSpec
import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.Orientation
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.transformation.Transformation
import com.android.compose.animation.scene.transformation.TransformationRange
+import com.android.compose.animation.scene.transformation.Translate
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -223,6 +225,17 @@ class TransitionDslTest {
.isSameInstanceAs(specFromAToC)
}
+ @Test
+ fun overscrollSpec() {
+ val transitions = transitions {
+ overscroll(TestScenes.SceneA, Orientation.Vertical) { translate(TestElements.Bar) }
+ }
+
+ val overscrollSpec = transitions.overscrollSpecs.single()
+ val transformation = overscrollSpec.transformationSpec.transformations.single()
+ assertThat(transformation).isInstanceOf(Translate::class.java)
+ }
+
companion object {
private val TRANSFORMATION_RANGE =
Correspondence.transforming<Transformation, TransformationRange?>(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 503463171ae7..92eb8f8c36c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -30,6 +30,7 @@ import android.view.MotionEvent
import android.view.Surface
import android.view.Surface.Rotation
import android.view.View
+import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -46,7 +47,12 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -127,7 +133,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
@Mock private lateinit var defaultUdfpsTouchOverlayViewModel: DefaultUdfpsTouchOverlayViewModel
@Mock
private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate
- @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
@Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
@@ -150,6 +157,19 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
mock(ScreenOffAnimationController::class.java),
statusBarStateController,
)
+ keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(
+ scope = testScope.backgroundScope,
+ repository = keyguardTransitionRepository,
+ fromLockscreenTransitionInteractor = {
+ mock(FromLockscreenTransitionInteractor::class.java)
+ },
+ fromPrimaryBouncerTransitionInteractor = {
+ mock(FromPrimaryBouncerTransitionInteractor::class.java)
+ },
+ fromAodTransitionInteractor = { mock(FromAodTransitionInteractor::class.java) },
+ )
whenever(inflater.inflate(R.layout.udfps_view, null, false)).thenReturn(udfpsView)
whenever(inflater.inflate(R.layout.udfps_bp_view, null))
.thenReturn(mock(UdfpsBpView::class.java))
@@ -159,11 +179,25 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
.thenReturn(mock(UdfpsFpmEmptyView::class.java))
}
+ private suspend fun withReasonSuspend(
+ @RequestReason reason: Int,
+ isDebuggable: Boolean = false,
+ enableDeviceEntryUdfpsRefactor: Boolean = false,
+ block: suspend () -> Unit,
+ ) {
+ withReason(
+ reason,
+ isDebuggable,
+ enableDeviceEntryUdfpsRefactor,
+ )
+ block()
+ }
+
private fun withReason(
@RequestReason reason: Int,
isDebuggable: Boolean = false,
enableDeviceEntryUdfpsRefactor: Boolean = false,
- block: () -> Unit,
+ block: () -> Unit = {},
) {
if (enableDeviceEntryUdfpsRefactor) {
mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
@@ -312,6 +346,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
lastWakeReason = WakeSleepReason.POWER_BUTTON,
lastSleepReason = WakeSleepReason.OTHER,
)
+ runCurrent()
controllerOverlay.show(udfpsController, overlayParams)
runCurrent()
verify(windowManager).addView(any(), any())
@@ -321,15 +356,25 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
@Test
fun showUdfpsOverlay_whileGoingToSleep() =
testScope.runTest {
- withReason(REASON_AUTH_KEYGUARD) {
+ withReasonSuspend(REASON_AUTH_KEYGUARD) {
mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OFF,
+ to = KeyguardState.GONE,
+ testScope = this,
+ )
powerRepository.updateWakefulness(
rawState = WakefulnessState.STARTING_TO_SLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
lastSleepReason = WakeSleepReason.OTHER,
)
+ runCurrent()
+
+ // WHEN a request comes to show the view
controllerOverlay.show(udfpsController, overlayParams)
runCurrent()
+
+ // THEN the view does not get added immediately
verify(windowManager, never()).addView(any(), any())
// we hide to end the job that listens for the finishedGoingToSleep signal
@@ -338,25 +383,82 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
}
@Test
- fun showUdfpsOverlay_afterFinishedGoingToSleep() =
+ fun showUdfpsOverlay_whileAsleep() =
testScope.runTest {
- withReason(REASON_AUTH_KEYGUARD) {
+ withReasonSuspend(REASON_AUTH_KEYGUARD) {
mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OFF,
+ to = KeyguardState.GONE,
+ testScope = this,
+ )
powerRepository.updateWakefulness(
- rawState = WakefulnessState.STARTING_TO_SLEEP,
+ rawState = WakefulnessState.ASLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
lastSleepReason = WakeSleepReason.OTHER,
)
+ runCurrent()
+
+ // WHEN a request comes to show the view
controllerOverlay.show(udfpsController, overlayParams)
runCurrent()
+
+ // THEN view isn't added yet
verify(windowManager, never()).addView(any(), any())
+ // we hide to end the job that listens for the finishedGoingToSleep signal
+ controllerOverlay.hide()
+ }
+ }
+
+ @Test
+ fun neverRemoveViewThatHasNotBeenAdded() =
+ testScope.runTest {
+ withReasonSuspend(REASON_AUTH_KEYGUARD) {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
+ controllerOverlay.show(udfpsController, overlayParams)
+ val view = controllerOverlay.getTouchOverlay()
+ view?.let {
+ // parent is null, signalling that the view was never added
+ whenever(view.parent).thenReturn(null)
+ }
+ verify(windowManager, never()).removeView(eq(view))
+ }
+ }
+
+ @Test
+ fun showUdfpsOverlay_afterFinishedTransitioningToAOD() =
+ testScope.runTest {
+ withReasonSuspend(REASON_AUTH_KEYGUARD) {
+ mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OFF,
+ to = KeyguardState.GONE,
+ testScope = this,
+ )
powerRepository.updateWakefulness(
- rawState = WakefulnessState.ASLEEP,
+ rawState = WakefulnessState.STARTING_TO_SLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
lastSleepReason = WakeSleepReason.OTHER,
)
runCurrent()
+
+ // WHEN a request comes to show the view
+ controllerOverlay.show(udfpsController, overlayParams)
+ runCurrent()
+
+ // THEN the view does not get added immediately
+ verify(windowManager, never()).addView(any(), any())
+
+ // WHEN the device finishes transitioning to AOD
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ testScope = this,
+ )
+ runCurrent()
+
+ // THEN the view gets added
verify(windowManager)
.addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture())
}
@@ -387,6 +489,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
private fun hideUdfpsOverlay() {
val didShow = controllerOverlay.show(udfpsController, overlayParams)
val view = controllerOverlay.getTouchOverlay()
+ view?.let { whenever(view.parent).thenReturn(mock(ViewGroup::class.java)) }
val didHide = controllerOverlay.hide()
verify(windowManager).removeView(eq(view))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 561cdbbf66ce..9b0b5dea0ad7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -63,6 +63,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -234,6 +235,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
private IUdfpsOverlayController mOverlayController;
@Captor
+ private ArgumentCaptor<View> mViewCaptor;
+ @Captor
private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
@Captor
private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
@@ -550,8 +553,11 @@ public class UdfpsControllerTest extends SysuiTestCase {
mOpticalProps.sensorId,
BiometricRequestConstants.REASON_ENROLL_ENROLLING,
mUdfpsOverlayControllerCallback);
+
mFgExecutor.runAllReady();
- verify(mWindowManager).addView(any(), any());
+ verify(mWindowManager).addView(mViewCaptor.capture(), any());
+ when(mViewCaptor.getValue().getParent())
+ .thenReturn(mock(ViewGroup.class));
// Update overlay parameters.
reset(mWindowManager);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index cd296524c17c..6e3573b64f9a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -27,6 +27,7 @@ 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
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
@@ -62,6 +63,7 @@ import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -831,6 +833,95 @@ class CommunalInteractorTest : SysuiTestCase() {
}
}
+ @Test
+ fun widgetContent_containsDisabledWidgets_whenCategoryNotAllowed() =
+ testScope.runTest {
+ // Communal available, and tutorial completed.
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ userRepository.setSelectedUserInfo(mainUser)
+
+ val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ runCurrent()
+
+ // Widgets available.
+ val widget1 =
+ createWidgetWithCategory(1, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN)
+ val widget2 =
+ createWidgetWithCategory(2, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
+ val widget3 =
+ createWidgetWithCategory(3, AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX)
+ val widgets = listOf(widget1, widget2, widget3)
+ widgetRepository.setCommunalWidgets(widgets)
+
+ val widgetContent by collectLastValue(underTest.widgetContent)
+ kosmos.fakeSettings.putIntForUser(
+ CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+ mainUser.id
+ )
+ runCurrent()
+
+ // Only the keyguard widget is enabled.
+ assertThat(widgetContent).hasSize(3)
+ assertThat(widgetContent!!.get(0))
+ .isInstanceOf(CommunalContentModel.WidgetContent.DisabledWidget::class.java)
+ assertThat(widgetContent!!.get(1))
+ .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
+ assertThat(widgetContent!!.get(2))
+ .isInstanceOf(CommunalContentModel.WidgetContent.DisabledWidget::class.java)
+ }
+
+ @Test
+ fun widgetContent_allEnabled_whenCategoryAllowed() =
+ testScope.runTest {
+ // Communal available, and tutorial completed.
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+ userRepository.setSelectedUserInfo(mainUser)
+
+ val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
+ userRepository.setUserInfos(userInfos)
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ runCurrent()
+
+ // Widgets available.
+ val widget1 =
+ createWidgetWithCategory(1, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN)
+ val widget2 =
+ createWidgetWithCategory(2, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
+ val widget3 =
+ createWidgetWithCategory(3, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
+ val widgets = listOf(widget1, widget2, widget3)
+ widgetRepository.setCommunalWidgets(widgets)
+
+ val widgetContent by collectLastValue(underTest.widgetContent)
+ kosmos.fakeSettings.putIntForUser(
+ CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD or
+ AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
+ mainUser.id
+ )
+ runCurrent()
+
+ // All widgets are enabled.
+ assertThat(widgetContent).hasSize(3)
+ widgetContent!!.forEach { model ->
+ assertThat(model)
+ .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
+ }
+ }
+
private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
val timer = mock(SmartspaceTarget::class.java)
whenever(timer.smartspaceTargetId).thenReturn(id)
@@ -848,6 +939,17 @@ class CommunalInteractorTest : SysuiTestCase() {
whenever(this.providerInfo).thenReturn(providerInfo)
}
+ private fun createWidgetWithCategory(
+ appWidgetId: Int,
+ category: Int
+ ): CommunalWidgetContentModel =
+ mock<CommunalWidgetContentModel> {
+ whenever(this.appWidgetId).thenReturn(appWidgetId)
+ val providerInfo = mock<AppWidgetProviderInfo>().apply { widgetCategory = category }
+ whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
+ whenever(this.providerInfo).thenReturn(providerInfo)
+ }
+
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 5ee88cb92fa0..8e2e94716660 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -135,9 +135,9 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
// Only Widgets and CTA tile are shown.
assertThat(communalContent?.size).isEqualTo(3)
assertThat(communalContent?.get(0))
- .isInstanceOf(CommunalContentModel.Widget::class.java)
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
assertThat(communalContent?.get(1))
- .isInstanceOf(CommunalContentModel.Widget::class.java)
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
assertThat(communalContent?.get(2))
.isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
}
@@ -181,9 +181,9 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
// Widgets and CTA tile are shown.
assertThat(communalContent?.size).isEqualTo(3)
assertThat(communalContent?.get(0))
- .isInstanceOf(CommunalContentModel.Widget::class.java)
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
assertThat(communalContent?.get(1))
- .isInstanceOf(CommunalContentModel.Widget::class.java)
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
assertThat(communalContent?.get(2))
.isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
@@ -192,7 +192,8 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
// Only one widget and CTA tile remain.
assertThat(communalContent?.size).isEqualTo(2)
val item = communalContent?.get(0)
- val appWidgetId = if (item is CommunalContentModel.Widget) item.appWidgetId else null
+ val appWidgetId =
+ if (item is CommunalContentModel.WidgetContent) item.appWidgetId else null
assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId)
assertThat(communalContent?.get(1))
.isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 1e523dd2a9cc..563aad1920f7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -184,9 +184,9 @@ class CommunalViewModelTest : SysuiTestCase() {
.isInstanceOf(CommunalContentModel.Smartspace::class.java)
assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java)
assertThat(communalContent?.get(2))
- .isInstanceOf(CommunalContentModel.Widget::class.java)
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
assertThat(communalContent?.get(3))
- .isInstanceOf(CommunalContentModel.Widget::class.java)
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
assertThat(communalContent?.get(4))
.isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
index 12611cbd8c63..88f5e1b85840 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
@@ -17,8 +17,10 @@
package com.android.systemui.communal.widgets
import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.content.pm.UserInfo
+import android.os.Bundle
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -33,8 +35,8 @@ import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,6 +45,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -91,7 +94,7 @@ class CommunalWidgetHostTest : SysuiTestCase() {
any<Int>(),
any<UserHandle>(),
any<ComponentName>(),
- nullable()
+ any<Bundle>(),
)
)
.thenReturn(true)
@@ -100,8 +103,14 @@ class CommunalWidgetHostTest : SysuiTestCase() {
val result = underTest.allocateIdAndBindWidget(provider)
verify(appWidgetHost).allocateAppWidgetId()
- verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null)
+ val bundle =
+ withArgCaptor<Bundle> {
+ verify(appWidgetManager)
+ .bindAppWidgetIdIfAllowed(eq(widgetId), eq(user), eq(provider), capture())
+ }
assertThat(result).isEqualTo(widgetId)
+ assertThat(bundle.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY))
+ .isEqualTo(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
}
@Test
@@ -117,7 +126,7 @@ class CommunalWidgetHostTest : SysuiTestCase() {
any<Int>(),
any<UserHandle>(),
any<ComponentName>(),
- nullable()
+ any<Bundle>()
)
)
.thenReturn(true)
@@ -126,8 +135,14 @@ class CommunalWidgetHostTest : SysuiTestCase() {
val result = underTest.allocateIdAndBindWidget(provider, user)
verify(appWidgetHost).allocateAppWidgetId()
- verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null)
+ val bundle =
+ withArgCaptor<Bundle> {
+ verify(appWidgetManager)
+ .bindAppWidgetIdIfAllowed(eq(widgetId), eq(user), eq(provider), capture())
+ }
assertThat(result).isEqualTo(widgetId)
+ assertThat(bundle.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY))
+ .isEqualTo(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
}
@Test
@@ -144,14 +159,15 @@ class CommunalWidgetHostTest : SysuiTestCase() {
any<Int>(),
any<UserHandle>(),
any<ComponentName>(),
- nullable()
+ any<Bundle>()
)
)
.thenReturn(false)
val result = underTest.allocateIdAndBindWidget(provider, user)
verify(appWidgetHost).allocateAppWidgetId()
- verify(appWidgetManager).bindAppWidgetIdIfAllowed(widgetId, user, provider, null)
+ verify(appWidgetManager)
+ .bindAppWidgetIdIfAllowed(eq(widgetId), eq(user), eq(provider), any())
verify(appWidgetHost).deleteAppWidgetId(widgetId)
assertThat(result).isNull()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 558e7e6564ae..3a28471b5f9a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticati
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
@@ -215,6 +216,39 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
}
@Test
+ fun onFingerprintFailed_failedAuthenticationStatusWithOtherStatuses() =
+ testScope.runTest {
+ val failStatus by
+ collectLastValue(
+ underTest.authenticationStatus.filterIsInstance<
+ FailFingerprintAuthenticationStatus
+ >()
+ )
+ runCurrent()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ updateMonitorCallback.value.onBiometricAcquired(
+ BiometricSourceType.FINGERPRINT,
+ /* acquireInfo */ 0,
+ )
+ updateMonitorCallback.value.onBiometricAuthFailed(
+ BiometricSourceType.FINGERPRINT,
+ )
+ updateMonitorCallback.value.onBiometricHelp(
+ /* msgId */ 7,
+ /* errString */ "Not recognized.",
+ BiometricSourceType.FINGERPRINT,
+ )
+ updateMonitorCallback.value.onBiometricError(
+ /* msgId */ 7,
+ /* errString */ "Too many attempts.",
+ BiometricSourceType.FINGERPRINT,
+ )
+
+ assertThat(failStatus).isNotNull()
+ }
+
+ @Test
fun onFingerprintError_errorAuthenticationStatus() =
testScope.runTest {
val authenticationStatus by collectLastValue(underTest.authenticationStatus)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 3484025f8d80..cd4db2fbf55f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -79,34 +79,66 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
}
@Test
- fun dozeAmountTransitionTest() = runTest {
- val dozeAmountSteps by collectValues(underTest.dozeAmountTransition)
+ fun dozeAmountTransitionTest_AodToFromLockscreen() =
+ testScope.runTest {
+ val dozeAmountSteps by collectValues(underTest.dozeAmountTransition)
- val steps = mutableListOf<TransitionStep>()
+ val steps = mutableListOf<TransitionStep>()
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0.8f, RUNNING))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.8f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
- steps.forEach {
- repository.sendTransitionStep(it)
- runCurrent()
+ steps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
+
+ assertThat(dozeAmountSteps.subList(0, 3))
+ .isEqualTo(
+ listOf(
+ steps[0].copy(value = 1f - steps[0].value),
+ steps[1].copy(value = 1f - steps[1].value),
+ steps[2].copy(value = 1f - steps[2].value),
+ )
+ )
+ assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7))
}
- assertThat(dozeAmountSteps.subList(0, 3))
- .isEqualTo(
- listOf(
- steps[0].copy(value = 1f - steps[0].value),
- steps[1].copy(value = 1f - steps[1].value),
- steps[2].copy(value = 1f - steps[2].value),
+ @Test
+ fun dozeAmountTransitionTest_AodToFromGone() =
+ testScope.runTest {
+ val dozeAmountSteps by collectValues(underTest.dozeAmountTransition)
+
+ val steps = mutableListOf<TransitionStep>()
+
+ steps.add(TransitionStep(AOD, GONE, 0f, STARTED))
+ steps.add(TransitionStep(AOD, GONE, 0.3f, RUNNING))
+ steps.add(TransitionStep(AOD, GONE, 1f, FINISHED))
+ steps.add(TransitionStep(GONE, AOD, 0f, STARTED))
+ steps.add(TransitionStep(GONE, AOD, 0.1f, RUNNING))
+ steps.add(TransitionStep(GONE, AOD, 0.3f, RUNNING))
+ steps.add(TransitionStep(GONE, AOD, 1f, FINISHED))
+
+ steps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
+
+ assertThat(dozeAmountSteps.subList(0, 3))
+ .isEqualTo(
+ listOf(
+ steps[0].copy(value = 1f - steps[0].value),
+ steps[1].copy(value = 1f - steps[1].value),
+ steps[2].copy(value = 1f - steps[2].value),
+ )
)
- )
- assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7))
- }
+ assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7))
+ }
@Test
fun finishedKeyguardStateTests() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt
index 4c972e9195e9..4c972e9195e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt
index db8fbf604430..db8fbf604430 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
index ad2ae8b41af9..ad2ae8b41af9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
index 4a10d80430e9..4a10d80430e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index 0b80ff8d6ca4..ad1cef1c1e92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -36,7 +37,6 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-@android.platform.test.annotations.EnabledOnRavenwood
class LockscreenContentViewModelTest : SysuiTestCase() {
private val kosmos: Kosmos = testKosmos()
@@ -47,6 +47,7 @@ class LockscreenContentViewModelTest : SysuiTestCase() {
fun setup() {
with(kosmos) {
fakeFeatureFlagsClassic.set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, true)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
underTest = lockscreenContentViewModel
}
}
@@ -88,11 +89,21 @@ class LockscreenContentViewModelTest : SysuiTestCase() {
}
@Test
+ fun areNotificationsVisible_splitShadeTrue_true() =
+ with(kosmos) {
+ testScope.runTest {
+ overrideResource(R.bool.config_use_split_notification_shade, true)
+ kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
+
+ assertThat(underTest.areNotificationsVisible(context.resources)).isTrue()
+ }
+ }
+ @Test
fun areNotificationsVisible_withSmallClock_true() =
with(kosmos) {
testScope.runTest {
kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.SMALL)
- assertThat(underTest.areNotificationsVisible).isTrue()
+ assertThat(underTest.areNotificationsVisible(context.resources)).isTrue()
}
}
@@ -101,7 +112,7 @@ class LockscreenContentViewModelTest : SysuiTestCase() {
with(kosmos) {
testScope.runTest {
kosmos.fakeKeyguardClockRepository.setClockSize(KeyguardClockSwitch.LARGE)
- assertThat(underTest.areNotificationsVisible).isFalse()
+ assertThat(underTest.areNotificationsVisible(context.resources)).isFalse()
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
index e139466c8096..bef951554b50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt
@@ -36,10 +36,10 @@ import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.testKosmos
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Test
import org.junit.runner.RunWith
@ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt
index 28f5eba28763..86b3f33b7555 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModelTest.kt
@@ -102,6 +102,24 @@ class LockscreenToDozingTransitionViewModelTest : SysuiTestCase() {
values.forEach { assertThat(it).isEqualTo(0f) }
}
+
+ @Test
+ fun lockscreenAlphaFadesOutAndFinishesVisible() =
+ testScope.runTest {
+ val alpha by collectValues(underTest.lockscreenAlpha)
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ testScope,
+ )
+
+ assertThat(alpha[0]).isEqualTo(1f)
+ // Halfway through, it will have faded out
+ assertThat(alpha[1]).isEqualTo(0f)
+ // FINISHED alpha should be visible, to support pulsing
+ assertThat(alpha[2]).isEqualTo(1f)
+ }
+
@Test
fun deviceEntryBackgroundViewDisappear() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
index e7aaddd94695..e7aaddd94695 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
index 7a564aca00bb..43ab93a18118 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt
@@ -33,10 +33,10 @@ import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.testKosmos
import com.google.common.collect.Range
import com.google.common.truth.Truth
-import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Test
import org.junit.runner.RunWith
@ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
index 1912987cc447..1912987cc447 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
index c55c27c3b516..c55c27c3b516 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 47e1ee9c1b71..db1d5d91eb65 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -149,6 +149,42 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
values.forEach { assertThat(it).isEqualTo(1f) }
}
+ @Test
+ fun notificationAlpha() =
+ testScope.runTest {
+ val values by collectValues(underTest.notificationAlpha)
+ runCurrent()
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+
+ assertThat(values[0]).isEqualTo(1f)
+ // Should fade to zero between here
+ assertThat(values[1]).isEqualTo(0f)
+ }
+
+ @Test
+ fun notificationAlpha_leaveShadeOpen() =
+ testScope.runTest {
+ val values by collectValues(underTest.notificationAlpha)
+ runCurrent()
+
+ sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(true)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+
+ assertThat(values.size).isEqualTo(2)
+ // Shade stays open, and alpha should remain visible
+ values.forEach { assertThat(it).isEqualTo(1f) }
+ }
+
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
index 0796af065790..0796af065790 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/domain/interactor/SpatializerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/domain/interactor/SpatializerInteractorTest.kt
new file mode 100644
index 000000000000..a932dd6d106d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/domain/interactor/SpatializerInteractorTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.domain.interactor
+
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.spatializerRepository
+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 SpatializerInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val underTest = SpatializerInteractor(kosmos.spatializerRepository)
+
+ @Test
+ fun setSpatialAudioEnabledFalse_isEnabled_false() {
+ with(kosmos) {
+ testScope.runTest {
+ underTest.setSpatialAudioEnabled(deviceAttributes, false)
+
+ assertThat(underTest.isSpatialAudioEnabled(deviceAttributes)).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun setSpatialAudioEnabledTrue_isEnabled_true() {
+ with(kosmos) {
+ testScope.runTest {
+ underTest.setSpatialAudioEnabled(deviceAttributes, true)
+
+ assertThat(underTest.isSpatialAudioEnabled(deviceAttributes)).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun setHeadTrackingEnabledFalse_isEnabled_false() {
+ with(kosmos) {
+ testScope.runTest {
+ underTest.setHeadTrackingEnabled(deviceAttributes, false)
+
+ assertThat(underTest.isHeadTrackingEnabled(deviceAttributes)).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun setHeadTrackingEnabledTrue_isEnabled_true() {
+ with(kosmos) {
+ testScope.runTest {
+ underTest.setHeadTrackingEnabled(deviceAttributes, true)
+
+ assertThat(underTest.isHeadTrackingEnabled(deviceAttributes)).isTrue()
+ }
+ }
+ }
+
+ private companion object {
+ val deviceAttributes =
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ "test_address",
+ )
+ }
+}
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 4e72843922e1..667f516317be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -40,6 +40,7 @@ import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.classifier.falsingManager
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
@@ -265,6 +266,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
displayId = displayTracker.defaultDisplayId,
sceneLogger = mock(),
falsingCollector = kosmos.falsingCollector,
+ falsingManager = kosmos.falsingManager,
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
@@ -613,6 +615,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
private fun TestScope.emulatePendingTransitionProgress(
expectedVisible: Boolean = true,
) {
+ val isVisible by collectLastValue(sceneContainerViewModel.isVisible)
assertWithMessage("The FakeSceneDataSource has to be paused for this to do anything.")
.that(fakeSceneDataSource.isPaused)
.isTrue()
@@ -649,7 +652,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
runCurrent()
assertWithMessage("Visibility mismatch after scene transition from $from to $to!")
- .that(sceneContainerViewModel.isVisible.value)
+ .that(isVisible)
.isEqualTo(expectedVisible)
assertThat(sceneContainerViewModel.currentScene.value).isEqualTo(to)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractorTest.kt
new file mode 100644
index 000000000000..9b0adb172e8d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractorTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.interactor
+
+import android.platform.test.annotations.DisableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.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.kosmos.testScope
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.panelExpansionInteractor
+import com.android.systemui.testKosmos
+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
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PanelExpansionInteractorTest : 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 transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(SceneKey.Lockscreen)
+ )
+ private val fakeSceneDataSource = kosmos.fakeSceneDataSource
+ private val fakeShadeRepository = kosmos.fakeShadeRepository
+
+ private lateinit var underTest: PanelExpansionInteractor
+
+ @Before
+ fun setUp() {
+ sceneInteractor.setTransitionState(transitionState)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun legacyPanelExpansion_whenIdle_whenLocked() =
+ testScope.runTest {
+ underTest = kosmos.panelExpansionInteractor
+ setUnlocked(false)
+ val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
+
+ changeScene(SceneKey.Lockscreen) { assertThat(panelExpansion).isEqualTo(1f) }
+ assertThat(panelExpansion).isEqualTo(1f)
+
+ changeScene(SceneKey.Bouncer) { assertThat(panelExpansion).isEqualTo(1f) }
+ assertThat(panelExpansion).isEqualTo(1f)
+
+ changeScene(SceneKey.Shade) { assertThat(panelExpansion).isEqualTo(1f) }
+ assertThat(panelExpansion).isEqualTo(1f)
+
+ changeScene(SceneKey.QuickSettings) { assertThat(panelExpansion).isEqualTo(1f) }
+ assertThat(panelExpansion).isEqualTo(1f)
+
+ changeScene(SceneKey.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
+ assertThat(panelExpansion).isEqualTo(1f)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun legacyPanelExpansion_whenIdle_whenUnlocked() =
+ testScope.runTest {
+ underTest = kosmos.panelExpansionInteractor
+ setUnlocked(true)
+ val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
+
+ changeScene(SceneKey.Gone) { assertThat(panelExpansion).isEqualTo(0f) }
+ assertThat(panelExpansion).isEqualTo(0f)
+
+ changeScene(SceneKey.Shade) { progress ->
+ assertThat(panelExpansion).isEqualTo(progress)
+ }
+ assertThat(panelExpansion).isEqualTo(1f)
+
+ changeScene(SceneKey.QuickSettings) {
+ // Shade's already expanded, so moving to QS should also be 1f.
+ assertThat(panelExpansion).isEqualTo(1f)
+ }
+ assertThat(panelExpansion).isEqualTo(1f)
+
+ changeScene(SceneKey.Communal) { assertThat(panelExpansion).isEqualTo(1f) }
+ assertThat(panelExpansion).isEqualTo(1f)
+ }
+
+ @Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
+ fun legacyPanelExpansion_whenInLegacyMode() =
+ testScope.runTest {
+ underTest = kosmos.panelExpansionInteractor
+ val leet = 0.1337f
+ fakeShadeRepository.setLegacyShadeExpansion(leet)
+ setUnlocked(false)
+ val panelExpansion by collectLastValue(underTest.legacyPanelExpansion)
+
+ changeScene(SceneKey.Lockscreen)
+ assertThat(panelExpansion).isEqualTo(leet)
+
+ changeScene(SceneKey.Bouncer)
+ assertThat(panelExpansion).isEqualTo(leet)
+
+ changeScene(SceneKey.Shade)
+ assertThat(panelExpansion).isEqualTo(leet)
+
+ changeScene(SceneKey.QuickSettings)
+ assertThat(panelExpansion).isEqualTo(leet)
+
+ changeScene(SceneKey.Communal)
+ assertThat(panelExpansion).isEqualTo(leet)
+ }
+
+ 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) = {},
+ ) {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val progressFlow = MutableStateFlow(0f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = checkNotNull(currentScene),
+ toScene = toScene,
+ progress = progressFlow,
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ )
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ progressFlow.value = 0.2f
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ progressFlow.value = 0.6f
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ progressFlow.value = 1f
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ transitionState.value = ObservableTransitionState.Idle(toScene)
+ fakeSceneDataSource.changeScene(toScene)
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ assertThat(currentScene).isEqualTo(toScene)
+ }
+}
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 dd3eb6845789..db94c39e1cb1 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
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.scene.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -31,6 +33,7 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
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.runCurrent
@@ -275,4 +278,18 @@ class SceneInteractorTest : SysuiTestCase() {
underTest.setVisible(true, "reason")
assertThat(isVisible).isTrue()
}
+
+ @Test
+ fun isVisible_duringRemoteUserInteraction_forcedVisible() =
+ testScope.runTest {
+ underTest.setVisible(false, "reason")
+ val isVisible by collectLastValue(underTest.isVisible)
+ assertThat(isVisible).isFalse()
+ underTest.onRemoteUserInteractionStarted("reason")
+ assertThat(isVisible).isTrue()
+
+ underTest.onUserInteractionFinished()
+
+ assertThat(isVisible).isFalse()
+ }
}
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 f49b4777cf14..4e1623661a58 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
@@ -32,6 +32,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.classifier.falsingManager
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -115,6 +116,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
displayId = Display.DEFAULT_DISPLAY,
sceneLogger = mock(),
falsingCollector = falsingCollector,
+ falsingManager = kosmos.falsingManager,
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
simBouncerInteractor = { kosmos.simBouncerInteractor },
@@ -970,6 +972,20 @@ class SceneContainerStartableTest : SysuiTestCase() {
)
}
+ @Test
+ fun respondToFalsingDetections() =
+ testScope.runTest {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val transitionStateFlow = prepareState()
+ underTest.start()
+ emulateSceneTransition(transitionStateFlow, toScene = SceneKey.Bouncer)
+ assertThat(currentScene).isNotEqualTo(SceneKey.Lockscreen)
+
+ kosmos.falsingManager.sendFalsingBelief()
+
+ assertThat(currentScene).isEqualTo(SceneKey.Lockscreen)
+ }
+
private fun TestScope.emulateSceneTransition(
transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
toScene: SceneKey,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index ffbdafe338e7..27ae8b60009c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.systemui.scene.ui.viewmodel
+import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -35,9 +34,9 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -50,7 +49,7 @@ class SceneContainerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope by lazy { kosmos.testScope }
- private val interactor by lazy { kosmos.sceneInteractor }
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val fakeSceneDataSource = kosmos.fakeSceneDataSource
private val sceneContainerConfig = kosmos.sceneContainerConfig
private val falsingManager = kosmos.fakeFalsingManager
@@ -62,7 +61,7 @@ class SceneContainerViewModelTest : SysuiTestCase() {
kosmos.fakeSceneContainerFlags.enabled = true
underTest =
SceneContainerViewModel(
- sceneInteractor = interactor,
+ sceneInteractor = sceneInteractor,
falsingInteractor = kosmos.falsingInteractor,
powerInteractor = kosmos.powerInteractor,
)
@@ -74,10 +73,10 @@ class SceneContainerViewModelTest : SysuiTestCase() {
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
- interactor.setVisible(false, "reason")
+ sceneInteractor.setVisible(false, "reason")
assertThat(isVisible).isFalse()
- interactor.setVisible(true, "reason")
+ sceneInteractor.setVisible(true, "reason")
assertThat(isVisible).isTrue()
}
@@ -199,4 +198,20 @@ class SceneContainerViewModelTest : SysuiTestCase() {
underTest.onMotionEvent(mock())
assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
}
+
+ @Test
+ fun remoteUserInteraction_keepsContainerVisible() =
+ testScope.runTest {
+ sceneInteractor.setVisible(false, "reason")
+ val isVisible by collectLastValue(underTest.isVisible)
+ assertThat(isVisible).isFalse()
+ sceneInteractor.onRemoteUserInteractionStarted("reason")
+ assertThat(isVisible).isTrue()
+
+ underTest.onMotionEvent(
+ mock { whenever(actionMasked).thenReturn(MotionEvent.ACTION_UP) }
+ )
+
+ assertThat(isVisible).isFalse()
+ }
}
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 0641c610aaff..7f5a658587f3 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
@@ -47,8 +47,6 @@ import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
-import com.android.systemui.statusbar.policy.SplitShadeStateController
-import com.android.systemui.statusbar.policy.splitShadeStateController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -68,7 +66,6 @@ import org.mockito.Mockito.mock
@RunWith(AndroidJUnit4::class)
class SharedNotificationContainerViewModelTest : SysuiTestCase() {
val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
- val splitShadeStateController = mock(SplitShadeStateController::class.java)
lateinit var translationYFlow: MutableStateFlow<Float>
val kosmos =
@@ -81,7 +78,6 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
init {
kosmos.aodBurnInViewModel = aodBurnInViewModel
- kosmos.splitShadeStateController = splitShadeStateController
}
val testScope = kosmos.testScope
val configurationRepository = kosmos.fakeConfigurationRepository
@@ -98,7 +94,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
@Before
fun setUp() {
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())).thenReturn(false)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
translationYFlow = MutableStateFlow(0f)
whenever(aodBurnInViewModel.translationY(any())).thenReturn(translationYFlow)
underTest = kosmos.sharedNotificationContainerViewModel
@@ -107,8 +103,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
@Test
fun validateMarginStartInSplitShade() =
testScope.runTest {
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(true)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
val dimens by collectLastValue(underTest.configurationBasedDimensions)
@@ -121,8 +116,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
@Test
fun validateMarginStart() =
testScope.runTest {
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(false)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
overrideResource(R.dimen.notification_panel_margin_horizontal, 20)
val dimens by collectLastValue(underTest.configurationBasedDimensions)
@@ -137,8 +131,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
testScope.runTest {
mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(true)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -156,8 +149,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
testScope.runTest {
mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX)
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(true)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -172,8 +164,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
@Test
fun validatePaddingTop() =
testScope.runTest {
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(false)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -431,8 +422,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
val bounds by collectLastValue(underTest.bounds)
// When not in split shade
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(false)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
configurationRepository.onAnyConfigurationChange()
runCurrent()
@@ -454,8 +444,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
// When in split shade
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(true)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -483,8 +472,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
// When in split shade
whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5)
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(true)
+ overrideResource(R.bool.config_use_split_notification_shade, true)
overrideResource(R.bool.config_use_large_screen_shade_header, true)
overrideResource(R.dimen.large_screen_shade_header_height, 10)
overrideResource(R.dimen.keyguard_split_shade_top_margin, 50)
@@ -543,8 +531,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
showLockscreen()
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(false)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
configurationRepository.onAnyConfigurationChange()
keyguardInteractor.setNotificationContainerBounds(
NotificationContainerBounds(top = 1f, bottom = 2f)
@@ -567,8 +554,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
showLockscreen()
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(false)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
configurationRepository.onAnyConfigurationChange()
keyguardInteractor.setNotificationContainerBounds(
NotificationContainerBounds(top = 1f, bottom = 2f)
@@ -604,8 +590,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
// Show lockscreen with shade expanded
showLockscreenWithShadeExpanded()
- whenever(splitShadeStateController.shouldUseSplitNotificationShade(any()))
- .thenReturn(false)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
configurationRepository.onAnyConfigurationChange()
keyguardInteractor.setNotificationContainerBounds(
NotificationContainerBounds(top = 1f, bottom = 2f)
@@ -701,6 +686,32 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
assertThat(fadeIn).isEqualTo(false)
}
+ @Test
+ fun shadeCollapseFadeIn_doesNotRunIfTransitioningToAod() =
+ testScope.runTest {
+ val fadeIn by collectLastValue(underTest.shadeCollapseFadeIn)
+
+ // Start on lockscreen without the shade
+ underTest.setShadeCollapseFadeInComplete(false)
+ showLockscreen()
+ assertThat(fadeIn).isEqualTo(false)
+
+ // ... then the shade expands
+ showLockscreenWithShadeExpanded()
+ assertThat(fadeIn).isEqualTo(false)
+
+ // ... then user hits power to go to AOD
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope,
+ )
+ // ... followed by a shade collapse
+ showLockscreen()
+ // ... does not trigger a fade in
+ assertThat(fadeIn).isEqualTo(false)
+ }
+
private suspend fun TestScope.showLockscreen() {
shadeRepository.setLockscreenShadeExpansion(0f)
shadeRepository.setQsExpansion(0f)
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 87d25ddcc75c..db4d42f4c864 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
@@ -19,8 +19,6 @@ package com.android.systemui.statusbar.policy;
import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
-import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
import static com.google.common.truth.Truth.assertThat;
@@ -41,31 +39,24 @@ import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
-import android.content.Context;
import android.content.Intent;
-import android.graphics.Region;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.testing.TestableLooper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.SmallTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
-import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.FakeGlobalSettings;
-import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.util.time.SystemClock;
import org.junit.Rule;
import org.junit.Test;
@@ -81,17 +72,13 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
- private static final String TEST_PACKAGE_NAME = "BaseHeadsUpManagerTest";
-
- private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
- private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
+ static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
+ static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
- private static final int TEST_UID = 0;
-
protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
protected static final int TEST_AUTO_DISMISS_TIME = 600;
protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
@@ -110,143 +97,6 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
}
- private final class TestableHeadsUpManager extends BaseHeadsUpManager {
-
- private HeadsUpEntry mLastCreatedEntry;
-
- TestableHeadsUpManager(Context context,
- HeadsUpManagerLogger logger,
- DelayableExecutor executor,
- GlobalSettings globalSettings,
- SystemClock systemClock,
- AccessibilityManagerWrapper accessibilityManagerWrapper,
- UiEventLogger uiEventLogger) {
- super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
- executor, accessibilityManagerWrapper, uiEventLogger);
-
- mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
- mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
- mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
- mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
-
- }
-
- @Override
- protected HeadsUpEntry createHeadsUpEntry() {
- mLastCreatedEntry = spy(super.createHeadsUpEntry());
- return mLastCreatedEntry;
- }
-
- @Override
- public int getContentFlag() {
- return FLAG_CONTENT_VIEW_CONTRACTED;
- }
-
- // The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
- @Override
- public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void addSwipedOutNotification(@NonNull String key) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void extendHeadsUp() {
- throw new UnsupportedOperationException();
- }
-
- @Nullable
- @Override
- public Region getTouchableRegion() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isHeadsUpGoingAway() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void onExpandingFinished() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
- boolean animate) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setAnimationStateHandler(@NonNull AnimationStateHandler handler) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setRemoteInputActive(@NonNull NotificationEntry entry,
- boolean remoteInputActive) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setTrackingHeadsUp(boolean tracking) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean shouldSwallowClick(@NonNull String key) {
- throw new UnsupportedOperationException();
- }
- }
-
- protected StatusBarNotification createSbn(int id, Notification n) {
- return new StatusBarNotification(
- TEST_PACKAGE_NAME /* pkg */,
- TEST_PACKAGE_NAME,
- id,
- null /* tag */,
- TEST_UID,
- 0 /* initialPid */,
- n,
- new UserHandle(ActivityManager.getCurrentUser()),
- null /* overrideGroupKey */,
- 0 /* postTime */);
- }
-
- protected StatusBarNotification createSbn(int id, Notification.Builder n) {
- return createSbn(id, n.build());
- }
-
- protected StatusBarNotification createSbn(int id) {
- final Notification.Builder b = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
- return createSbn(id, b);
- }
-
- protected NotificationEntry createEntry(int id, Notification n) {
- return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
- }
-
- protected NotificationEntry createEntry(int id) {
- return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
- }
-
-
private BaseHeadsUpManager createHeadsUpManager() {
return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
@@ -257,7 +107,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
.setSmallIcon(R.drawable.ic_person)
.setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
.build();
- return createEntry(id, notif);
+ return HeadsUpManagerTestUtil.createEntry(id, notif);
}
private NotificationEntry createStickyForSomeTimeEntry(int id) {
@@ -265,7 +115,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
.setSmallIcon(R.drawable.ic_person)
.setFlag(FLAG_FSI_REQUESTED_BUT_DENIED, true)
.build();
- return createEntry(id, notif);
+ return HeadsUpManagerTestUtil.createEntry(id, notif);
}
private PendingIntent createFullScreenIntent() {
@@ -279,7 +129,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
.setSmallIcon(R.drawable.ic_person)
.setFullScreenIntent(createFullScreenIntent(), /* highPriority */ true)
.build();
- return createEntry(id, notif);
+ return HeadsUpManagerTestUtil.createEntry(id, notif);
}
@@ -293,10 +143,16 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
}
}
+ @Override
+ public void SysuiSetup() throws Exception {
+ super.SysuiSetup();
+ mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
+ }
+
@Test
public void testShowNotification_addsEntry() {
final BaseHeadsUpManager alm = createHeadsUpManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
alm.showNotification(entry);
@@ -308,7 +164,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShowNotification_autoDismisses() {
final BaseHeadsUpManager alm = createHeadsUpManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
alm.showNotification(entry);
mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
@@ -319,7 +175,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testRemoveNotification_removeDeferred() {
final BaseHeadsUpManager alm = createHeadsUpManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
alm.showNotification(entry);
@@ -332,7 +188,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testRemoveNotification_forceRemove() {
final BaseHeadsUpManager alm = createHeadsUpManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
alm.showNotification(entry);
@@ -346,7 +202,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
public void testReleaseAllImmediately() {
final BaseHeadsUpManager alm = createHeadsUpManager();
for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
- final NotificationEntry entry = createEntry(i);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(i, mContext);
entry.setRow(mRow);
alm.showNotification(entry);
}
@@ -359,7 +215,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testCanRemoveImmediately_notShownLongEnough() {
final BaseHeadsUpManager alm = createHeadsUpManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
alm.showNotification(entry);
@@ -370,7 +226,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testHunRemovedLogging() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+ final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
+ mContext);
final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = mock(
BaseHeadsUpManager.HeadsUpEntry.class);
headsUpEntry.mEntry = notifEntry;
@@ -413,7 +270,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShouldHeadsUpBecomePinned_noFSI_false() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
assertFalse(hum.shouldHeadsUpBecomePinned(entry));
}
@@ -422,7 +279,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
useAccessibilityTimeout(false);
hum.showNotification(entry);
@@ -435,7 +292,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShowNotification_autoDismissesWithDefaultTimeout() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
useAccessibilityTimeout(false);
hum.showNotification(entry);
@@ -476,7 +333,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
useAccessibilityTimeout(true);
hum.showNotification(entry);
@@ -504,7 +361,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testRemoveNotification_beforeMinimumDisplayTime() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
useAccessibilityTimeout(false);
hum.showNotification(entry);
@@ -523,7 +380,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testRemoveNotification_afterMinimumDisplayTime() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
useAccessibilityTimeout(false);
hum.showNotification(entry);
@@ -541,7 +398,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testRemoveNotification_releaseImmediately() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
hum.showNotification(entry);
@@ -555,7 +412,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testIsSticky_rowPinnedAndExpanded_true() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+ final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
+ mContext);
when(mRow.isPinned()).thenReturn(true);
notifEntry.setRow(mRow);
@@ -571,7 +429,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testIsSticky_remoteInputActive_true() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+ final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
+ mContext);
hum.showNotification(notifEntry);
@@ -607,7 +466,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testIsSticky_false() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+ final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
+ mContext);
hum.showNotification(notifEntry);
@@ -653,14 +513,14 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
final BaseHeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry();
ongoingCall.setEntry(new NotificationEntryBuilder()
- .setSbn(createSbn(/* id = */ 0,
+ .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
new Notification.Builder(mContext, "")
.setCategory(Notification.CATEGORY_CALL)
.setOngoing(true)))
.build());
final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
- activeRemoteInput.setEntry(createEntry(/* id = */ 1));
+ activeRemoteInput.setEntry(HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
activeRemoteInput.mRemoteInputActive = true;
assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -675,14 +535,14 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
final Person person = new Person.Builder().setName("person").build();
final PendingIntent intent = mock(PendingIntent.class);
incomingCall.setEntry(new NotificationEntryBuilder()
- .setSbn(createSbn(/* id = */ 0,
+ .setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
new Notification.Builder(mContext, "")
.setStyle(Notification.CallStyle
.forIncomingCall(person, intent, intent))))
.build());
final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
- activeRemoteInput.setEntry(createEntry(/* id = */ 1));
+ activeRemoteInput.setEntry(HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
activeRemoteInput.mRemoteInputActive = true;
assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -712,7 +572,8 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testSetUserActionMayIndirectlyRemove() {
final BaseHeadsUpManager hum = createHeadsUpManager();
- final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+ final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
+ mContext);
hum.showNotification(notifEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index c350de21dda5..c032d7cb06b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.statusbar.policy;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
@@ -26,10 +26,10 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -39,11 +39,10 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
-import com.android.systemui.statusbar.policy.BaseHeadsUpManagerTest;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
+import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
@@ -61,7 +60,7 @@ import org.mockito.junit.MockitoRule;
import kotlinx.coroutines.flow.StateFlowKt;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
@Rule public MockitoRule rule = MockitoJUnit.rule();
@@ -138,6 +137,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
@Before
public void setUp() {
+ mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
+
when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
final AccessibilityManagerWrapper accessibilityMgr =
mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
@@ -152,7 +153,7 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
@Test
public void testSnooze() {
final HeadsUpManager hmp = createHeadsUpManagerPhone();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
hmp.showNotification(entry);
hmp.snooze();
@@ -163,7 +164,7 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
@Test
public void testSwipedOutNotification() {
final HeadsUpManager hmp = createHeadsUpManagerPhone();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
hmp.showNotification(entry);
hmp.addSwipedOutNotification(entry.getKey());
@@ -179,7 +180,7 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
@Test
public void testCanRemoveImmediately_swipedOut() {
final HeadsUpManager hmp = createHeadsUpManagerPhone();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
hmp.showNotification(entry);
hmp.addSwipedOutNotification(entry.getKey());
@@ -192,8 +193,10 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
@Test
public void testCanRemoveImmediately_notTopEntry() {
final HeadsUpManager hmp = createHeadsUpManagerPhone();
- final NotificationEntry earlierEntry = createEntry(/* id = */ 0);
- final NotificationEntry laterEntry = createEntry(/* id = */ 1);
+ final NotificationEntry earlierEntry =
+ HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
+ final NotificationEntry laterEntry =
+ HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext);
laterEntry.setRow(mRow);
hmp.showNotification(earlierEntry);
@@ -206,7 +209,7 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
@Test
public void testExtendHeadsUp() {
final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
- final NotificationEntry entry = createEntry(/* id = */ 0);
+ final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
hmp.showNotification(entry);
hmp.extendHeadsUp();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
new file mode 100644
index 000000000000..c70b03b08789
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+import android.app.ActivityManager;
+import android.os.UserHandle;
+
+import android.content.Context;
+import android.service.notification.StatusBarNotification;
+import android.app.Notification;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+/**
+ * Test helper class for HeadsUpEntry creation.
+ */
+public class HeadsUpManagerTestUtil {
+
+ private static final String TEST_PACKAGE_NAME = "HeadsUpManagerTestUtil";
+ private static final int TEST_UID = 0;
+
+ protected static StatusBarNotification createSbn(int id, Notification.Builder n) {
+ return createSbn(id, n.build());
+ }
+
+ protected static StatusBarNotification createSbn(int id, Context context) {
+ final Notification.Builder b = new Notification.Builder(context, "")
+ .setSmallIcon(com.android.systemui.res.R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text");
+ return createSbn(id, b);
+ }
+
+ protected static StatusBarNotification createSbn(int id, Notification n) {
+ return new StatusBarNotification(
+ TEST_PACKAGE_NAME /* pkg */,
+ TEST_PACKAGE_NAME,
+ id,
+ null /* tag */,
+ TEST_UID,
+ 0 /* initialPid */,
+ n,
+ new UserHandle(ActivityManager.getCurrentUser()),
+ null /* overrideGroupKey */,
+ 0 /* postTime */);
+ }
+
+ protected static NotificationEntry createEntry(int id, Notification n) {
+ return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+ }
+
+ protected static NotificationEntry createEntry(int id, Context context) {
+ return new NotificationEntryBuilder().setSbn(
+ HeadsUpManagerTestUtil.createSbn(id, context)).build();
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
new file mode 100644
index 000000000000..27476299cf18
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.policy;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
+
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.graphics.Region;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.SystemClock;
+
+class TestableHeadsUpManager extends BaseHeadsUpManager {
+
+ private HeadsUpEntry mLastCreatedEntry;
+
+ TestableHeadsUpManager(Context context,
+ HeadsUpManagerLogger logger,
+ DelayableExecutor executor,
+ GlobalSettings globalSettings,
+ SystemClock systemClock,
+ AccessibilityManagerWrapper accessibilityManagerWrapper,
+ UiEventLogger uiEventLogger) {
+ super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
+ executor, accessibilityManagerWrapper, uiEventLogger);
+
+ mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
+ mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
+ mAutoDismissTime = BaseHeadsUpManagerTest.TEST_AUTO_DISMISS_TIME;
+ mStickyForSomeTimeAutoDismissTime = BaseHeadsUpManagerTest.TEST_STICKY_AUTO_DISMISS_TIME;
+ }
+
+ @Override
+ protected HeadsUpEntry createHeadsUpEntry() {
+ mLastCreatedEntry = spy(super.createHeadsUpEntry());
+ return mLastCreatedEntry;
+ }
+
+ @Override
+ public int getContentFlag() {
+ return FLAG_CONTENT_VIEW_CONTRACTED;
+ }
+
+ // The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
+ @Override
+ public void addHeadsUpPhoneListener(@NonNull OnHeadsUpPhoneListenerChange listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addSwipedOutNotification(@NonNull String key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void extendHeadsUp() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Nullable
+ @Override
+ public Region getTouchableRegion() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isHeadsUpGoingAway() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void onExpandingFinished() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
+ boolean animate) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setAnimationStateHandler(@NonNull AnimationStateHandler handler) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setGutsShown(@NonNull NotificationEntry entry, boolean gutsShown) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setRemoteInputActive(@NonNull NotificationEntry entry,
+ boolean remoteInputActive) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setTrackingHeadsUp(boolean tracking) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean shouldSwallowClick(@NonNull String key) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt
new file mode 100644
index 000000000000..fdfcdc486c02
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/wakelock/ClientTrackingWakeLockTest.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.util.wakelock
+
+import android.os.Build
+import android.os.PowerManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ClientTrackingWakeLockTest : SysuiTestCase() {
+
+ private val WHY = "test"
+ private val WHY_2 = "test2"
+
+ lateinit var mWakeLock: ClientTrackingWakeLock
+ lateinit var mInner: PowerManager.WakeLock
+
+ @Before
+ fun setUp() {
+ mInner =
+ WakeLock.createWakeLockInner(mContext, "WakeLockTest", PowerManager.PARTIAL_WAKE_LOCK)
+ mWakeLock = ClientTrackingWakeLock(mInner, null, 20000)
+ }
+
+ @After
+ fun tearDown() {
+ mInner.setReferenceCounted(false)
+ mInner.release()
+ }
+
+ @Test
+ fun createPartialInner_notHeldYet() {
+ Assert.assertFalse(mInner.isHeld)
+ }
+
+ @Test
+ fun wakeLock_acquire() {
+ mWakeLock.acquire(WHY)
+ Assert.assertTrue(mInner.isHeld)
+ }
+
+ @Test
+ fun wakeLock_release() {
+ mWakeLock.acquire(WHY)
+ mWakeLock.release(WHY)
+ Assert.assertFalse(mInner.isHeld)
+ }
+
+ @Test
+ fun wakeLock_acquiredReleasedMultipleSources_stillHeld() {
+ mWakeLock.acquire(WHY)
+ mWakeLock.acquire(WHY_2)
+ mWakeLock.release(WHY)
+
+ Assert.assertTrue(mInner.isHeld)
+ mWakeLock.release(WHY_2)
+ Assert.assertFalse(mInner.isHeld)
+ }
+
+ @Test
+ fun wakeLock_releasedTooManyTimes_stillReleased_noThrow() {
+ Assume.assumeFalse(Build.IS_ENG)
+ mWakeLock.acquire(WHY)
+ mWakeLock.acquire(WHY_2)
+ mWakeLock.release(WHY)
+ mWakeLock.release(WHY_2)
+ mWakeLock.release(WHY)
+ Assert.assertFalse(mInner.isHeld)
+ }
+
+ @Test
+ fun wakeLock_wrap() {
+ val ran = BooleanArray(1)
+ val wrapped = mWakeLock.wrap { ran[0] = true }
+ Assert.assertTrue(mInner.isHeld)
+ Assert.assertFalse(ran[0])
+ wrapped.run()
+ Assert.assertTrue(ran[0])
+ Assert.assertFalse(mInner.isHeld)
+ }
+
+ @Test
+ fun prodBuild_wakeLock_releaseWithoutAcquire_noThrow() {
+ Assume.assumeFalse(Build.IS_ENG)
+ // shouldn't throw an exception on production builds
+ mWakeLock.release(WHY)
+ }
+
+ @Test
+ fun acquireSeveralLocks_stringReportsCorrectCount() {
+ mWakeLock.acquire(WHY)
+ mWakeLock.acquire(WHY_2)
+ mWakeLock.acquire(WHY)
+ mWakeLock.acquire(WHY)
+ mWakeLock.acquire(WHY_2)
+ Assert.assertEquals(5, mWakeLock.activeClients())
+
+ mWakeLock.release(WHY_2)
+ mWakeLock.release(WHY_2)
+ Assert.assertEquals(3, mWakeLock.activeClients())
+
+ mWakeLock.release(WHY)
+ mWakeLock.release(WHY)
+ mWakeLock.release(WHY)
+ Assert.assertEquals(0, mWakeLock.activeClients())
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt
new file mode 100644
index 000000000000..737b7f3e0af0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.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.volume.panel.component.spatial
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.spatializerInteractor
+import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
+
+val Kosmos.spatialAudioComponentInteractor by
+ Kosmos.Fixture {
+ SpatialAudioComponentInteractor(
+ mediaOutputInteractor,
+ spatializerInteractor,
+ testScope.backgroundScope
+ )
+ }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
new file mode 100644
index 000000000000..36be90ecbf7e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.spatial.domain
+
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.spatializerRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.localMediaRepository
+import com.android.systemui.volume.mediaController
+import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.panel.component.spatial.spatialAudioComponentInteractor
+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)
+@RunWithLooper(setAsMainLooper = true)
+class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val cachedBluetoothDevice: CachedBluetoothDevice = mock {
+ whenever(address).thenReturn("test_address")
+ }
+ private val bluetoothMediaDevice: BluetoothMediaDevice = mock {
+ whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
+ }
+
+ private lateinit var underTest: SpatialAudioAvailabilityCriteria
+
+ @Before
+ fun setup() {
+ with(kosmos) {
+ mediaControllerRepository.setActiveLocalMediaController(
+ mediaController.apply {
+ whenever(packageName).thenReturn("test.pkg")
+ whenever(sessionToken).thenReturn(MediaSession.Token(0, mock {}))
+ whenever(playbackState).thenReturn(PlaybackState.Builder().build())
+ }
+ )
+
+ underTest = SpatialAudioAvailabilityCriteria(spatialAudioComponentInteractor)
+ }
+ }
+
+ @Test
+ fun noSpatialAudio_noHeadTracking_unavailable() {
+ with(kosmos) {
+ testScope.runTest {
+ localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
+ spatializerRepository.setIsHeadTrackingAvailable(false)
+ spatializerRepository.defaultSpatialAudioAvailable = false
+
+ val isAvailable by collectLastValue(underTest.isAvailable())
+ runCurrent()
+
+ assertThat(isAvailable).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun spatialAudio_noHeadTracking_available() {
+ with(kosmos) {
+ testScope.runTest {
+ localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
+ spatializerRepository.setIsHeadTrackingAvailable(false)
+ spatializerRepository.defaultSpatialAudioAvailable = true
+
+ val isAvailable by collectLastValue(underTest.isAvailable())
+ runCurrent()
+
+ assertThat(isAvailable).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun spatialAudio_headTracking_available() {
+ with(kosmos) {
+ testScope.runTest {
+ localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
+ spatializerRepository.setIsHeadTrackingAvailable(true)
+ spatializerRepository.defaultSpatialAudioAvailable = true
+
+ val isAvailable by collectLastValue(underTest.isAvailable())
+ runCurrent()
+
+ assertThat(isAvailable).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun spatialAudio_headTracking_noDevice_unavailable() {
+ with(kosmos) {
+ testScope.runTest {
+ spatializerRepository.setIsHeadTrackingAvailable(true)
+ spatializerRepository.defaultSpatialAudioAvailable = true
+
+ val isAvailable by collectLastValue(underTest.isAvailable())
+ runCurrent()
+
+ assertThat(isAvailable).isFalse()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
new file mode 100644
index 000000000000..eb6f0b2e32b3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.domain.interactor
+
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.spatializerInteractor
+import com.android.systemui.media.spatializerRepository
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.localMediaRepository
+import com.android.systemui.volume.mediaController
+import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+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)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class SpatialAudioComponentInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private lateinit var underTest: SpatialAudioComponentInteractor
+
+ @Before
+ fun setup() {
+ with(kosmos) {
+ val cachedBluetoothDevice: CachedBluetoothDevice = mock {
+ whenever(address).thenReturn("test_address")
+ }
+ localMediaRepository.updateCurrentConnectedDevice(
+ mock<BluetoothMediaDevice> {
+ whenever(name).thenReturn("test_device")
+ whenever(cachedDevice).thenReturn(cachedBluetoothDevice)
+ }
+ )
+
+ whenever(mediaController.packageName).thenReturn("test.pkg")
+ whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
+ whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build())
+
+ mediaControllerRepository.setActiveLocalMediaController(mediaController)
+
+ spatializerRepository.setIsSpatialAudioAvailable(
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ "test_address"
+ ),
+ true
+ )
+ spatializerRepository.setIsHeadTrackingAvailable(true)
+
+ underTest =
+ SpatialAudioComponentInteractor(
+ mediaOutputInteractor,
+ spatializerInteractor,
+ testScope.backgroundScope,
+ )
+ }
+ }
+
+ @Test
+ fun setEnabled_changesIsEnabled() {
+ with(kosmos) {
+ testScope.runTest {
+ val values by collectValues(underTest.isEnabled)
+
+ underTest.setEnabled(SpatialAudioEnabledModel.Disabled)
+ runCurrent()
+ underTest.setEnabled(SpatialAudioEnabledModel.HeadTrackingEnabled)
+ runCurrent()
+ underTest.setEnabled(SpatialAudioEnabledModel.SpatialAudioEnabled)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ SpatialAudioEnabledModel.Disabled,
+ SpatialAudioEnabledModel.HeadTrackingEnabled,
+ SpatialAudioEnabledModel.SpatialAudioEnabled,
+ )
+ .inOrder()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/res/drawable/battery_unified_attr_charging.xml b/packages/SystemUI/res/drawable/battery_unified_attr_charging.xml
new file mode 100644
index 000000000000..8e3b89b9a7eb
--- /dev/null
+++ b/packages/SystemUI/res/drawable/battery_unified_attr_charging.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="8dp"
+ android:height="10dp"
+ android:viewportWidth="16.0"
+ android:viewportHeight="20.0">
+ <path
+ android:pathData="M4,20L5,13H0L9,0H11L10,8H16L6,20H4Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/battery_unified_attr_defend.xml b/packages/SystemUI/res/drawable/battery_unified_attr_defend.xml
new file mode 100644
index 000000000000..e7beee2bc7c0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/battery_unified_attr_defend.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="8dp"
+ android:height="9dp"
+ android:viewportWidth="8.0"
+ android:viewportHeight="9.0">
+ <path
+ android:pathData="M3.698,9C2.629,8.765 1.746,8.181 1.048,7.247C0.349,6.306 0,5.266 0,4.126V1.422L3.698,0L7.397,1.422V4.126C7.397,5.266 7.048,6.306 6.349,7.247C5.651,8.181 4.767,8.765 3.698,9ZM3.698,7.846C4.439,7.596 5.052,7.129 5.537,6.445C6.029,5.754 6.274,4.981 6.274,4.126V2.191L3.698,1.197L1.122,2.191V4.126C1.122,4.981 1.365,5.754 1.849,6.445C2.341,7.129 2.957,7.596 3.698,7.846ZM3.698,7.183C3.1,6.99 2.605,6.616 2.213,6.061C1.828,5.505 1.635,4.888 1.635,4.211V2.651L3.698,1.86L5.761,2.651V4.211C5.761,4.888 5.565,5.505 5.173,6.061C4.789,6.616 4.297,6.99 3.698,7.183Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/battery_unified_attr_powersave.xml b/packages/SystemUI/res/drawable/battery_unified_attr_powersave.xml
new file mode 100644
index 000000000000..a2c0cbae3e8a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/battery_unified_attr_powersave.xml
@@ -0,0 +1,32 @@
+<?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.
+ -->
+
+<!-- This drawable is inset for now until the unified battery attrs can get their own paddings -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetRight="0.5dp"
+ >
+ <vector
+ android:width="8dp"
+ android:height="8dp"
+ android:viewportWidth="8.0"
+ android:viewportHeight="8.0"
+ >
+ <path
+ android:pathData="M3.242,4.758H0V3.257H3.242V0H4.758V3.257H8V4.758H4.758V8.015H3.242V4.758Z"
+ android:fillColor="#000"/>
+ </vector>
+</inset>
diff --git a/packages/SystemUI/res/drawable/battery_unified_frame.xml b/packages/SystemUI/res/drawable/battery_unified_frame.xml
new file mode 100644
index 000000000000..016b88b5cca1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/battery_unified_frame.xml
@@ -0,0 +1,34 @@
+<?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.
+ -->
+
+<!-- Unified battery frame for BatteryLayersDrawable.kt -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="14dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="14.0">
+ <!-- body -->
+ <path
+ android:pathData="@string/battery_unified_frame_path_string"
+ android:strokeColor="#000"
+ android:strokeWidth="1.5"
+ />
+ <!-- cap -->
+ <path
+ android:pathData="M0,4C0,3.448 0.448,3 1,3H1.5V11H1C0.448,11 0,10.552 0,10V4Z"
+ android:fillColor="#000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/battery_unified_frame_bg.xml b/packages/SystemUI/res/drawable/battery_unified_frame_bg.xml
new file mode 100644
index 000000000000..8e04f109fade
--- /dev/null
+++ b/packages/SystemUI/res/drawable/battery_unified_frame_bg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- Vector description of the battery gutter: the background of the fill -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="14dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="14.0">
+ <path
+ android:pathData="@string/battery_unified_frame_path_string"
+ android:fillColor="#fff" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/bg_shutdown_finder_message.xml b/packages/SystemUI/res/drawable/bg_shutdown_finder_message.xml
new file mode 100644
index 000000000000..324ae0c5c1d4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bg_shutdown_finder_message.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <corners android:radius="28dp" />
+ <solid android:color="@color/global_actions_lite_button_background" />
+</shape> \ 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
new file mode 100644
index 000000000000..8ca221ab7392
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_finder_active.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,0L12,0A12,12 0,0 1,24 12L24,12A12,12 0,0 1,12 24L12,24A12,12 0,0 1,0 12L0,12A12,12 0,0 1,12 0z"
+ android:fillColor="#00677D"/>
+ <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"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 1365a11d7a56..22d156da7580 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -263,8 +263,9 @@
android:layout_gravity="center_vertical|start">
<ImageView
android:id="@+id/wifi_connected_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="fitCenter"
android:layout_gravity="center"/>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml
index f6a213662a18..0da0f2b1e9b1 100644
--- a/packages/SystemUI/res/layout/internet_list_item.xml
+++ b/packages/SystemUI/res/layout/internet_list_item.xml
@@ -35,8 +35,9 @@
android:layout_gravity="center_vertical|start">
<ImageView
android:id="@+id/wifi_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="fitCenter"
android:layout_gravity="center"/>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/shutdown_dialog_finder_active.xml b/packages/SystemUI/res/layout/shutdown_dialog_finder_active.xml
new file mode 100644
index 000000000000..b6db7fc8007f
--- /dev/null
+++ b/packages/SystemUI/res/layout/shutdown_dialog_finder_active.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="24dp"
+ android:fontFamily="google-sans"
+ android:gravity="center"
+ android:text="@string/shutdown_progress"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textDirection="locale"
+ android:textSize="18sp"
+ android:visibility="gone"
+ app:layout_constraintBottom_toTopOf="@android:id/text2"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.375"
+ app:layout_constraintVertical_chainStyle="packed" />
+
+ <TextView
+ android:id="@android:id/text2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="24dp"
+ android:fontFamily="google-sans"
+ android:gravity="center"
+ android:text="@string/shutdown_progress"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textDirection="locale"
+ android:textSize="24sp"
+ app:layout_constraintBottom_toTopOf="@android:id/progress"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@android:id/text1" />
+
+ <ProgressBar
+ android:id="@android:id/progress"
+ style="?android:attr/progressBarStyleLarge"
+ android:layout_width="30dp"
+ android:layout_height="30dp"
+ android:importantForAccessibility="no"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@android:id/text2" />
+
+ <TextView
+ android:id="@+id/finer_hint"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="32dp"
+ android:background="@drawable/bg_shutdown_finder_message"
+ android:drawablePadding="16dp"
+ android:drawableStart="@drawable/ic_finder_active"
+ android:fontFamily="google-sans"
+ android:gravity="start"
+ android:padding="20dp"
+ android:text="@string/finder_active"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@android:color/secondary_text_dark"
+ android:textDirection="locale"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@android:id/progress"
+ app:layout_constraintVertical_bias="1" />
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4be1deb3de1c..b7eff38aa015 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -164,6 +164,9 @@
so the width of the icon should be 13.0sp * (12.0 / 20.0) -->
<dimen name="status_bar_battery_icon_width">7.8sp</dimen>
+ <dimen name="status_bar_battery_unified_icon_width">24sp</dimen>
+ <dimen name="status_bar_battery_unified_icon_height">14sp</dimen>
+
<!-- The battery icon is 13sp tall, but the other system icons are 15sp tall (see
@*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
@@ -199,6 +202,7 @@
<!-- Size of the view displaying the mobile signal icon in the status bar. This value should
match the core/status_bar_system_icon_size and change to sp unit -->
<dimen name="status_bar_mobile_signal_size">15sp</dimen>
+ <dimen name="status_bar_mobile_signal_size_updated">14sp</dimen>
<!-- Size of the view displaying the mobile signal icon in the status bar. This value should
match the viewport height of mobile signal drawables such as ic_lte_mobiledata -->
<dimen name="status_bar_mobile_type_size">16sp</dimen>
@@ -898,8 +902,8 @@
<dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen>
<!-- Width and height used to filter widgets displayed in the communal widget picker -->
- <dimen name="communal_widget_picker_desired_width">464dp</dimen>
- <dimen name="communal_widget_picker_desired_height">307dp</dimen>
+ <dimen name="communal_widget_picker_desired_width">424dp</dimen>
+ <dimen name="communal_widget_picker_desired_height">282dp</dimen>
<!-- The width/height of the unlock icon view on keyguard. -->
<dimen name="keyguard_lock_height">42dp</dimen>
@@ -1742,12 +1746,18 @@
<dimen name="communal_grid_height">630dp</dimen>
<!-- Number of columns for each communal card -->
<integer name="communal_grid_columns_per_card">6</integer>
- <!-- Width of area on right edge of screen in which swipes will open the communal hub -->
- <dimen name="communal_right_edge_swipe_region_width">16dp</dimen>
+
+ <!-- The width of the swipe target to initiate opening or closing communal hub. -->
+ <dimen name="communal_gesture_initiation_width">68dp</dimen>
+
+ <!-- TODO(b/322549765): unify with communal_gesture_initiation_width -->
+ <!-- Width of area on right edge of screen in which swipes will open the communal hub when on
+ the lockscreen -->
+ <dimen name="communal_right_edge_swipe_region_width">40dp</dimen>
<!-- Height of area at top of communal hub where swipes should open the notification shade -->
- <dimen name="communal_top_edge_swipe_region_height">32dp</dimen>
+ <dimen name="communal_top_edge_swipe_region_height">68dp</dimen>
<!-- Height of area at bottom of communal hub where swipes should open the bouncer -->
- <dimen name="communal_bottom_edge_swipe_region_height">32dp</dimen>
+ <dimen name="communal_bottom_edge_swipe_region_height">68dp</dimen>
<dimen name="drag_and_drop_icon_size">70dp</dimen>
@@ -1819,9 +1829,6 @@
<dimen name="dream_overlay_complication_smartspace_padding">24dp</dimen>
<dimen name="dream_overlay_complication_smartspace_max_width">408dp</dimen>
- <!-- The width of the swipe target to initiate opening communal hub over dreams. -->
- <dimen name="communal_gesture_initiation_width">48dp</dimen>
-
<!-- The position of the end guide, which dream overlay complications can align their start with
if their end is aligned with the parent end. Represented as the percentage over from the
start of the parent container. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4263d9402d66..346bdfc8b2d8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -34,6 +34,10 @@
remaining [CHAR LIMIT=none]-->
<string name="battery_low_percent_format"><xliff:g id="percentage">%s</xliff:g> remaining</string>
+ <!-- SVG path description for the battery frame of the unified battery drawable
+ (BatteryLayersDrawable.kt). Drawn on a 24x14 canvas. Not suitable outside in any other context -->
+ <string name="battery_unified_frame_path_string" translatable="false">M2.75,3C2.75,1.757 3.757,0.75 5,0.75H20C21.795,0.75 23.25,2.205 23.25,4V10C23.25,11.795 21.795,13.25 20,13.25H5C3.757,13.25 2.75,12.243 2.75,11V3Z </string>
+
<!-- A message that appears when the battery remaining estimate is low in a dialog. This is
appended to the subtitle of the low battery alert. "percentage" is the percentage of battery
remaining. "time" is the amount of time remaining before the phone runs out of battery [CHAR LIMIT=none]-->
@@ -1114,6 +1118,8 @@
<string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string>
<!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] -->
<string name="button_to_configure_widgets_text">Customize widgets</string>
+ <!-- Description for the App icon of disabled widget. [CHAR LIMIT=NONE] -->
+ <string name="icon_description_for_disabled_widget">App icon for disabled widget</string>
<!-- Label for the button which configures widgets [CHAR LIMIT=NONE] -->
<string name="edit_widget">Edit widget</string>
<!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
@@ -2236,6 +2242,11 @@
<!-- Tuner string -->
<!-- Tuner string -->
+ <!-- Message shown during shutdown when Find My Device with Dead Battery Finder is active [CHAR LIMIT=300] -->
+ <string name="finder_active">You can locate this phone with Find My Device even when powered off</string>
+ <!-- Shutdown Progress Dialog. This is shown if the user chooses to power off the phone. [CHAR LIMIT=60] -->
+ <string name="shutdown_progress">Shutting down\u2026</string>
+
<!-- Text help link for care instructions for overheating devices [CHAR LIMIT=40] -->
<string name="thermal_shutdown_dialog_help_text">See care steps</string>
<!-- URL for care instructions for overheating devices -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 10393cf83247..ca63483f656a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -196,7 +196,7 @@ public class ActivityManagerWrapper {
final RecentsAnimationListener animationHandler, final Consumer<Boolean> resultCallback,
Handler resultCallbackHandler) {
boolean result = startRecentsActivity(intent, eventTime, animationHandler);
- if (resultCallback != null) {
+ if (resultCallback != null && resultCallbackHandler != null) {
resultCallbackHandler.post(new Runnable() {
@Override
public void run() {
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 1cfa816f4612..75cace424e8b 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -17,9 +17,9 @@
package com.android.keyguard;
import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ACTIVE_DATA_SUB_CHANGED;
-import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_SIM_STATE_CHANGED;
import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_TELEPHONY_CAPABLE;
import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_REFRESH_CARRIER_INFO;
+import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_SIM_ERROR_STATE_CHANGED;
import android.content.Context;
import android.content.Intent;
@@ -123,12 +123,15 @@ public class CarrierTextManager {
return;
}
- mLogger.logUpdateCarrierTextForReason(REASON_ON_SIM_STATE_CHANGED);
+
+ mLogger.logSimStateChangedCallback(subId, slotId, simState);
if (getStatusForIccState(simState) == CarrierTextManager.StatusMode.SimIoError) {
mSimErrorState[slotId] = true;
+ mLogger.logUpdateCarrierTextForReason(REASON_SIM_ERROR_STATE_CHANGED);
updateCarrierText();
} else if (mSimErrorState[slotId]) {
mSimErrorState[slotId] = false;
+ mLogger.logUpdateCarrierTextForReason(REASON_SIM_ERROR_STATE_CHANGED);
updateCarrierText();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 05e07a788892..169a4e0f3501 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -413,7 +413,6 @@ constructor(
listenForDozing(this)
if (migrateClocksToBlueprint()) {
listenForDozeAmountTransition(this)
- listenForAnyStateToAodTransition(this)
} else {
listenForDozeAmount(this)
}
@@ -522,19 +521,6 @@ constructor(
}
}
- /**
- * When keyguard is displayed again after being gone, the clock must be reset to full dozing.
- */
- @VisibleForTesting
- internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
- return scope.launch {
- keyguardTransitionInteractor.transitionStepsToState(AOD)
- .filter { it.transitionState == TransitionState.STARTED }
- .filter { it.from != LOCKSCREEN }
- .collect { handleDoze(1f) }
- }
- }
-
@VisibleForTesting
internal fun listenForDozing(scope: CoroutineScope): Job {
return scope.launch {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 38c2829e27f6..b7667a8669f4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -34,6 +34,7 @@ import static android.hardware.biometrics.BiometricSourceType.FACE;
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
@@ -437,7 +438,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
};
- private final OnSubscriptionsChangedListener mSubscriptionListener =
+ @VisibleForTesting
+ final OnSubscriptionsChangedListener mSubscriptionListener =
new OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
@@ -586,16 +588,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void handleSimSubscriptionInfoChanged() {
Assert.isMainThread();
mLogger.v("onSubscriptionInfoChanged()");
- List<SubscriptionInfo> sil = mSubscriptionManager
- .getCompleteActiveSubscriptionInfoList();
- if (sil != null) {
- for (SubscriptionInfo subInfo : sil) {
+ List<SubscriptionInfo> subscriptionInfos = getSubscriptionInfo(true /* forceReload */);
+ if (!subscriptionInfos.isEmpty()) {
+ for (SubscriptionInfo subInfo : subscriptionInfos) {
mLogger.logSubInfo(subInfo);
}
} else {
mLogger.v("onSubscriptionInfoChanged: list is null");
}
- List<SubscriptionInfo> subscriptionInfos = getSubscriptionInfo(true /* forceReload */);
// Hack level over 9000: Because the subscription id is not yet valid when we see the
// first update in handleSimStateChange, we need to force refresh all SIM states
@@ -658,18 +658,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
/**
* @return List of SubscriptionInfo records, maybe empty but never null.
+ *
+ * Note that this method will filter out any subscription which is PROFILE_CLASS_PROVISIONING
*/
public List<SubscriptionInfo> getSubscriptionInfo(boolean forceReload) {
List<SubscriptionInfo> sil = mSubscriptionInfo;
if (sil == null || forceReload) {
- sil = mSubscriptionManager.getCompleteActiveSubscriptionInfoList();
- }
- if (sil == null) {
- // getCompleteActiveSubscriptionInfoList was null callers expect an empty list.
- mSubscriptionInfo = new ArrayList<>();
- } else {
- mSubscriptionInfo = sil;
+ mSubscriptionInfo = mSubscriptionManager.getCompleteActiveSubscriptionInfoList()
+ .stream()
+ .filter(subInfo -> subInfo.getProfileClass() != PROFILE_CLASS_PROVISIONING)
+ .toList();
}
+
return new ArrayList<>(mSubscriptionInfo);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
index d02b72f37795..cb474d3d7a92 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt
@@ -94,6 +94,20 @@ class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val bu
)
}
+ fun logSimStateChangedCallback(subId: Int, slotId: Int, simState: Int) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ // subId is always a very small int, and we've run out of integers for log buffer
+ long1 = subId.toLong()
+ int1 = slotId
+ int2 = simState
+ },
+ { "onSimStateChangedCallback: subId=$long1 slotId=$int1 simState=$int2" }
+ )
+ }
+
/**
* Used to log the starting point for _why_ the carrier text is updating. In order to keep us
* from holding on to too many objects, we'll just use simple ints for reasons here
@@ -113,7 +127,7 @@ class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val bu
companion object {
const val REASON_REFRESH_CARRIER_INFO = 1
const val REASON_ON_TELEPHONY_CAPABLE = 2
- const val REASON_ON_SIM_STATE_CHANGED = 3
+ const val REASON_SIM_ERROR_STATE_CHANGED = 3
const val REASON_ACTIVE_DATA_SUB_CHANGED = 4
@Retention(AnnotationRetention.SOURCE)
@@ -122,7 +136,7 @@ class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val bu
[
REASON_REFRESH_CARRIER_INFO,
REASON_ON_TELEPHONY_CAPABLE,
- REASON_ON_SIM_STATE_CHANGED,
+ REASON_SIM_ERROR_STATE_CHANGED,
REASON_ACTIVE_DATA_SUB_CHANGED,
]
)
@@ -132,7 +146,7 @@ class CarrierTextManagerLogger @Inject constructor(@CarrierTextManagerLog val bu
when (this) {
REASON_REFRESH_CARRIER_INFO -> "REFRESH_CARRIER_INFO"
REASON_ON_TELEPHONY_CAPABLE -> "ON_TELEPHONY_CAPABLE"
- REASON_ON_SIM_STATE_CHANGED -> "SIM_STATE_CHANGED"
+ REASON_SIM_ERROR_STATE_CHANGED -> "SIM_ERROR_STATE_CHANGED"
REASON_ACTIVE_DATA_SUB_CHANGED -> "ACTIVE_DATA_SUB_CHANGED"
else -> "unknown"
}
diff --git a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
index 22d0bc9e4b5c..af810ea1bc2e 100644
--- a/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/DualToneHandler.kt
@@ -20,6 +20,7 @@ import android.animation.ArgbEvaluator
import android.content.Context
import android.view.ContextThemeWrapper
import com.android.settingslib.Utils
+import com.android.settingslib.flags.Flags.newStatusBarIcons
import com.android.systemui.res.R
/**
@@ -53,14 +54,26 @@ class DualToneHandler(context: Context) {
Utils.getThemeAttr(context, R.attr.darkIconTheme))
val dualToneLightTheme = ContextThemeWrapper(context,
Utils.getThemeAttr(context, R.attr.lightIconTheme))
- darkColor = Color(
- Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.singleToneColor),
+ if (newStatusBarIcons()) {
+ darkColor = Color(
+ android.graphics.Color.BLACK,
Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.iconBackgroundColor),
Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.fillColor))
- lightColor = Color(
- Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.singleToneColor),
+
+ lightColor = Color(
+ android.graphics.Color.WHITE,
Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.iconBackgroundColor),
Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor))
+ } else {
+ darkColor = Color(
+ Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.singleToneColor),
+ Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.iconBackgroundColor),
+ Utils.getColorAttrDefaultColor(dualToneDarkTheme, R.attr.fillColor))
+ lightColor = Color(
+ Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.singleToneColor),
+ Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.iconBackgroundColor),
+ Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor))
+ }
}
private fun getColorForDarkIntensity(darkIntensity: Float, lightColor: Int, darkColor: Int) =
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index b1a153aa86aa..31698a35c811 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -17,6 +17,7 @@ package com.android.systemui.battery;
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
+import static com.android.settingslib.flags.Flags.newStatusBarIcons;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -25,6 +26,7 @@ import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.annotation.IntDef;
import android.annotation.IntRange;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -47,6 +49,9 @@ import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
import com.android.systemui.DualToneHandler;
+import com.android.systemui.battery.unified.BatteryColors;
+import com.android.systemui.battery.unified.BatteryDrawableState;
+import com.android.systemui.battery.unified.BatteryLayersDrawable;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
import com.android.systemui.res.R;
@@ -78,6 +83,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
private boolean mShowPercentAvailable;
private String mEstimateText = null;
private boolean mPluggedIn;
+ private boolean mPowerSaveEnabled;
private boolean mIsBatteryDefender;
private boolean mIsIncompatibleCharging;
private boolean mDisplayShieldEnabled;
@@ -91,6 +97,12 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
private BatteryEstimateFetcher mBatteryEstimateFetcher;
+ // for Flags.newStatusBarIcons. The unified battery icon can show percent inside
+ @Nullable private BatteryLayersDrawable mUnifiedBattery;
+ private BatteryColors mUnifiedBatteryColors = BatteryColors.LIGHT_THEME_COLORS;
+ private BatteryDrawableState mUnifiedBatteryState =
+ BatteryDrawableState.Companion.getDefaultInitialState();
+
public BatteryMeterView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -106,6 +118,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
context.getColor(com.android.settingslib.R.color.meter_background_color));
mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0);
+
mDrawable = new AccessorizedBatteryDrawable(context, frameColor);
atts.recycle();
@@ -115,13 +128,26 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
setupLayoutTransition();
mBatteryIconView = new ImageView(context);
- mBatteryIconView.setImageDrawable(mDrawable);
- final MarginLayoutParams mlp = new MarginLayoutParams(
- getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width),
- getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height));
- mlp.setMargins(0, 0, 0,
- getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));
- addView(mBatteryIconView, mlp);
+ if (newStatusBarIcons()) {
+ mUnifiedBattery = BatteryLayersDrawable.Companion
+ .newBatteryDrawable(context, mUnifiedBatteryState);
+ mBatteryIconView.setImageDrawable(mUnifiedBattery);
+
+ final MarginLayoutParams mlp = new MarginLayoutParams(
+ getResources().getDimensionPixelSize(
+ R.dimen.status_bar_battery_unified_icon_width),
+ getResources().getDimensionPixelSize(
+ R.dimen.status_bar_battery_unified_icon_height));
+ addView(mBatteryIconView, mlp);
+ } else {
+ mBatteryIconView.setImageDrawable(mDrawable);
+ final MarginLayoutParams mlp = new MarginLayoutParams(
+ getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width),
+ getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height));
+ mlp.setMargins(0, 0, 0,
+ getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));
+ addView(mBatteryIconView, mlp);
+ }
updateShowPercent();
mDualToneHandler = new DualToneHandler(context);
@@ -132,6 +158,14 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
setClipToPadding(false);
}
+
+ private void setBatteryDrawableState(BatteryDrawableState newState) {
+ if (!newStatusBarIcons()) return;
+
+ mUnifiedBatteryState = newState;
+ mUnifiedBattery.setBatteryState(mUnifiedBatteryState);
+ }
+
private void setupLayoutTransition() {
LayoutTransition transition = new LayoutTransition();
transition.setDuration(200);
@@ -200,25 +234,94 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
* @param pluggedIn whether the device is plugged in or not
*/
public void onBatteryLevelChanged(@IntRange(from = 0, to = 100) int level, boolean pluggedIn) {
+ boolean wasCharging = isCharging();
mPluggedIn = pluggedIn;
mLevel = level;
- mDrawable.setCharging(isCharging());
+ boolean isCharging = isCharging();
+ mDrawable.setCharging(isCharging);
mDrawable.setBatteryLevel(level);
updatePercentText();
+
+ if (newStatusBarIcons()) {
+ Drawable attr = mUnifiedBatteryState.getAttribution();
+ if (isCharging != wasCharging) {
+ attr = getBatteryAttribution(isCharging);
+ }
+
+ BatteryDrawableState newState =
+ new BatteryDrawableState(
+ level,
+ mUnifiedBatteryState.getShowPercent(),
+ level <= 20,
+ attr
+ );
+
+ setBatteryDrawableState(newState);
+ }
+ }
+
+ // Potentially reloads any attribution. Should not be called if the state hasn't changed
+ private Drawable getBatteryAttribution(boolean isCharging) {
+ if (!newStatusBarIcons()) return null;
+
+ int resId = 0;
+ if (mPowerSaveEnabled) {
+ resId = R.drawable.battery_unified_attr_powersave;
+ } else if (mIsBatteryDefender && mDisplayShieldEnabled) {
+ resId = R.drawable.battery_unified_attr_defend;
+ } else if (isCharging) {
+ resId = R.drawable.battery_unified_attr_charging;
+ }
+
+ Drawable attr = null;
+ if (resId > 0) {
+ attr = mContext.getDrawable(resId);
+ }
+
+ return attr;
}
void onPowerSaveChanged(boolean isPowerSave) {
- mDrawable.setPowerSaveEnabled(isPowerSave);
+ if (isPowerSave == mPowerSaveEnabled) {
+ return;
+ }
+ mPowerSaveEnabled = isPowerSave;
+ if (!newStatusBarIcons()) {
+ mDrawable.setPowerSaveEnabled(isPowerSave);
+ } else {
+ setBatteryDrawableState(
+ new BatteryDrawableState(
+ mUnifiedBatteryState.getLevel(),
+ mUnifiedBatteryState.getShowPercent(),
+ mUnifiedBatteryState.getShowErrorState(),
+ getBatteryAttribution(isCharging())
+ )
+ );
+ }
}
void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
boolean valueChanged = mIsBatteryDefender != isBatteryDefender;
mIsBatteryDefender = isBatteryDefender;
- if (valueChanged) {
- updateContentDescription();
+
+ if (!valueChanged) {
+ return;
+ }
+
+ updateContentDescription();
+ if (!newStatusBarIcons()) {
// The battery drawable is a different size depending on whether it's currently
// overheated or not, so we need to re-scale the view when overheated changes.
scaleBatteryMeterViews();
+ } else {
+ setBatteryDrawableState(
+ new BatteryDrawableState(
+ mUnifiedBatteryState.getLevel(),
+ mUnifiedBatteryState.getShowPercent(),
+ mUnifiedBatteryState.getShowErrorState(),
+ getBatteryAttribution(isCharging())
+ )
+ );
}
}
@@ -226,7 +329,18 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
boolean valueChanged = mIsIncompatibleCharging != isIncompatibleCharging;
mIsIncompatibleCharging = isIncompatibleCharging;
if (valueChanged) {
- mDrawable.setCharging(isCharging());
+ if (newStatusBarIcons()) {
+ setBatteryDrawableState(
+ new BatteryDrawableState(
+ mUnifiedBatteryState.getLevel(),
+ mUnifiedBatteryState.getShowPercent(),
+ mUnifiedBatteryState.getShowErrorState(),
+ getBatteryAttribution(isCharging())
+ )
+ );
+ } else {
+ mDrawable.setCharging(isCharging());
+ }
updateContentDescription();
}
}
@@ -260,6 +374,38 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
}
void updatePercentText() {
+ if (!newStatusBarIcons()) {
+ updatePercentTextLegacy();
+ return;
+ }
+
+ // The unified battery can show the percent inside, so we only need to handle
+ // the estimated time remaining case
+ if (mShowPercentMode == MODE_ESTIMATE
+ && mBatteryEstimateFetcher != null
+ && !isCharging()
+ ) {
+ mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
+ (String estimate) -> {
+ if (mBatteryPercentView == null) {
+ mBatteryPercentView = loadPercentView();
+ }
+ if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
+ mEstimateText = estimate;
+ mBatteryPercentView.setText(estimate);
+ updateContentDescription();
+ } else {
+ mEstimateText = null;
+ mBatteryPercentView.setText(null);
+ updateContentDescription();
+ }
+ });
+ } else {
+ updateContentDescription();
+ }
+ }
+
+ void updatePercentTextLegacy() {
if (mBatteryStateUnknown) {
return;
}
@@ -334,6 +480,45 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
}
void updateShowPercent() {
+ if (!newStatusBarIcons()) {
+ updateShowPercentLegacy();
+ return;
+ }
+
+ if (mUnifiedBattery == null) {
+ return;
+ }
+
+ // TODO(b/140051051)
+ final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
+ .getIntForUser(getContext().getContentResolver(),
+ SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
+ ? 1 : 0, UserHandle.USER_CURRENT));
+
+ boolean shouldShow =
+ (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
+ || mShowPercentMode == MODE_ON;
+ shouldShow = shouldShow && !mBatteryStateUnknown;
+
+ setBatteryDrawableState(
+ new BatteryDrawableState(
+ mUnifiedBatteryState.getLevel(),
+ shouldShow,
+ mUnifiedBatteryState.getShowErrorState(),
+ mUnifiedBatteryState.getAttribution()
+ )
+ );
+
+ // The legacy impl used the percent view for the estimate and the percent text. The modern
+ // version only uses it for estimate. It can be safely removed here
+ if (mShowPercentMode != MODE_ESTIMATE) {
+ removeView(mBatteryPercentView);
+ mBatteryPercentView = null;
+ }
+ }
+
+ private void updateShowPercentLegacy() {
final boolean showing = mBatteryPercentView != null;
// TODO(b/140051051)
final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
@@ -395,10 +580,39 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
updateShowPercent();
}
+ void scaleBatteryMeterViews() {
+ if (!newStatusBarIcons()) {
+ scaleBatteryMeterViewsLegacy();
+ return;
+ }
+
+ // For simplicity's sake, copy the general pattern in the legacy method and use the new
+ // resources, excluding what we don't need
+ Resources res = getContext().getResources();
+ TypedValue typedValue = new TypedValue();
+
+ res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
+ float iconScaleFactor = typedValue.getFloat();
+
+ float mainBatteryHeight =
+ res.getDimensionPixelSize(
+ R.dimen.status_bar_battery_unified_icon_height) * iconScaleFactor;
+ float mainBatteryWidth =
+ res.getDimensionPixelSize(
+ R.dimen.status_bar_battery_unified_icon_width) * iconScaleFactor;
+
+ LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
+ Math.round(mainBatteryWidth),
+ Math.round(mainBatteryHeight));
+
+ mBatteryIconView.setLayoutParams(scaledLayoutParams);
+ mBatteryIconView.invalidateDrawable(mUnifiedBattery);
+ }
+
/**
* Looks up the scale factor for status bar icons and scales the battery view by that amount.
*/
- void scaleBatteryMeterViews() {
+ void scaleBatteryMeterViewsLegacy() {
Resources res = getContext().getResources();
TypedValue typedValue = new TypedValue();
@@ -445,6 +659,32 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
@Override
public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
if (mIsStaticColor) return;
+
+ if (!newStatusBarIcons()) {
+ onDarkChangedLegacy(areas, darkIntensity, tint);
+ return;
+ }
+
+ if (mUnifiedBattery == null) {
+ return;
+ }
+
+ if (DarkIconDispatcher.isInAreas(areas, this)) {
+ if (darkIntensity < 0.5) {
+ mUnifiedBatteryColors = BatteryColors.DARK_THEME_COLORS;
+ } else {
+ mUnifiedBatteryColors = BatteryColors.LIGHT_THEME_COLORS;
+ }
+
+ mUnifiedBattery.setColors(mUnifiedBatteryColors);
+ } else {
+ // Same behavior as the legacy code when not isInArea
+ mUnifiedBatteryColors = BatteryColors.DARK_THEME_COLORS;
+ mUnifiedBattery.setColors(mUnifiedBatteryColors);
+ }
+ }
+
+ private void onDarkChangedLegacy(ArrayList<Rect> areas, float darkIntensity, int tint) {
float intensity = DarkIconDispatcher.isInAreas(areas, this) ? darkIntensity : 0;
int nonAdaptedSingleToneColor = mDualToneHandler.getSingleColor(intensity);
int nonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity);
@@ -478,7 +718,16 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
}
}
- private boolean isCharging() {
+ /** For newStatusBarIcons(), we use a BatteryColors object to declare the theme */
+ public void setUnifiedBatteryColors(BatteryColors colors) {
+ if (!newStatusBarIcons()) return;
+
+ mUnifiedBatteryColors = colors;
+ mUnifiedBattery.setColors(mUnifiedBatteryColors);
+ }
+
+ @VisibleForTesting
+ boolean isCharging() {
return mPluggedIn && !mIsIncompatibleCharging;
}
@@ -505,6 +754,16 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver {
return mBatteryPercentView.getText();
}
+ @VisibleForTesting
+ TextView getBatteryPercentView() {
+ return mBatteryPercentView;
+ }
+
+ @VisibleForTesting
+ BatteryDrawableState getUnifiedBatteryState() {
+ return mUnifiedBatteryState;
+ }
+
/** An interface that will fetch the estimated time remaining for the user's battery. */
public interface BatteryEstimateFetcher {
void fetchBatteryTimeRemainingEstimate(
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
new file mode 100644
index 000000000000..1b8495ace243
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.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.battery.unified
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.DrawableWrapper
+import android.view.Gravity
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+/**
+ * A battery attribution is defined as a drawable that can display either alongside the percent text
+ * or solely in the center of the battery frame.
+ *
+ * Attributions are given an explicit canvas of 18x8, or 6x6 depending on the display mode (centered
+ * or right-aligned). The size is configured in [BatteryLayersDrawable] by changing this drawable
+ * wrapper's bounds, and optionally setting the [gravity]
+ */
+@Suppress("RtlHardcoded")
+class BatteryAttributionDrawable(dr: Drawable?) : DrawableWrapper(dr) {
+ /** One of [CENTER, LEFT]. Note that RTL is handled in the parent */
+ var gravity = Gravity.CENTER
+ set(value) {
+ field = value
+ updateBoundsInner()
+ }
+
+ // Must be called if bounds change, gravity changes, or the wrapped drawable changes
+ private fun updateBoundsInner() {
+ val dr = drawable ?: return
+
+ val hScale = bounds.width().toFloat() / dr.intrinsicWidth.toFloat()
+ val vScale = bounds.height().toFloat() / dr.intrinsicHeight.toFloat()
+ val scale = min(hScale, vScale)
+
+ val dw = scale * dr.intrinsicWidth
+ val dh = scale * dr.intrinsicHeight
+
+ if (gravity == Gravity.CENTER) {
+ val padLeft = (bounds.width() - dw) / 2
+ val padTop = (bounds.height() - dh) / 2
+ dr.setBounds(
+ (bounds.left + padLeft).roundToInt(),
+ (bounds.top + padTop).roundToInt(),
+ (bounds.left + padLeft + dw).roundToInt(),
+ (bounds.top + padTop + dh).roundToInt()
+ )
+ } else if (gravity == Gravity.LEFT) {
+ dr.setBounds(
+ bounds.left,
+ bounds.top,
+ (bounds.left + dw).roundToInt(),
+ (bounds.top + dh).roundToInt()
+ )
+ }
+ }
+
+ override fun setDrawable(dr: Drawable?) {
+ super.setDrawable(dr)
+ updateBoundsInner()
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ updateBoundsInner()
+ }
+
+ /**
+ * DrawableWrapper allows for a null constructor, but this method assumes that the drawable is
+ * non-null. It is called by LayerDrawable on init, so we have to handle null here specifically
+ */
+ override fun getChangingConfigurations(): Int = drawable?.changingConfigurations ?: 0
+
+ override fun draw(canvas: Canvas) {
+ drawable?.draw(canvas)
+ }
+
+ // Deprecated, but needed for Drawable implementation
+ override fun getOpacity() = PixelFormat.OPAQUE
+
+ // We don't use this
+ override fun setAlpha(alpha: Int) {}
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
new file mode 100644
index 000000000000..b5a93b6635c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.battery.unified
+
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+
+/**
+ * Encapsulates all drawing information needed by BatteryMeterDrawable to render properly. Rendered
+ * state will be equivalent to the most recent state passed in.
+ */
+data class BatteryDrawableState(
+ /** [0-100] description of the battery level */
+ val level: Int,
+ /** Whether or not to render the percent as a foreground text layer */
+ val showPercent: Boolean,
+ /**
+ * In an error state, the drawable will use the error colors and removes the third layer. If
+ * [showPercent] is false, then the fill will be rendered in the foreground error color. Else
+ * the fill is not rendered.
+ */
+ val showErrorState: Boolean,
+
+ /**
+ * An attribution is a drawable that shows either alongside the percent, or centered in the
+ * foreground of the overall drawable.
+ *
+ * When space sharing with the percent text, the default rect is 6x6, positioned directly next
+ * to the percent and left-aligned.
+ *
+ * When the attribution is the only foreground layer, then we use a 16x8 canvas and center this
+ * drawable.
+ *
+ * In both cases, we use a FIT_CENTER style scaling. Note that for now the attributions will
+ * have to configure their own padding inside of their vector definitions. Future versions
+ * should abstract the side- and center- canvases and allow attributions to be defined with
+ * separate designs for each case.
+ */
+ val attribution: Drawable?
+) {
+ fun hasForegroundContent() = showPercent || attribution != null
+
+ companion object {
+ val DefaultInitialState =
+ BatteryDrawableState(
+ level = 50,
+ showPercent = false,
+ showErrorState = false,
+ attribution = null,
+ )
+ }
+}
+
+sealed interface BatteryColors {
+ /** The color for the frame and any foreground attributions for the battery */
+ val fg: Int
+ /**
+ * Default color for the frame background. Configured to be a transparent white or black that
+ * matches the current mode (white for light theme, black for dark theme) and provides extra
+ * contrast for the drawable
+ */
+ val bg: Int
+
+ /** Color for the level fill when there is an attribution on top */
+ val fill: Int
+ /**
+ * When there is no attribution, [fillOnlyColor] describes an opaque color with more contrast
+ */
+ val fillOnly: Int
+
+ /** Error colors are used for low battery states typically */
+ val errorForeground: Int
+ val errorBackground: Int
+
+ /** Currently unused */
+ val warnBackground: Int
+
+ /** Color scheme appropriate for light mode (dark icons) */
+ data object LightThemeColors : BatteryColors {
+ override val fg = Color.BLACK
+ // 22% alpha white
+ override val bg: Int = Color.valueOf(1f, 1f, 1f, 0.22f).toArgb()
+
+ // 18% alpha black
+ override val fill = Color.valueOf(0f, 0f, 0f, 0.18f).toArgb()
+ // GM Gray 500
+ override val fillOnly = Color.parseColor("#9AA0A6")
+
+ // GM Red 600
+ override val errorForeground = Color.parseColor("#D93025")
+ // GM Red 100
+ override val errorBackground = Color.parseColor("#FAD2CF")
+
+ // GM Yellow 500
+ override val warnBackground = Color.parseColor("#FBBC04")
+ }
+
+ /** Color scheme appropriate for dark mode (light icons) */
+ data object DarkThemeColors : BatteryColors {
+ override val fg = Color.WHITE
+ // 18% alpha black
+ override val bg: Int = Color.valueOf(0f, 0f, 0f, 0.18f).toArgb()
+
+ // 22% alpha white
+ override val fill = Color.valueOf(1f, 1f, 1f, 0.22f).toArgb()
+ // GM Gray 600
+ override val fillOnly = Color.parseColor("#80868B")
+
+ // GM Red 600
+ override val errorForeground = Color.parseColor("#D93025")
+ // GM Red 200
+ override val errorBackground = Color.parseColor("#F6AEA9")
+ // GM Yellow
+ override val warnBackground = Color.parseColor("#FBBC04")
+ }
+
+ companion object {
+ /** For use from java */
+ @JvmField val LIGHT_THEME_COLORS = LightThemeColors
+
+ @JvmField val DARK_THEME_COLORS = DarkThemeColors
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
new file mode 100644
index 000000000000..6d3206767d2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.battery.unified
+
+import android.graphics.BlendMode
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.Drawable
+import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
+import kotlin.math.floor
+import kotlin.math.roundToInt
+
+/**
+ * Draws a right-to-left fill inside of the given [framePath]. This fill is designed to exactly fill
+ * the usable space inside of [framePath], given that the stroke width of the path is 1.5, and we
+ * want an extra 0.25 (canvas units) of a gap between the fill and the stroke
+ */
+class BatteryFillDrawable(private val framePath: Path) : Drawable() {
+ private var hScale = 1f
+ private val scaleMatrix = Matrix().also { it.setScale(1f, 1f) }
+ private val scaledPath = Path()
+ private val scaledFillRect = RectF()
+ private var scaledLeftOffset = 0f
+ private var scaledRightInset = 0f
+
+ // Drawable.level cannot be overloaded
+ var batteryLevel = 0
+ set(value) {
+ field = value
+ invalidateSelf()
+ }
+
+ var fillColor: Int = 0
+ set(value) {
+ field = value
+ fillPaint.color = value
+ invalidateSelf()
+ }
+
+ private val clearPaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+ p.style = Paint.Style.STROKE
+ p.strokeWidth = 5f
+ p.blendMode = BlendMode.CLEAR
+ }
+
+ private val fillPaint =
+ Paint(Paint.ANTI_ALIAS_FLAG).also { p ->
+ p.style = Paint.Style.FILL
+ p.color = fillColor
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+
+ hScale = bounds.right / Metrics.ViewportWidth
+
+ if (bounds.isEmpty) {
+ scaleMatrix.setScale(1f, 1f)
+ } else {
+ scaleMatrix.setScale(
+ (bounds.right / Metrics.ViewportWidth),
+ (bounds.bottom / Metrics.ViewportHeight)
+ )
+ }
+
+ updateScale()
+ }
+
+ private fun updateScale() {
+ framePath.transform(/* matrix = */ scaleMatrix, /* dst = */ scaledPath)
+ scaleMatrix.mapRect(/* dst = */ scaledFillRect, /* src = */ FillRect)
+
+ scaledLeftOffset = LeftFillOffset * hScale
+ scaledRightInset = RightFillInset * hScale
+ }
+
+ override fun draw(canvas: Canvas) {
+ if (batteryLevel == 0) {
+ return
+ }
+
+ // saveLayer is needed here so we don't clip the other layers of our drawable
+ canvas.saveLayer(null, null)
+
+ // We need to use 3 draw commands:
+ // 1. Clip to the current level
+ // 2. Clip anything outside of the path
+ // 3. render the fill as a rect the correct size to fit the inner space
+ // 4. Clip out the padding between the frame and the fill
+
+ val fillLeft: Int =
+ if (batteryLevel == 100) {
+ 0
+ } else {
+ val fillFraction = batteryLevel / 100f
+ floor(scaledFillRect.width() * (1 - fillFraction)).roundToInt()
+ }
+
+ // Clip to the fill level
+ canvas.clipOutRect(
+ scaledLeftOffset,
+ bounds.top.toFloat(),
+ scaledLeftOffset + fillLeft,
+ bounds.height().toFloat()
+ )
+ // Clip everything outside of the path
+ canvas.clipPath(scaledPath)
+
+ // Draw the fill
+ canvas.drawRect(scaledFillRect, fillPaint)
+
+ // Clear around the fill
+ canvas.drawPath(scaledPath, clearPaint)
+
+ // Finally, restore the layer
+ canvas.restore()
+ }
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ clearPaint.setColorFilter(colorFilter)
+ fillPaint.setColorFilter(colorFilter)
+ }
+
+ // unused
+ override fun getOpacity(): Int = PixelFormat.OPAQUE
+
+ // unused
+ override fun setAlpha(alpha: Int) {}
+
+ companion object {
+ // 3.75f =
+ // 2.75 (left-most edge of the frame path)
+ // + 0.75 (1/2 of the stroke width)
+ // + 0.25 (padding between stroke and fill edge)
+ private const val LeftFillOffset = 3.75f
+
+ // 1.75, calculated the same way, but from the right edge (without the battery cap), which
+ // consumes 2 units of width.
+ private const val RightFillInset = 1.75f
+
+ /** Scale this to the viewport so we fill correctly! */
+ private val FillRect =
+ RectF(
+ LeftFillOffset,
+ 0f,
+ Metrics.ViewportWidth - RightFillInset,
+ Metrics.ViewportHeight
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
new file mode 100644
index 000000000000..199dd1f18a42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.battery.unified
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.LayerDrawable
+import android.util.PathParser
+import android.view.Gravity
+import com.android.systemui.res.R
+import kotlin.math.roundToInt
+
+/**
+ * Custom [Drawable] that manages a list of other drawables which, together, achieve an appropriate
+ * view for [BatteryDrawableState].
+ *
+ * The main elements managed by this drawable are:
+ *
+ * 1. A battery frame background, which may show a solid fill color
+ * 2. The battery frame itself
+ * 3. A custom [BatteryFillDrawable], which renders a fill level, appropriately scale and
+ * clipped to the battery percent
+ * 4. Percent text
+ * 5. An attribution
+ *
+ * Layers (1) and (2) are loaded directly from xml, as they are static assets. Layer (3) contains a
+ * custom [Drawable.draw] implementation and uses the same path as the battery shape to achieve an
+ * appropriate fill shape.
+ *
+ * The text and attribution layers have the following behaviors:
+ *
+ * - When text-only or attribute-only, the foreground layer is centered and the maximum size
+ * - When sharing space between the attribute and the text:
+ * - The internal space is divided into 12x10 and 6x6 rectangles
+ * - The attribution is aligned left
+ * - The percent text is scaled based on the number of characters (1,2, or 3) in the string
+ *
+ * When [BatteryDrawableState.showErrorState] is true, we will only show either the percent text OR
+ * the battery fill, in order to maximize contrast when using the error colors.
+ */
+@Suppress("RtlHardcoded")
+class BatteryLayersDrawable(
+ private val frameBg: Drawable,
+ private val frame: Drawable,
+ private val fill: BatteryFillDrawable,
+ private val textOnly: BatteryPercentTextOnlyDrawable,
+ private val spaceSharingText: BatterySpaceSharingPercentTextDrawable,
+ private val attribution: BatteryAttributionDrawable,
+ batteryState: BatteryDrawableState,
+) : LayerDrawable(arrayOf(frameBg, frame, fill, textOnly, spaceSharingText, attribution)) {
+
+ private val scaleMatrix = Matrix().also { it.setScale(1f, 1f) }
+ private val scaledAttrFullCanvas = RectF(Metrics.AttrFullCanvas)
+ private val scaledAttrRightCanvas = RectF(Metrics.AttrRightCanvas)
+
+ var batteryState = batteryState
+ set(value) {
+ if (field != value) {
+ // Update before we set the backing field so we can diff
+ handleUpdateState(field, value)
+ field = value
+ invalidateSelf()
+ }
+ }
+
+ var colors: BatteryColors = BatteryColors.LightThemeColors
+ set(value) {
+ field = value
+ updateColors(batteryState.showErrorState, value)
+ }
+
+ private fun handleUpdateState(old: BatteryDrawableState, new: BatteryDrawableState) {
+ if (new.showErrorState != old.showErrorState) {
+ updateColors(new.showErrorState, colors)
+ }
+
+ if (new.level != old.level) {
+ fill.batteryLevel = new.level
+ textOnly.batteryLevel = new.level
+ spaceSharingText.batteryLevel = new.level
+ }
+
+ if (new.attribution != null && new.attribution != attribution.drawable) {
+ attribution.drawable = new.attribution
+ updateColors(new.showErrorState, colors)
+ }
+
+ if (new.hasForegroundContent() != old.hasForegroundContent()) {
+ setFillColor(new.hasForegroundContent(), new.showErrorState, colors)
+ }
+ }
+
+ /** In error states, we don't draw fill unless there is no foreground content (e.g., percent) */
+ private fun updateColors(showErrorState: Boolean, colorInfo: BatteryColors) {
+ frameBg.setTint(if (showErrorState) colorInfo.errorBackground else colorInfo.bg)
+ frame.setTint(colorInfo.fg)
+ attribution.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg)
+ textOnly.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg)
+ spaceSharingText.setTint(if (showErrorState) colorInfo.errorForeground else colorInfo.fg)
+ setFillColor(batteryState.hasForegroundContent(), showErrorState, colorInfo)
+ }
+
+ /**
+ * If there is a foreground layer, then we draw the fill with the low opacity
+ * [BatteryColors.fill] color. Otherwise, if there is no other foreground layer, we will use
+ * either the error or fillOnly colors for more contrast
+ */
+ private fun setFillColor(
+ hasFg: Boolean,
+ error: Boolean,
+ colorInfo: BatteryColors,
+ ) {
+ if (hasFg) {
+ fill.fillColor = colorInfo.fill
+ } else {
+ fill.fillColor = if (error) colorInfo.errorForeground else colorInfo.fillOnly
+ }
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+
+ scaleMatrix.setScale(
+ bounds.width() / Metrics.ViewportWidth,
+ bounds.height() / Metrics.ViewportHeight
+ )
+
+ // Scale the attribution bounds
+ scaleMatrix.mapRect(scaledAttrFullCanvas, Metrics.AttrFullCanvas)
+ scaleMatrix.mapRect(scaledAttrRightCanvas, Metrics.AttrRightCanvas)
+ }
+
+ override fun draw(canvas: Canvas) {
+ // 1. Draw the frame bg
+ frameBg.draw(canvas)
+ // 2. Then the frame itself
+ frame.draw(canvas)
+
+ // 3. Fill it the appropriate amount if non-error state or error + no attribute
+ if (!batteryState.showErrorState || !batteryState.hasForegroundContent()) {
+ fill.draw(canvas)
+ }
+ // 4. Decide what goes inside
+ if (batteryState.showPercent && batteryState.attribution != null) {
+ // 4a. percent & attribution. Implies space-sharing
+
+ // Configure the attribute to draw in a smaller bounding box and align left
+ attribution.gravity = Gravity.LEFT
+ attribution.setBounds(
+ scaledAttrRightCanvas.left.roundToInt(),
+ scaledAttrRightCanvas.top.roundToInt(),
+ scaledAttrRightCanvas.right.roundToInt(),
+ scaledAttrRightCanvas.bottom.roundToInt(),
+ )
+ attribution.draw(canvas)
+
+ spaceSharingText.draw(canvas)
+ } else if (batteryState.showPercent) {
+ // 4b. Percent only
+ textOnly.draw(canvas)
+ } else if (batteryState.attribution != null) {
+ // 4c. Attribution only
+ attribution.gravity = Gravity.CENTER
+ attribution.setBounds(
+ scaledAttrFullCanvas.left.roundToInt(),
+ scaledAttrFullCanvas.top.roundToInt(),
+ scaledAttrFullCanvas.right.roundToInt(),
+ scaledAttrFullCanvas.bottom.roundToInt(),
+ )
+ attribution.draw(canvas)
+ }
+ }
+
+ /**
+ * This drawable relies on [BatteryColors] to encode all alpha in their values, so we ignore
+ * externally-set alpha
+ */
+ override fun setAlpha(alpha: Int) {}
+
+ interface M {
+ val ViewportWidth: Float
+ val ViewportHeight: Float
+
+ // Bounds, oriented in the above viewport, where we will fit-center and center-align
+ // an attribution that is the sole foreground element
+ val AttrFullCanvas: RectF
+ // Bounds, oriented in the above viewport, where we will fit-center and left-align
+ // an attribution that is sharing space with the percent text of the drawable
+ val AttrRightCanvas: RectF
+ }
+
+ companion object {
+ private val PercentFont = Typeface.create("google-sans", Typeface.BOLD)
+
+ /**
+ * Think of this like the `android:<attr>` values in a drawable.xml file. [Metrics] defines
+ * relevant canvas and size information for us to layout this cluster of drawables
+ */
+ val Metrics =
+ object : M {
+ override val ViewportWidth: Float = 24f
+ override val ViewportHeight: Float = 14f
+
+ /**
+ * Bounds, oriented in the above viewport, where we will fit-center and center-align
+ * an attribution that is the sole foreground element
+ *
+ * 18x8 point size
+ */
+ override val AttrFullCanvas: RectF = RectF(4f, 3f, 22f, 11f)
+ /**
+ * Bounds, oriented in the above viewport, where we will fit-center and left-align
+ * an attribution that is sharing space with the percent text of the drawable
+ *
+ * 6x6 point size
+ */
+ override val AttrRightCanvas: RectF = RectF(16f, 4f, 22f, 10f)
+ }
+
+ /**
+ * Create all of the layers needed by [BatteryLayersDrawable]. This class relies on the
+ * following resources to exist in order to properly render:
+ * - R.drawable.battery_unified_frame_bg
+ * - R.drawable.battery_unified_frame
+ * - R.string.battery_unified_frame_path_string
+ * - GoogleSans bold font
+ *
+ * See [BatteryDrawableState] for how to set the properties of the resulting class
+ */
+ fun newBatteryDrawable(
+ context: Context,
+ initialState: BatteryDrawableState = BatteryDrawableState.DefaultInitialState,
+ ): BatteryLayersDrawable {
+ val framePath =
+ PathParser.createPathFromPathData(
+ context.getString(R.string.battery_unified_frame_path_string)
+ )
+
+ val frameBg =
+ context.getDrawable(R.drawable.battery_unified_frame_bg)
+ ?: throw IllegalStateException("Missing battery_unified_frame_bg.xml")
+ val frame =
+ context.getDrawable(R.drawable.battery_unified_frame)
+ ?: throw IllegalStateException("Missing battery_unified_frame.xml")
+ val fill = BatteryFillDrawable(framePath)
+ val textOnly = BatteryPercentTextOnlyDrawable(PercentFont)
+ val spaceSharingText = BatterySpaceSharingPercentTextDrawable(PercentFont)
+ val attribution = BatteryAttributionDrawable(null)
+
+ return BatteryLayersDrawable(
+ frameBg = frameBg,
+ frame = frame,
+ fill = fill,
+ textOnly = textOnly,
+ spaceSharingText = spaceSharingText,
+ attribution = attribution,
+ batteryState = initialState,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
new file mode 100644
index 000000000000..123d6ba57900
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.battery.unified
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
+
+/**
+ * (Names are hard) this drawable calculates the percent text for inside of the
+ * [BatteryLayersDrawable], assuming that there is no other attribution in the foreground. In this
+ * case, we can use the maximum font size and center the text in the full render area inside of the
+ * frame. After accounting for the stroke width and the insets from there, our rendering area is
+ * 18x10 points.
+ *
+ * See [BatterySpaceSharingPercentTextDrawable] (names are still hard) for the space-sharing
+ * approach.
+ *
+ * Note that these drawing metrics are only tested to work with google-sans BOLD
+ */
+class BatteryPercentTextOnlyDrawable(font: Typeface) : Drawable() {
+ private var hScale = 1f
+ private var vScale = 1f
+
+ // range 0-100
+ var batteryLevel: Int = 100
+ set(value) {
+ field = value
+ percentText = "$value"
+ invalidateSelf()
+ }
+
+ private var percentText = "$batteryLevel"
+
+ private val textPaint =
+ Paint().also { p ->
+ p.textSize = 10f
+ p.typeface = font
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+
+ vScale = bounds.bottom / Metrics.ViewportHeight
+ hScale = bounds.right / Metrics.ViewportWidth
+
+ updateScale()
+ }
+
+ private fun updateScale() {
+ textPaint.textSize = TextSize * vScale
+ }
+
+ override fun draw(canvas: Canvas) {
+ val totalAvailableHeight = CanvasHeight * vScale
+
+ // Distribute the vertical whitespace around the text. This is a simplified version of
+ // the equation ((C - T) / 2) + T - V, where C == canvas height, T == text height, and V
+ // is the vertical nudge.
+ val offsetY = (totalAvailableHeight + textPaint.textSize) / 2 - (VerticalNudge * vScale)
+
+ val totalAvailableWidth = CanvasWidth * hScale
+ val textWidth = textPaint.measureText(percentText)
+ val offsetX = (totalAvailableWidth - textWidth) / 2
+
+ // Draw the text centered in the available area
+ canvas.drawText(
+ percentText,
+ (ViewportInsetLeft * hScale) + offsetX,
+ (ViewportInsetTop * vScale) + offsetY,
+ textPaint
+ )
+ }
+
+ override fun setTint(tintColor: Int) {
+ textPaint.color = tintColor
+ super.setTint(tintColor)
+ }
+
+ override fun getOpacity() = PixelFormat.OPAQUE
+
+ override fun setAlpha(alpha: Int) {}
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {}
+
+ companion object {
+ // Based on the 24x14 canvas, we can render in an 18x10 canvas, inset like so:
+ const val ViewportInsetLeft = 4f
+ const val ViewportInsetRight = 2f
+ const val ViewportInsetTop = 2f
+ const val CanvasHeight = 10f
+ const val CanvasWidth = 18f
+
+ // raise the text up by a smidgen so that it is more centered. Experimentally determined
+ const val VerticalNudge = 1.5f
+
+ // Experimentally-determined value
+ const val TextSize = 10f
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
new file mode 100644
index 000000000000..0c418b9caa7d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.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.battery.unified
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.Paint
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.Typeface
+import android.graphics.drawable.Drawable
+import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
+
+/**
+ * A variant of [BatteryPercentTextOnlyDrawable] with the following differences:
+ * 1. It is defined on a canvas of 12x10 (shortened by 6 points horizontally)
+ * 2. Because of this, we scale the font according to the number of characters
+ *
+ * Note that these drawing metrics are only tested to work with google-sans BOLD
+ */
+class BatterySpaceSharingPercentTextDrawable(font: Typeface) : Drawable() {
+ private var verticalNudge = 0f
+ private var hScale = 1f
+ private var vScale = 1f
+
+ // range 0-100
+ var batteryLevel: Int = 88
+ set(value) {
+ field = value
+ percentText = "$value"
+ invalidateSelf()
+ }
+
+ private var percentText = "$batteryLevel"
+ set(value) {
+ field = value
+ numberOfCharacters = percentText.length
+ }
+
+ private var numberOfCharacters = percentText.length
+ set(value) {
+ if (field != value) {
+ field = value
+ updateFontSize()
+ }
+ }
+
+ private val textPaint =
+ Paint().also { p ->
+ p.textSize = 10f
+ p.typeface = font
+ }
+
+ private fun updateFontSize() {
+ // These values are determined experimentally
+ when (numberOfCharacters) {
+ 3 -> {
+ verticalNudge = 1f
+ textPaint.textSize = 6f * hScale
+ }
+ // 1, 2
+ else -> {
+ verticalNudge = 1.25f
+ textPaint.textSize = 9f * hScale
+ }
+ }
+ }
+
+ private fun updateScale() {
+ updateFontSize()
+ }
+
+ override fun onBoundsChange(bounds: Rect) {
+ super.onBoundsChange(bounds)
+
+ hScale = bounds.right / Metrics.ViewportWidth
+ vScale = bounds.bottom / Metrics.ViewportHeight
+
+ updateScale()
+ }
+
+ override fun draw(canvas: Canvas) {
+ val totalAvailableHeight = CanvasHeight * vScale
+
+ // Distribute the vertical whitespace around the text. This is a simplified version of
+ // the equation ((C - T) / 2) + T - V, where C == canvas height, T == text height, and V
+ // is the vertical nudge.
+ val offsetY = (totalAvailableHeight + textPaint.textSize) / 2 - (verticalNudge * vScale)
+
+ val totalAvailableWidth = CanvasWidth * hScale
+ val textWidth = textPaint.measureText(percentText)
+ val offsetX = (totalAvailableWidth - textWidth) / 2
+
+ canvas.drawText(
+ percentText,
+ (ViewportInsetLeft * hScale) + offsetX,
+ (ViewportInsetTop * vScale) + offsetY,
+ textPaint
+ )
+ }
+
+ override fun setTint(tintColor: Int) {
+ textPaint.color = tintColor
+ super.setTint(tintColor)
+ }
+
+ override fun getOpacity() = PixelFormat.OPAQUE
+
+ override fun setAlpha(p0: Int) {}
+
+ override fun setColorFilter(colorFilter: ColorFilter?) {
+ textPaint.colorFilter = colorFilter
+ }
+
+ companion object {
+ private const val ViewportInsetLeft = 4f
+ private const val ViewportInsetTop = 2f
+
+ private const val CanvasWidth = 12f
+ private const val CanvasHeight = 10f
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 921e39532f58..16865ca809d9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -31,6 +31,7 @@ import android.hardware.biometrics.BiometricRequestConstants.RequestReason
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
import android.os.Build
import android.os.RemoteException
+import android.os.Trace
import android.provider.Settings
import android.util.Log
import android.util.RotationUtils
@@ -58,9 +59,9 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -125,11 +126,15 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
private val powerInteractor: PowerInteractor,
@Application private val scope: CoroutineScope,
) {
- private val isFinishedGoingToSleep: Flow<Unit> =
- powerInteractor.detailedWakefulness
- .filter { it.internalWakefulnessState == WakefulnessState.ASLEEP }
+ private val currentStateUpdatedToOffAodOrDozing: Flow<Unit> =
+ transitionInteractor.currentKeyguardState
+ .filter {
+ it == KeyguardState.OFF ||
+ it == KeyguardState.AOD ||
+ it == KeyguardState.DOZING
+ }
.map { } // map to Unit
- private var listenForAsleepJob: Job? = null
+ private var listenForCurrentKeyguardState: Job? = null
private var addViewRunnable: Runnable? = null
private var overlayViewLegacy: UdfpsView? = null
private set
@@ -280,18 +285,19 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
private fun addViewNowOrLater(view: View, animation: UdfpsAnimationViewController<*>?) {
if (udfpsViewPerformance()) {
addViewRunnable = kotlinx.coroutines.Runnable {
+ Trace.setCounter("UdfpsAddView", 1)
windowManager.addView(
view,
coreLayoutParams.updateDimensions(animation)
)
}
- if (powerInteractor.detailedWakefulness.value.internalWakefulnessState
- != WakefulnessState.STARTING_TO_SLEEP) {
+ if (powerInteractor.detailedWakefulness.value.isAwake()) {
+ // Device is awake, so we add the view immediately.
addViewIfPending()
} else {
- listenForAsleepJob?.cancel()
- listenForAsleepJob = scope.launch {
- isFinishedGoingToSleep.collect {
+ listenForCurrentKeyguardState?.cancel()
+ listenForCurrentKeyguardState = scope.launch {
+ currentStateUpdatedToOffAodOrDozing.collect {
addViewIfPending()
}
}
@@ -306,7 +312,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
private fun addViewIfPending() {
addViewRunnable?.let {
- listenForAsleepJob?.cancel()
+ listenForCurrentKeyguardState?.cancel()
it.run()
}
addViewRunnable = null
@@ -412,7 +418,14 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
udfpsDisplayModeProvider.disable(null)
}
getTouchOverlay()?.apply {
- windowManager.removeView(this)
+ if (udfpsViewPerformance()) {
+ if (this.parent != null) {
+ windowManager.removeView(this)
+ }
+ Trace.setCounter("UdfpsAddView", 0)
+ } else {
+ windowManager.removeView(this)
+ }
setOnTouchListener(null)
setOnHoverListener(null)
overlayTouchListener?.let {
@@ -423,7 +436,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
overlayViewLegacy = null
overlayTouchView = null
overlayTouchListener = null
- listenForAsleepJob?.cancel()
+ listenForCurrentKeyguardState?.cancel()
return wasShowing
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
index ae1539ebaf89..59b59bf1e437 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
@@ -50,6 +50,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -64,13 +65,16 @@ interface FacePropertyRepository {
/** The current face sensor location in current device rotation */
val sensorLocation: StateFlow<Point?>
+
+ /** The info of current available camera. */
+ val cameraInfo: StateFlow<CameraInfo?>
}
/** Describes a biometric sensor */
data class FaceSensorInfo(val id: Int, val strength: SensorStrength)
/** Data class for camera info */
-private data class CameraInfo(
+data class CameraInfo(
/** The logical id of the camera */
val cameraId: String,
/** The physical id of the camera */
@@ -124,7 +128,7 @@ constructor(
private val cameraInfoList: List<CameraInfo> = loadCameraInfoList()
private var currentPhysicalCameraId: String? = null
- private val defaultSensorLocation: StateFlow<Point?> =
+ override val cameraInfo: StateFlow<CameraInfo?> =
ConflatedCallbackFlow.conflatedCallbackFlow {
val callback =
object : CameraManager.AvailabilityCallback() {
@@ -142,7 +146,7 @@ constructor(
physicalCameraId == it.cameraPhysicalId
}
trySendWithFailureLogging(
- cameraInfo?.cameraLocation,
+ cameraInfo,
TAG,
"Update face sensor location to $cameraInfo."
)
@@ -168,7 +172,7 @@ constructor(
}
currentPhysicalCameraId = cameraInfo?.cameraPhysicalId
trySendWithFailureLogging(
- cameraInfo?.cameraLocation,
+ cameraInfo,
TAG,
"Update face sensor location to $cameraInfo."
)
@@ -181,8 +185,16 @@ constructor(
.stateIn(
applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue =
- if (cameraInfoList.isNotEmpty()) cameraInfoList[0].cameraLocation else null
+ initialValue = if (cameraInfoList.isNotEmpty()) cameraInfoList[0] else null
+ )
+
+ private val defaultSensorLocation: StateFlow<Point?> =
+ cameraInfo
+ .map { it?.cameraLocation }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null
)
override val sensorLocation: StateFlow<Point?> =
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 5d525413a919..151e1eeaefc5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -24,6 +24,7 @@ import com.android.systemui.communal.data.repository.CommunalPrefsRepository
import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalContentSize.FULL
import com.android.systemui.communal.shared.model.CommunalContentSize.HALF
@@ -278,14 +279,25 @@ constructor(
}
/** A list of widget content to be displayed in the communal hub. */
- val widgetContent: Flow<List<CommunalContentModel.Widget>> =
- widgetRepository.communalWidgets.map { widgets ->
- filterWidgetsByExistingUsers(widgets).map Widget@{ widget ->
- return@Widget CommunalContentModel.Widget(
- appWidgetId = widget.appWidgetId,
- providerInfo = widget.providerInfo,
- appWidgetHost = appWidgetHost,
- )
+ val widgetContent: Flow<List<WidgetContent>> =
+ combine(
+ widgetRepository.communalWidgets.map { filterWidgetsByExistingUsers(it) },
+ communalSettingsInteractor.communalWidgetCategories
+ ) { widgets, allowedCategories ->
+ widgets.map { widget ->
+ if (widget.providerInfo.widgetCategory and allowedCategories != 0) {
+ // At least one category this widget specified is allowed, so show it
+ WidgetContent.Widget(
+ appWidgetId = widget.appWidgetId,
+ providerInfo = widget.providerInfo,
+ appWidgetHost = appWidgetHost,
+ )
+ } else {
+ WidgetContent.DisabledWidget(
+ appWidgetId = widget.appWidgetId,
+ providerInfo = widget.providerInfo,
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index ae019a187bae..c64f666ebf10 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.communal.domain.model
import android.appwidget.AppWidgetProviderInfo
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
+import android.content.pm.ApplicationInfo
import android.widget.RemoteViews
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
@@ -42,20 +43,37 @@ sealed interface CommunalContentModel {
val createdTimestampMillis: Long
}
- data class Widget(
- val appWidgetId: Int,
- val providerInfo: AppWidgetProviderInfo,
- val appWidgetHost: CommunalAppWidgetHost,
- ) : CommunalContentModel {
- override val key = KEY.widget(appWidgetId)
- // Widget size is always half.
- override val size = CommunalContentSize.HALF
+ sealed interface WidgetContent : CommunalContentModel {
+ val appWidgetId: Int
+ val providerInfo: AppWidgetProviderInfo
+
+ data class Widget(
+ override val appWidgetId: Int,
+ override val providerInfo: AppWidgetProviderInfo,
+ val appWidgetHost: CommunalAppWidgetHost,
+ ) : WidgetContent {
+ override val key = KEY.widget(appWidgetId)
+ // Widget size is always half.
+ override val size = CommunalContentSize.HALF
+
+ /** Whether this widget can be reconfigured after it has already been added. */
+ val reconfigurable: Boolean
+ get() =
+ (providerInfo.widgetFeatures and WIDGET_FEATURE_RECONFIGURABLE != 0) &&
+ providerInfo.configure != null
+ }
+
+ data class DisabledWidget(
+ override val appWidgetId: Int,
+ override val providerInfo: AppWidgetProviderInfo
+ ) : WidgetContent {
+ override val key = KEY.disabledWidget(appWidgetId)
+ // Widget size is always half.
+ override val size = CommunalContentSize.HALF
- /** Whether this widget can be reconfigured after it has already been added. */
- val reconfigurable: Boolean
- get() =
- (providerInfo.widgetFeatures and WIDGET_FEATURE_RECONFIGURABLE != 0) &&
- providerInfo.configure != null
+ val appInfo: ApplicationInfo?
+ get() = providerInfo.providerInfo?.applicationInfo
+ }
}
/** A placeholder item representing a new widget being added */
@@ -111,6 +129,10 @@ sealed interface CommunalContentModel {
return "widget_$id"
}
+ fun disabledWidget(id: Int): String {
+ return "disabled_widget_$id"
+ }
+
fun widgetPlaceholder(): String {
return "widget_placeholder_${UUID.randomUUID()}"
}
@@ -129,5 +151,5 @@ sealed interface CommunalContentModel {
}
}
- fun isWidget() = this is Widget
+ fun isWidgetContent() = this is WidgetContent
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
index 080dbedcb350..93e2b37cfe87 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
@@ -21,6 +21,7 @@ import android.appwidget.AppWidgetProviderInfo
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
import android.content.ComponentName
+import android.os.Bundle
import android.os.UserHandle
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -56,6 +57,7 @@ constructor(
return widgetInfo.configure != null && !configurationOptional
}
}
+
private val logger = Logger(logBuffer, TAG)
/**
@@ -84,9 +86,16 @@ constructor(
private fun bindWidget(widgetId: Int, user: UserHandle, provider: ComponentName): Boolean {
if (appWidgetManager.isPresent) {
+ val options =
+ Bundle().apply {
+ putInt(
+ AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
+ AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+ )
+ }
return appWidgetManager
.get()
- .bindAppWidgetIdIfAllowed(widgetId, user, provider, /* options */ null)
+ .bindAppWidgetIdIfAllowed(widgetId, user, provider, options)
}
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index e8a84449566d..7f8103e63684 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -18,7 +18,7 @@ package com.android.systemui.controls.ui
import android.app.Activity
import android.app.ActivityOptions
-import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
import android.app.Dialog
import android.app.PendingIntent
import android.content.ComponentName
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 6d9994fb2205..ce24259bbc1e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -71,6 +71,7 @@ import android.media.MediaRouter2Manager;
import android.media.projection.IMediaProjectionManager;
import android.media.projection.MediaProjectionManager;
import android.media.session.MediaSessionManager;
+import android.nearby.NearbyManager;
import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
@@ -441,6 +442,12 @@ public class FrameworkServicesModule {
@Provides
@Singleton
+ static NearbyManager provideNearbyManager(Context context) {
+ return context.getSystemService(NearbyManager.class);
+ }
+
+ @Provides
+ @Singleton
static NetworkScoreManager provideNetworkScoreManager(Context context) {
return context.getSystemService(NetworkScoreManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index a90980fddfb0..a431a59fcef6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -16,7 +16,6 @@
package com.android.systemui.dagger;
-import com.android.systemui.globalactions.ShutdownUiModule;
import com.android.systemui.keyguard.CustomizationProvider;
import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
@@ -32,7 +31,6 @@ import dagger.Subcomponent;
DependencyProvider.class,
NotificationInsetsModule.class,
QsFrameTranslateModule.class,
- ShutdownUiModule.class,
SystemUIBinder.class,
SystemUIModule.class,
SystemUICoreStartableModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 1157d97f2f2e..19af371d1dfa 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -51,6 +51,7 @@ import com.android.systemui.complication.dagger.ComplicationComponent;
import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.SystemUser;
+import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.dagger.DemoModeModule;
import com.android.systemui.deviceentry.DeviceEntryModule;
import com.android.systemui.display.DisplayModule;
@@ -365,7 +366,8 @@ public abstract class SystemUIModule {
SysUiState sysUiState,
FeatureFlags featureFlags,
NotifPipelineFlags notifPipelineFlags,
- @Main Executor sysuiMainExecutor) {
+ @Main Executor sysuiMainExecutor,
+ @UiBackground Executor sysuiUiBgExecutor) {
return Optional.ofNullable(BubblesManager.create(context,
bubblesOptional,
notificationShadeWindowController,
@@ -384,7 +386,8 @@ public abstract class SystemUIModule {
sysUiState,
featureFlags,
notifPipelineFlags,
- sysuiMainExecutor));
+ sysuiMainExecutor,
+ sysuiUiBgExecutor));
}
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index cf91e147d1b3..0c9fbc27cc5d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -189,6 +189,18 @@ constructor(
}
}
.launchIn(applicationScope)
+
+ facePropertyRepository.cameraInfo
+ .onEach {
+ if (it != null && isRunning()) {
+ repository.cancel()
+ runFaceAuth(
+ FaceAuthUiEvent.FACE_AUTH_CAMERA_AVAILABLE_CHANGED,
+ fallbackToDetect = true
+ )
+ }
+ }
+ .launchIn(applicationScope)
}
private suspend fun resetLockedOutState(currentUserId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
index ee220d5fb713..08a6166fe557 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
@@ -31,6 +31,7 @@ import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ALTERNATE
import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ASSISTANT_VISIBILITY_CHANGED
import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.AUTH_REQUEST_DURING_CANCELLATION
import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.BIOMETRIC_ENABLED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.CAMERA_AVAILABLE_CHANGED
import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.CAMERA_LAUNCHED
import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE
import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DISPLAY_OFF
@@ -130,6 +131,7 @@ private object InternalFaceAuthReasons {
"Face auth stopped because non strong biometric allowed changed"
const val POSTURE_CHANGED = "Face auth started/stopped due to device posture changed."
const val DISPLAY_OFF = "Face auth stopped due to display state OFF."
+ const val CAMERA_AVAILABLE_CHANGED = "Face auth started due to the available camera changed"
}
/**
@@ -221,7 +223,9 @@ constructor(private val id: Int, val reason: String, var extraInfo: Int = 0) :
@UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED)
FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED),
@UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION),
- @UiEvent(doc = DISPLAY_OFF) FACE_AUTH_DISPLAY_OFF(1461, DISPLAY_OFF);
+ @UiEvent(doc = DISPLAY_OFF) FACE_AUTH_DISPLAY_OFF(1461, DISPLAY_OFF),
+ @UiEvent(doc = CAMERA_AVAILABLE_CHANGED)
+ FACE_AUTH_CAMERA_AVAILABLE_CHANGED(1623, CAMERA_AVAILABLE_CHANGED);
override fun getId(): Int = this.id
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 7a24d7693035..298da1359728 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -22,8 +22,10 @@ import com.android.server.notification.Flags.FLAG_VIBRATE_WHILE_UNLOCKED
import com.android.server.notification.Flags.crossAppPoliteNotifications
import com.android.server.notification.Flags.politeNotifications
import com.android.server.notification.Flags.vibrateWhileUnlocked
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
+import com.android.systemui.Flags.communalHub
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.SysUISingleton
@@ -58,6 +60,9 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
// ComposeLockscreen dependencies
ComposeLockscreen.token dependsOn keyguardBottomAreaRefactor
ComposeLockscreen.token dependsOn migrateClocksToBlueprint
+
+ // CommunalHub dependencies
+ communalHub dependsOn migrateClocksToBlueprint
}
private inline val politeNotifications
@@ -70,4 +75,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
private inline val migrateClocksToBlueprint
get() = FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint())
+ private inline val communalHub
+ get() = FlagToken(FLAG_COMMUNAL_HUB, communalHub())
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 33a69bf0d774..6bb846491224 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -369,12 +369,6 @@ object Flags {
@Keep
val WM_BUBBLE_BAR = sysPropBooleanFlag("persist.wm.debug.bubble_bar", default = false)
- // TODO(b/260271148): Tracking bug
- @Keep
- @JvmField
- val WM_DESKTOP_WINDOWING_2 =
- sysPropBooleanFlag("persist.wm.debug.desktop_mode_2", default = false)
-
// TODO(b/254513207): Tracking Bug to delete
@Keep
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
index 51978ece14db..ccd69ca55f0c 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
@@ -22,7 +22,10 @@ import android.annotation.Nullable;
import android.annotation.StringRes;
import android.app.Dialog;
import android.content.Context;
+import android.nearby.NearbyManager;
+import android.net.platform.flags.Flags;
import android.os.PowerManager;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
@@ -38,6 +41,8 @@ import com.android.systemui.scrim.ScrimDrawable;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.phone.ScrimController;
+import javax.inject.Inject;
+
/**
* Provides the UI shown during system shutdown.
*/
@@ -45,9 +50,13 @@ public class ShutdownUi {
private Context mContext;
private BlurUtils mBlurUtils;
- public ShutdownUi(Context context, BlurUtils blurUtils) {
+ private NearbyManager mNearbyManager;
+
+ @Inject
+ public ShutdownUi(Context context, BlurUtils blurUtils, NearbyManager nearbyManager) {
mContext = context;
mBlurUtils = blurUtils;
+ mNearbyManager = nearbyManager;
}
/**
@@ -132,12 +141,28 @@ public class ShutdownUi {
/**
* Returns the layout resource to use for UI while shutting down.
* @param isReboot Whether this is a reboot or a shutdown.
- * @return
*/
- public int getShutdownDialogContent(boolean isReboot) {
- return R.layout.shutdown_dialog;
+ @VisibleForTesting int getShutdownDialogContent(boolean isReboot) {
+ if (!Flags.poweredOffFindingPlatform()) {
+ return R.layout.shutdown_dialog;
+ }
+ int finderActive = mNearbyManager.getPoweredOffFindingMode();
+ if (finderActive == NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED
+ || finderActive == NearbyManager.POWERED_OFF_FINDING_MODE_UNSUPPORTED) {
+ // inactive or unsupported, use regular shutdown dialog
+ return R.layout.shutdown_dialog;
+ } else if (finderActive == NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED) {
+ // active, use dialog with finder info if shutting down
+ return isReboot ? R.layout.shutdown_dialog :
+ com.android.systemui.res.R.layout.shutdown_dialog_finder_active;
+ } else {
+ // that's weird? default to regular dialog
+ Log.w("ShutdownUi", "Unexpected value for finder active: " + finderActive);
+ return R.layout.shutdown_dialog;
+ }
}
+
@StringRes
@VisibleForTesting int getRebootMessage(boolean isReboot, @Nullable String reason) {
if (reason != null && reason.startsWith(PowerManager.REBOOT_RECOVERY_UPDATE)) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c6b99528b2ba..6d917bbde82b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -40,6 +40,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground;
import static com.android.systemui.Flags.refactorGetCurrentUser;
import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -1488,7 +1489,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
public void userActivity() {
- mPM.userActivity(mSystemClock.uptimeMillis(), false);
+ if (notifyPowerManagerUserActivityBackground()) {
+ mUiBgExecutor.execute(() -> mPM.userActivity(mSystemClock.uptimeMillis(), false));
+ } else {
+ mPM.userActivity(mSystemClock.uptimeMillis(), false);
+ }
}
private void setupLocked() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index b152eea63028..2bede977c958 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -40,6 +40,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.shareIn
@@ -177,92 +178,99 @@ constructor(
// TODO(b/322555228) Remove after consolidating device entry auth messages with BP auth messages
// in BiometricStatusRepository
+ /**
+ * FingerprintAuthenticationStatus Multiple statuses may arrive in immediate sequence (ie:
+ * acquired, failed, help, error), so we use a buffer to ensure consumers receive each distinct
+ * status.
+ */
override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
- get() = conflatedCallbackFlow {
- val callback =
- object : KeyguardUpdateMonitorCallback() {
- override fun onBiometricAuthenticated(
- userId: Int,
- biometricSourceType: BiometricSourceType,
- isStrongBiometric: Boolean,
- ) {
- sendUpdateIfFingerprint(
- biometricSourceType,
- SuccessFingerprintAuthenticationStatus(
- userId,
- isStrongBiometric,
- ),
- )
- }
+ get() =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricAuthenticated(
+ userId: Int,
+ biometricSourceType: BiometricSourceType,
+ isStrongBiometric: Boolean,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ SuccessFingerprintAuthenticationStatus(
+ userId,
+ isStrongBiometric,
+ ),
+ )
+ }
- override fun onBiometricError(
- msgId: Int,
- errString: String?,
- biometricSourceType: BiometricSourceType,
- ) {
- sendUpdateIfFingerprint(
- biometricSourceType,
- ErrorFingerprintAuthenticationStatus(
- msgId,
- errString,
- ),
- )
- }
+ override fun onBiometricError(
+ msgId: Int,
+ errString: String?,
+ biometricSourceType: BiometricSourceType,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ ErrorFingerprintAuthenticationStatus(
+ msgId,
+ errString,
+ ),
+ )
+ }
- override fun onBiometricHelp(
- msgId: Int,
- helpString: String?,
- biometricSourceType: BiometricSourceType,
- ) {
- sendUpdateIfFingerprint(
- biometricSourceType,
- HelpFingerprintAuthenticationStatus(
- msgId,
- helpString,
- ),
- )
- }
+ override fun onBiometricHelp(
+ msgId: Int,
+ helpString: String?,
+ biometricSourceType: BiometricSourceType,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ HelpFingerprintAuthenticationStatus(
+ msgId,
+ helpString,
+ ),
+ )
+ }
- override fun onBiometricAuthFailed(
- biometricSourceType: BiometricSourceType,
- ) {
- sendUpdateIfFingerprint(
- biometricSourceType,
- FailFingerprintAuthenticationStatus,
- )
- }
+ override fun onBiometricAuthFailed(
+ biometricSourceType: BiometricSourceType,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ FailFingerprintAuthenticationStatus,
+ )
+ }
- override fun onBiometricAcquired(
- biometricSourceType: BiometricSourceType,
- acquireInfo: Int,
- ) {
- sendUpdateIfFingerprint(
- biometricSourceType,
- AcquiredFingerprintAuthenticationStatus(
- AuthenticationReason.DeviceEntryAuthentication,
- acquireInfo
- ),
- )
- }
+ override fun onBiometricAcquired(
+ biometricSourceType: BiometricSourceType,
+ acquireInfo: Int,
+ ) {
+ sendUpdateIfFingerprint(
+ biometricSourceType,
+ AcquiredFingerprintAuthenticationStatus(
+ AuthenticationReason.DeviceEntryAuthentication,
+ acquireInfo
+ ),
+ )
+ }
- private fun sendUpdateIfFingerprint(
- biometricSourceType: BiometricSourceType,
- authenticationStatus: FingerprintAuthenticationStatus
- ) {
- if (biometricSourceType != BiometricSourceType.FINGERPRINT) {
- return
- }
+ private fun sendUpdateIfFingerprint(
+ biometricSourceType: BiometricSourceType,
+ authenticationStatus: FingerprintAuthenticationStatus
+ ) {
+ if (biometricSourceType != BiometricSourceType.FINGERPRINT) {
+ return
+ }
- trySendWithFailureLogging(
- authenticationStatus,
- TAG,
- "new fingerprint authentication status"
- )
- }
+ trySendWithFailureLogging(
+ authenticationStatus,
+ TAG,
+ "new fingerprint authentication status"
+ )
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
- keyguardUpdateMonitor.registerCallback(callback)
- awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
- }
+ .buffer(capacity = 4)
override val shouldUpdateIndicatorVisibility: Flow<Boolean> =
conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
index 59288a155666..0ea53906b7f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt
@@ -41,7 +41,6 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onStart
@@ -111,7 +110,6 @@ constructor(
awaitClose { clockRegistry.unregisterClockChangeListener(listener) }
}
.mapNotNull { it }
- .distinctUntilChanged()
override val currentClock: StateFlow<ClockController?> =
currentClockId
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 9fc36923b04d..6aed944ac809 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -31,8 +31,8 @@ import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.flow.sample
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@SysUISingleton
@@ -62,7 +62,6 @@ constructor(
listenForTransitionToCamera(scope, keyguardInteractor)
}
- @FlowPreview
private fun listenForAlternateBouncerToLockscreenHubAodOrDozing() {
scope.launch {
keyguardInteractor.alternateBouncerShowing
@@ -71,7 +70,7 @@ constructor(
// happening prematurely.
// This should eventually be removed in favor of
// [KeyguardTransitionInteractor#startDismissKeyguardTransition]
- .sample(150L)
+ .onEach { delay(150L) }
.sampleCombine(
keyguardInteractor.primaryBouncerShowing,
startedKeyguardTransitionStep,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index d1fd7195d8cc..719edd7e8535 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -45,7 +45,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.shareIn
/** Encapsulates business-logic related to the keyguard transitions. */
@@ -177,10 +176,16 @@ constructor(
* Lockscreen (0f).
*/
val dozeAmountTransition: Flow<TransitionStep> =
- merge(
- aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) },
- lockscreenToAodTransition,
- )
+ repository.transitions
+ .filter { step -> step.from == AOD || step.to == AOD }
+ .map { step ->
+ if (step.from == AOD) {
+ step.copy(value = 1 - step.value)
+ } else {
+ step
+ }
+ }
+ .shareIn(scope, SharingStarted.Eagerly, replay = 1)
/** The last [TransitionStep] with a [TransitionState] of STARTED */
val startedKeyguardTransitionStep: Flow<TransitionStep> =
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 873cc847fa60..f46a207b273a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -22,6 +22,7 @@ import android.content.res.ColorStateList
import android.util.StateSet
import android.view.HapticFeedbackConstants
import android.view.View
+import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.common.ui.view.LongPressHandlingView
@@ -82,6 +83,11 @@ object DeviceEntryIconViewBinder {
// of the transition.
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
+ viewModel.isVisible.collect { isVisible ->
+ longPressHandlingView.isInvisible = !isVisible
+ }
+ }
+ launch {
viewModel.isLongPressEnabled.collect { isEnabled ->
longPressHandlingView.setLongPressHandlingEnabled(isEnabled)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
index 8a3b57ba027f..5741b9485287 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
@@ -29,7 +29,6 @@ import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combineTransform
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onStart
/** Models UI state for the alpha of the AOD (always-on display). */
@@ -46,24 +45,23 @@ constructor(
/** The alpha level for the entire lockscreen while in AOD. */
val alpha: Flow<Float> =
combineTransform(
- keyguardTransitionInteractor.transitions,
- goneToAodTransitionViewModel.enterFromTopAnimationAlpha.onStart { emit(0f) },
- goneToDozingTransitionViewModel.lockscreenAlpha.onStart { emit(0f) },
- keyguardInteractor.keyguardAlpha.onStart { emit(1f) },
- ) { step, goneToAodAlpha, goneToDozingAlpha, keyguardAlpha ->
- if (step.to == GONE) {
- // When transitioning to GONE, only emit a value when complete as other
- // transitions may be controlling the alpha fade
- if (step.value == 1f) {
- emit(0f)
- }
- } else if (step.from == GONE && step.to == AOD) {
- emit(goneToAodAlpha)
- } else if (step.from == GONE && step.to == DOZING) {
- emit(goneToDozingAlpha)
- } else if (!migrateClocksToBlueprint()) {
- emit(keyguardAlpha)
+ keyguardTransitionInteractor.transitions,
+ goneToAodTransitionViewModel.enterFromTopAnimationAlpha.onStart { emit(0f) },
+ goneToDozingTransitionViewModel.lockscreenAlpha.onStart { emit(0f) },
+ keyguardInteractor.keyguardAlpha.onStart { emit(1f) },
+ ) { step, goneToAodAlpha, goneToDozingAlpha, keyguardAlpha ->
+ if (step.to == GONE) {
+ // When transitioning to GONE, only emit a value when complete as other
+ // transitions may be controlling the alpha fade
+ if (step.value == 1f) {
+ emit(0f)
}
+ } else if (step.from == GONE && step.to == AOD) {
+ emit(goneToAodAlpha)
+ } else if (step.from == GONE && step.to == DOZING) {
+ emit(goneToDozingAlpha)
+ } else if (!migrateClocksToBlueprint()) {
+ emit(keyguardAlpha)
}
- .distinctUntilChanged()
+ }
}
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 8665aabc3ef7..7be390a4526f 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
@@ -28,10 +28,6 @@ import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
-import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
-import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
@@ -47,7 +43,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
/**
@@ -127,17 +122,9 @@ constructor(
params: BurnInParameters,
): Flow<BurnInModel> {
return combine(
- merge(
- keyguardTransitionInteractor.transition(GONE, AOD).map { it.value },
- keyguardTransitionInteractor.transition(AOD, PRIMARY_BOUNCER).map {
- 1f - it.value
- },
- keyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, AOD).map {
- it.value
- },
- keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
- )
- .map { dozeAmount -> Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) },
+ keyguardTransitionInteractor.dozeAmountTransition.map {
+ Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value)
+ },
burnInInteractor.keyguardBurnIn,
) { interpolated, burnIn ->
val useScaleOnly =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index d657c24d7ae0..62810974be80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -70,12 +71,13 @@ constructor(
// While dozing, the display can show the AOD UI; show the AOD udfps when dozing
private val useAodIconVariant: Flow<Boolean> =
- combine(isShowingAodOrDozing, deviceEntryUdfpsInteractor.isUdfpsSupported) {
- isTransitionToAodOrDozing,
- isUdfps ->
- isTransitionToAodOrDozing && isUdfps
+ deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfspEnrolled ->
+ if (udfspEnrolled) {
+ isShowingAodOrDozing.distinctUntilChanged()
+ } else {
+ flowOf(false)
}
- .distinctUntilChanged()
+ }
private val padding: Flow<Int> =
deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { udfpsSupported ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index c9cf0c31a8fd..31e809356e02 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -37,6 +37,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
@@ -206,6 +207,7 @@ constructor(
DeviceEntryIconView.IconType.LOCK
}
}
+ val isVisible: Flow<Boolean> = deviceEntryViewAlpha.map { it > 0f }.distinctUntilChanged()
val isLongPressEnabled: Flow<Boolean> =
combine(
iconType,
@@ -217,6 +219,7 @@ constructor(
DeviceEntryIconView.IconType.FINGERPRINT -> false
}
}
+
val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> =
combine(iconType, isLongPressEnabled) { deviceEntryStatus, longPressEnabled ->
if (longPressEnabled) {
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 921eb66cd3cc..bdcaf0951c5b 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
@@ -74,6 +74,10 @@ constructor(
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
+ private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+ private val goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel,
+ private val lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel,
+ private val lockscreenToDozingTransitionViewModel: LockscreenToDozingTransitionViewModel,
private val lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel,
private val lockscreenToGlanceableHubTransitionViewModel:
LockscreenToGlanceableHubTransitionViewModel,
@@ -133,17 +137,24 @@ constructor(
fun alpha(viewState: ViewStateAccessor): Flow<Float> {
return combine(
communalInteractor.isIdleOnCommunal,
- keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
+ keyguardTransitionInteractor
+ .transitionValue(GONE)
+ .map { it == 1f }
+ .onStart { emit(false) }
+ .distinctUntilChanged(),
// The transitions are mutually exclusive, so they are safe to merge to get the last
// value emitted by any of them. Do not add flows that cannot make this guarantee.
merge(
- aodAlphaViewModel.alpha,
alphaOnShadeExpansion,
keyguardInteractor.dismissAlpha.filterNotNull(),
alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
+ goneToAodTransitionViewModel.enterFromTopAnimationAlpha,
+ goneToDozingTransitionViewModel.lockscreenAlpha,
+ lockscreenToAodTransitionViewModel.lockscreenAlpha(viewState),
+ lockscreenToDozingTransitionViewModel.lockscreenAlpha,
lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState),
@@ -156,8 +167,8 @@ constructor(
primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha,
)
.onStart { emit(1f) }
- ) { isIdleOnCommunal, goneValue, alpha ->
- if (isIdleOnCommunal || goneValue == 1f) {
+ ) { isIdleOnCommunal, gone, alpha ->
+ if (isIdleOnCommunal || gone) {
// Keyguard should not show while the communal hub is fully visible. This check
// is added since at the moment, closing the notification shade will cause the
// keyguard alpha to be set back to 1. Also ensure keyguard is never visible
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index d79288947e78..23320be129fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -23,6 +23,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -39,6 +40,7 @@ constructor(
private val interactor: KeyguardBlueprintInteractor,
private val authController: AuthController,
val longPress: KeyguardLongPressViewModel,
+ val splitShadeStateController: SplitShadeStateController,
) {
private val clockSize = clockInteractor.clockSize
@@ -46,8 +48,10 @@ constructor(
get() = authController.isUdfpsSupported
val isLargeClockVisible: Boolean
get() = clockSize.value == KeyguardClockSwitch.LARGE
- val areNotificationsVisible: Boolean
- get() = !isLargeClockVisible
+ fun areNotificationsVisible(resources: Resources): Boolean {
+ return !isLargeClockVisible ||
+ splitShadeStateController.shouldUseSplitNotificationShade(resources)
+ }
fun getSmartSpacePaddingTop(resources: Resources): Int {
return if (isLargeClockVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
index 7bf51a7d3d54..1f9f3043dfdf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
@@ -67,6 +68,15 @@ constructor(
onCancel = { 1f },
)
+ fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var startAlpha = 1f
+ return transitionAnimation.sharedFlow(
+ duration = 500.milliseconds,
+ onStart = { startAlpha = viewState.alpha() },
+ onStep = { MathUtils.lerp(startAlpha, 1f, it) },
+ )
+ }
+
override val deviceEntryParentViewAlpha: Flow<Float> =
deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest {
isUdfpsEnrolledAndEnabled ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
index b60c52b1c3df..c836f01e2ee9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt
@@ -44,6 +44,14 @@ constructor(
to = KeyguardState.DOZING,
)
+ val lockscreenAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ onStep = { 1 - it },
+ onFinish = { 1f },
+ onCancel = { 1f },
+ )
+
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
duration = 250.milliseconds,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt
new file mode 100644
index 000000000000..027a739b4abf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.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.keyguard.ui.viewmodel
+
+import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import javax.inject.Inject
+
+class MediaCarouselViewModel @Inject constructor(mediaDataManager: MediaDataManager) {
+ val isMediaVisible: Boolean = mediaDataManager.hasActiveMediaOrRecommendation()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 378ce52b4331..53f448826e80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -60,6 +60,22 @@ constructor(
private var leaveShadeOpen: Boolean = false
private var willRunDismissFromKeyguard: Boolean = false
+ val notificationAlpha: Flow<Float> =
+ transitionAnimation.sharedFlow(
+ duration = 200.milliseconds,
+ onStart = {
+ leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+ willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard()
+ },
+ onStep = {
+ if (willRunDismissFromKeyguard || leaveShadeOpen) {
+ 1f
+ } else {
+ 1f - it
+ }
+ },
+ )
+
/** Bouncer container alpha */
val bouncerAlpha: Flow<Float> =
if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
@@ -94,6 +110,7 @@ constructor(
} else {
createLockscreenAlpha(primaryBouncerInteractor::willRunDismissFromKeyguard)
}
+
private fun createLockscreenAlpha(willRunAnimationOnKeyguard: () -> Boolean): Flow<Float> {
return transitionAnimation.sharedFlow(
duration = 50.milliseconds,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
index 6fc22ea60a9e..865c49e1d817 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.domain.pipeline
import android.annotation.SuppressLint
+import android.app.ActivityOptions
import android.app.BroadcastOptions
import android.app.Notification
import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
@@ -1272,7 +1273,7 @@ class MediaDataManager(
val options = BroadcastOptions.makeBasic()
options.setInteractive(true)
options.setPendingIntentBackgroundActivityStartMode(
- BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
)
intent.send(options.toBundle())
true
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
index afe628527833..f1cade7512e2 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionServiceHelper.kt
@@ -16,9 +16,6 @@
package com.android.systemui.mediaprojection
-import android.compat.annotation.ChangeId
-import android.compat.annotation.Disabled
-import android.compat.annotation.Overridable
import android.content.Context
import android.media.projection.IMediaProjection
import android.media.projection.IMediaProjectionManager
@@ -34,18 +31,6 @@ import android.util.Log
*/
class MediaProjectionServiceHelper {
companion object {
- /**
- * This change id ensures that users are presented with a choice of capturing a single app
- * or the entire screen when initiating a MediaProjection session, overriding the usage of
- * MediaProjectionConfig#createConfigForDefaultDisplay.
- *
- * @hide
- */
- @ChangeId
- @Overridable
- @Disabled
- const val OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION = 316897322L // buganizer id
-
private const val TAG = "MediaProjectionServiceHelper"
private val service =
IMediaProjectionManager.Stub.asInterface(
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index a811065fdc65..e1741c73d453 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -18,7 +18,7 @@ package com.android.systemui.mediaprojection.appselector.view
import android.app.ActivityOptions
import android.app.ActivityOptions.LaunchCookie
-import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+import android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
import android.app.IActivityTaskManager
import android.graphics.Rect
import android.view.LayoutInflater
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 0769731bffb1..8b034b293dcb 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -16,26 +16,21 @@
package com.android.systemui.mediaprojection.permission;
-import static android.Manifest.permission.LOG_COMPAT_CHANGE;
-import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-import static com.android.systemui.mediaprojection.MediaProjectionServiceHelper.OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION;
import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.ENTIRE_SCREEN;
import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions.LaunchCookie;
import android.app.AlertDialog;
import android.app.StatusBarManager;
-import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -109,7 +104,6 @@ public class MediaProjectionPermissionActivity extends Activity
}
@Override
- @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE})
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -237,9 +231,6 @@ public class MediaProjectionPermissionActivity extends Activity
// the correct screen width when in split screen.
Context dialogContext = getApplicationContext();
if (isPartialScreenSharingEnabled()) {
- final boolean overrideDisableSingleAppOption = CompatChanges.isChangeEnabled(
- OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
- mPackageName, getHostUserHandle());
MediaProjectionPermissionDialogDelegate delegate =
new MediaProjectionPermissionDialogDelegate(
dialogContext,
@@ -251,7 +242,6 @@ public class MediaProjectionPermissionActivity extends Activity
},
() -> finish(RECORD_CANCEL, /* projection= */ null),
appName,
- overrideDisableSingleAppOption,
mUid,
mMediaProjectionMetricsLogger);
mDialog =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 9ce8070131fa..0f54e934f3cf 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -30,12 +30,11 @@ class MediaProjectionPermissionDialogDelegate(
private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
private val onCancelClicked: Runnable,
private val appName: String?,
- forceShowPartialScreenshare: Boolean,
hostUid: Int,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
) :
BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
- createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
+ createOptionList(context, appName, mediaProjectionConfig),
appName,
hostUid,
mediaProjectionMetricsLogger
@@ -66,8 +65,7 @@ class MediaProjectionPermissionDialogDelegate(
private fun createOptionList(
context: Context,
appName: String?,
- mediaProjectionConfig: MediaProjectionConfig?,
- overrideDisableSingleAppOption: Boolean = false,
+ mediaProjectionConfig: MediaProjectionConfig?
): List<ScreenShareOption> {
val singleAppWarningText =
if (appName == null) {
@@ -82,13 +80,8 @@ class MediaProjectionPermissionDialogDelegate(
R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
}
- // The single app option should only be disabled if there is an app name provided,
- // the client has setup a MediaProjection with
- // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by
- // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
val singleAppOptionDisabled =
appName != null &&
- !overrideDisableSingleAppOption &&
mediaProjectionConfig?.regionToCapture ==
MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt
index 6b53c7ac0a14..7ece6e0defc1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegate.kt
@@ -37,11 +37,13 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.time.SystemClock
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -52,19 +54,24 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
/** Dialog for showing active, connected and saved bluetooth devices. */
-@SysUISingleton
-internal class BluetoothTileDialog
-constructor(
- private val bluetoothToggleInitialValue: Boolean,
- private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
- private val cachedContentHeight: Int,
- private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
+class BluetoothTileDialogDelegate
+@AssistedInject
+internal constructor(
+ @Assisted private val context: Context,
+ @Assisted private val initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
+ @Assisted private val cachedContentHeight: Int,
+ @Assisted private val bluetoothToggleInitialValue: Boolean,
+ @Assisted private val bluetoothTileDialogCallback: BluetoothTileDialogCallback,
+ @Assisted private val dismissListener: Runnable,
@Main private val mainDispatcher: CoroutineDispatcher,
private val systemClock: SystemClock,
private val uiEventLogger: UiEventLogger,
private val logger: BluetoothTileDialogLogger,
- context: Context,
-) : SystemUIDialog(context, DEFAULT_THEME, DEFAULT_DISMISS_ON_DEVICE_LOCK) {
+ private val systemuiDialogFactory: SystemUIDialog.Factory,
+ mainLayoutInflater: LayoutInflater,
+) : SystemUIDialog.Delegate {
+
+ private val layoutInflater = mainLayoutInflater.cloneInContext(context)
private val mutableBluetoothStateToggle: MutableStateFlow<Boolean> =
MutableStateFlow(bluetoothToggleInitialValue)
@@ -91,78 +98,72 @@ constructor(
private var lastItemRow: Int = -1
- private lateinit var toggleView: Switch
- private lateinit var subtitleTextView: TextView
- private lateinit var autoOnToggle: Switch
- private lateinit var autoOnToggleView: View
- private lateinit var doneButton: View
- private lateinit var seeAllButton: View
- private lateinit var pairNewDeviceButton: View
- private lateinit var deviceListView: RecyclerView
- private lateinit var scrollViewContent: View
- private lateinit var progressBarAnimation: ProgressBar
- private lateinit var progressBarBackground: View
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
+ @AssistedFactory
+ internal interface Factory {
+ fun create(
+ context: Context,
+ initialUiProperties: BluetoothTileDialogViewModel.UiProperties,
+ cachedContentHeight: Int,
+ bluetoothEnabled: Boolean,
+ dialogCallback: BluetoothTileDialogCallback,
+ dimissListener: Runnable
+ ): BluetoothTileDialogDelegate
+ }
+
+ override fun createDialog(): SystemUIDialog {
+ val dialog = systemuiDialogFactory.create(this, context)
+
+ return dialog
+ }
+
+ override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+ SystemUIDialog.registerDismissListener(dialog, dismissListener)
uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TILE_DIALOG_SHOWN)
- LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null).apply {
+ layoutInflater.inflate(R.layout.bluetooth_tile_dialog, null).apply {
accessibilityPaneTitle = context.getText(R.string.accessibility_desc_quick_settings)
- setContentView(this)
+ dialog.setContentView(this)
}
- toggleView = requireViewById(R.id.bluetooth_toggle)
- subtitleTextView = requireViewById(R.id.bluetooth_tile_dialog_subtitle) as TextView
- autoOnToggle = requireViewById(R.id.bluetooth_auto_on_toggle)
- autoOnToggleView = requireViewById(R.id.bluetooth_auto_on_toggle_layout)
- doneButton = requireViewById(R.id.done_button)
- seeAllButton = requireViewById(R.id.see_all_button)
- pairNewDeviceButton = requireViewById(R.id.pair_new_device_button)
- deviceListView = requireViewById<RecyclerView>(R.id.device_list)
-
- setupToggle()
- setupRecyclerView()
-
- subtitleTextView.text = context.getString(initialUiProperties.subTitleResId)
- doneButton.setOnClickListener { dismiss() }
- seeAllButton.setOnClickListener { bluetoothTileDialogCallback.onSeeAllClicked(it) }
- pairNewDeviceButton.setOnClickListener {
+ setupToggle(dialog)
+ setupRecyclerView(dialog)
+
+ getSubtitleTextView(dialog).text = context.getString(initialUiProperties.subTitleResId)
+ dialog.requireViewById<View>(R.id.done_button).setOnClickListener { dialog.dismiss() }
+ getSeeAllButton(dialog).setOnClickListener {
+ bluetoothTileDialogCallback.onSeeAllClicked(it)
+ }
+ getPairNewDeviceButton(dialog).setOnClickListener {
bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
}
- requireViewById<View>(R.id.scroll_view).apply {
- scrollViewContent = this
+ getScrollViewContent(dialog).apply {
minimumHeight =
resources.getDimensionPixelSize(initialUiProperties.scrollViewMinHeightResId)
layoutParams.height = maxOf(cachedContentHeight, minimumHeight)
}
- progressBarAnimation = requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
- progressBarBackground = requireViewById(R.id.bluetooth_tile_dialog_progress_background)
}
- override fun start() {
+ override fun onStart(dialog: SystemUIDialog) {
lastUiUpdateMs = systemClock.elapsedRealtime()
}
- override fun dismiss() {
- if (::scrollViewContent.isInitialized) {
- mutableContentHeight.tryEmit(scrollViewContent.measuredHeight)
- }
- super.dismiss()
+ override fun onStop(dialog: SystemUIDialog) {
+ mutableContentHeight.tryEmit(getScrollViewContent(dialog).measuredHeight)
}
- internal suspend fun animateProgressBar(animate: Boolean) {
+ internal suspend fun animateProgressBar(dialog: SystemUIDialog, animate: Boolean) {
withContext(mainDispatcher) {
if (animate) {
- showProgressBar()
+ showProgressBar(dialog)
} else {
delay(PROGRESS_BAR_ANIMATION_DURATION_MS)
- hideProgressBar()
+ hideProgressBar(dialog)
}
}
}
internal suspend fun onDeviceItemUpdated(
+ dialog: SystemUIDialog,
deviceItem: List<DeviceItem>,
showSeeAll: Boolean,
showPairNewDevice: Boolean
@@ -176,10 +177,11 @@ constructor(
}
if (isActive) {
deviceItemAdapter.refreshDeviceItemList(deviceItem) {
- seeAllButton.visibility = if (showSeeAll) VISIBLE else GONE
- pairNewDeviceButton.visibility = if (showPairNewDevice) VISIBLE else GONE
+ getSeeAllButton(dialog).visibility = if (showSeeAll) VISIBLE else GONE
+ getPairNewDeviceButton(dialog).visibility =
+ if (showPairNewDevice) VISIBLE else GONE
// Update the height after data is updated
- scrollViewContent.layoutParams.height = WRAP_CONTENT
+ getScrollViewContent(dialog).layoutParams.height = WRAP_CONTENT
lastUiUpdateMs = systemClock.elapsedRealtime()
lastItemRow = itemRow
logger.logDeviceUiUpdate(lastUiUpdateMs - start)
@@ -189,29 +191,29 @@ constructor(
}
internal fun onBluetoothStateUpdated(
+ dialog: SystemUIDialog,
isEnabled: Boolean,
uiProperties: BluetoothTileDialogViewModel.UiProperties
) {
- toggleView.apply {
+ getToggleView(dialog).apply {
isChecked = isEnabled
setEnabled(true)
alpha = ENABLED_ALPHA
}
- subtitleTextView.text = context.getString(uiProperties.subTitleResId)
- autoOnToggleView.visibility = uiProperties.autoOnToggleVisibility
+ getSubtitleTextView(dialog).text = context.getString(uiProperties.subTitleResId)
+ getAutoOnToggleView(dialog).visibility = uiProperties.autoOnToggleVisibility
}
- internal fun onBluetoothAutoOnUpdated(isEnabled: Boolean) {
- if (::autoOnToggle.isInitialized) {
- autoOnToggle.apply {
- isChecked = isEnabled
- setEnabled(true)
- alpha = ENABLED_ALPHA
- }
+ internal fun onBluetoothAutoOnUpdated(dialog: SystemUIDialog, isEnabled: Boolean) {
+ getAutoOnToggle(dialog).apply {
+ isChecked = isEnabled
+ setEnabled(true)
+ alpha = ENABLED_ALPHA
}
}
- private fun setupToggle() {
+ private fun setupToggle(dialog: SystemUIDialog) {
+ val toggleView = getToggleView(dialog)
toggleView.isChecked = bluetoothToggleInitialValue
toggleView.setOnCheckedChangeListener { view, isChecked ->
mutableBluetoothStateToggle.value = isChecked
@@ -223,8 +225,8 @@ constructor(
uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_TOGGLE_CLICKED)
}
- autoOnToggleView.visibility = initialUiProperties.autoOnToggleVisibility
- autoOnToggle.setOnCheckedChangeListener { view, isChecked ->
+ getAutoOnToggleView(dialog).visibility = initialUiProperties.autoOnToggleVisibility
+ getAutoOnToggle(dialog).setOnCheckedChangeListener { view, isChecked ->
mutableBluetoothAutoOnToggle.value = isChecked
view.apply {
isEnabled = false
@@ -234,30 +236,66 @@ constructor(
}
}
- private fun setupRecyclerView() {
- deviceListView.apply {
+ private fun getToggleView(dialog: SystemUIDialog): Switch {
+ return dialog.requireViewById(R.id.bluetooth_toggle)
+ }
+
+ private fun getSubtitleTextView(dialog: SystemUIDialog): TextView {
+ return dialog.requireViewById(R.id.bluetooth_tile_dialog_subtitle)
+ }
+
+ private fun getSeeAllButton(dialog: SystemUIDialog): View {
+ return dialog.requireViewById(R.id.see_all_button)
+ }
+
+ private fun getPairNewDeviceButton(dialog: SystemUIDialog): View {
+ return dialog.requireViewById(R.id.pair_new_device_button)
+ }
+
+ private fun getDeviceListView(dialog: SystemUIDialog): RecyclerView {
+ return dialog.requireViewById(R.id.device_list)
+ }
+
+ private fun getAutoOnToggle(dialog: SystemUIDialog): Switch {
+ return dialog.requireViewById(R.id.bluetooth_auto_on_toggle)
+ }
+
+ private fun getAutoOnToggleView(dialog: SystemUIDialog): View {
+ return dialog.requireViewById(R.id.bluetooth_auto_on_toggle_layout)
+ }
+
+ private fun getProgressBarAnimation(dialog: SystemUIDialog): ProgressBar {
+ return dialog.requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
+ }
+
+ private fun getProgressBarBackground(dialog: SystemUIDialog): View {
+ return dialog.requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
+ }
+
+ private fun getScrollViewContent(dialog: SystemUIDialog): View {
+ return dialog.requireViewById(R.id.scroll_view)
+ }
+
+ private fun setupRecyclerView(dialog: SystemUIDialog) {
+ getDeviceListView(dialog).apply {
layoutManager = LinearLayoutManager(context)
adapter = deviceItemAdapter
}
}
- private fun showProgressBar() {
- if (
- ::progressBarAnimation.isInitialized &&
- ::progressBarBackground.isInitialized &&
- progressBarAnimation.visibility != VISIBLE
- ) {
+ private fun showProgressBar(dialog: SystemUIDialog) {
+ val progressBarAnimation = getProgressBarAnimation(dialog)
+ val progressBarBackground = getProgressBarBackground(dialog)
+ if (progressBarAnimation.visibility != VISIBLE) {
progressBarAnimation.visibility = VISIBLE
progressBarBackground.visibility = INVISIBLE
}
}
- private fun hideProgressBar() {
- if (
- ::progressBarAnimation.isInitialized &&
- ::progressBarBackground.isInitialized &&
- progressBarAnimation.visibility != INVISIBLE
- ) {
+ private fun hideProgressBar(dialog: SystemUIDialog) {
+ val progressBarAnimation = getProgressBarAnimation(dialog)
+ val progressBarBackground = getProgressBarBackground(dialog)
+ if (progressBarAnimation.visibility != INVISIBLE) {
progressBarAnimation.visibility = INVISIBLE
progressBarBackground.visibility = VISIBLE
}
@@ -295,9 +333,7 @@ constructor(
private val asyncListDiffer = AsyncListDiffer(this, diffUtilCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeviceItemViewHolder {
- val view =
- LayoutInflater.from(parent.context)
- .inflate(R.layout.bluetooth_device_item, parent, false)
+ val view = layoutInflater.inflate(R.layout.bluetooth_device_item, parent, false)
return DeviceItemViewHolder(view)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
index 5a14e5f11d38..04862077969d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
@@ -38,13 +38,11 @@ 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.BluetoothTileDialog.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ACTION_PAIR_NEW_DEVICE
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
-import com.android.systemui.qs.tiles.dialog.bluetooth.BluetoothTileDialog.Companion.MAX_DEVICE_ITEM_ENTRY
+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 com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -67,13 +65,12 @@ constructor(
private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val activityStarter: ActivityStarter,
- private val systemClock: SystemClock,
private val uiEventLogger: UiEventLogger,
- private val logger: BluetoothTileDialogLogger,
@Application private val coroutineScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@Background private val backgroundDispatcher: CoroutineDispatcher,
@Main private val sharedPreferences: SharedPreferences,
+ private val bluetoothDialogDelegateFactory: BluetoothTileDialogDelegate.Factory,
) : BluetoothTileDialogCallback {
private var job: Job? = null
@@ -92,7 +89,8 @@ constructor(
coroutineScope.launch(mainDispatcher) {
var updateDeviceItemJob: Job?
var updateDialogUiJob: Job? = null
- val dialog = createBluetoothTileDialog(context)
+ val dialogDelegate = createBluetoothTileDialog(context)
+ val dialog = dialogDelegate.createDialog()
view?.let {
dialogTransitionAnimator.showFromView(
@@ -118,13 +116,14 @@ constructor(
.onEach {
updateDialogUiJob?.cancel()
updateDialogUiJob = launch {
- dialog.apply {
+ dialogDelegate.apply {
onDeviceItemUpdated(
+ dialog,
it.take(MAX_DEVICE_ITEM_ENTRY),
showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
)
- animateProgressBar(false)
+ animateProgressBar(dialog, false)
}
}
}
@@ -134,7 +133,7 @@ constructor(
// the device item list and animiate the progress bar.
deviceItemInteractor.deviceItemUpdateRequest
.onEach {
- dialog.animateProgressBar(true)
+ dialogDelegate.animateProgressBar(dialog, true)
updateDeviceItemJob?.cancel()
updateDeviceItemJob = launch {
deviceItemInteractor.updateDeviceItems(
@@ -150,7 +149,8 @@ constructor(
bluetoothStateInteractor.bluetoothStateUpdate
.filterNotNull()
.onEach {
- dialog.onBluetoothStateUpdated(
+ dialogDelegate.onBluetoothStateUpdated(
+ dialog,
it,
UiProperties.build(it, isAutoOnToggleFeatureAvailable())
)
@@ -166,20 +166,20 @@ constructor(
// bluetoothStateToggle is emitted when user toggles the bluetooth state switch,
// send the new value to the bluetoothStateInteractor and animate the progress bar.
- dialog.bluetoothStateToggle
+ dialogDelegate.bluetoothStateToggle
.onEach {
- dialog.animateProgressBar(true)
+ dialogDelegate.animateProgressBar(dialog, true)
bluetoothStateInteractor.isBluetoothEnabled = it
}
.launchIn(this)
// deviceItemClick is emitted when user clicked on a device item.
- dialog.deviceItemClick
+ dialogDelegate.deviceItemClick
.onEach { deviceItemInteractor.updateDeviceItemOnClick(it) }
.launchIn(this)
// contentHeight is emitted when the dialog is dismissed.
- dialog.contentHeight
+ dialogDelegate.contentHeight
.onEach {
withContext(backgroundDispatcher) {
sharedPreferences.edit().putInt(CONTENT_HEIGHT_PREF_KEY, it).apply()
@@ -191,12 +191,12 @@ constructor(
// bluetoothAutoOnUpdate is emitted when bluetooth auto on on/off state is
// changed.
bluetoothAutoOnInteractor.isEnabled
- .onEach { dialog.onBluetoothAutoOnUpdated(it) }
+ .onEach { dialogDelegate.onBluetoothAutoOnUpdated(dialog, it) }
.launchIn(this)
// bluetoothAutoOnToggle is emitted when user toggles the bluetooth auto on
// switch, send the new value to the bluetoothAutoOnInteractor.
- dialog.bluetoothAutoOnToggle
+ dialogDelegate.bluetoothAutoOnToggle
.filterNotNull()
.onEach { bluetoothAutoOnInteractor.setEnabled(it) }
.launchIn(this)
@@ -206,7 +206,7 @@ constructor(
}
}
- private suspend fun createBluetoothTileDialog(context: Context): BluetoothTileDialog {
+ private suspend fun createBluetoothTileDialog(context: Context): BluetoothTileDialogDelegate {
val cachedContentHeight =
withContext(backgroundDispatcher) {
sharedPreferences.getInt(
@@ -215,21 +215,17 @@ constructor(
)
}
- return BluetoothTileDialog(
+ return bluetoothDialogDelegateFactory.create(
+ context,
+ UiProperties.build(
bluetoothStateInteractor.isBluetoothEnabled,
- UiProperties.build(
- bluetoothStateInteractor.isBluetoothEnabled,
- isAutoOnToggleFeatureAvailable()
- ),
- cachedContentHeight,
- this@BluetoothTileDialogViewModel,
- mainDispatcher,
- systemClock,
- uiEventLogger,
- logger,
- context
- )
- .apply { SystemUIDialog.registerDismissListener(this) { cancelJob() } }
+ isAutoOnToggleFeatureAvailable()
+ ),
+ cachedContentHeight,
+ bluetoothStateInteractor.isBluetoothEnabled,
+ this@BluetoothTileDialogViewModel,
+ { cancelJob() }
+ )
}
override fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View) {
@@ -308,7 +304,7 @@ constructor(
}
}
-internal interface BluetoothTileDialogCallback {
+interface BluetoothTileDialogCallback {
fun onDeviceItemGearClicked(deviceItem: DeviceItem, view: View)
fun onSeeAllClicked(view: View)
fun onPairNewDeviceClicked(view: View)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
index 1c9be0f105b2..f13ecf3e91b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactory.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.media.AudioManager
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.flags.Flags
import com.android.systemui.res.R
private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
@@ -36,6 +37,7 @@ private val actionAccessibilityLabelDisconnect =
/** Factories to create different types of Bluetooth device items from CachedBluetoothDevice. */
internal abstract class DeviceItemFactory {
abstract fun isFilterMatched(
+ context: Context,
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager?
): Boolean
@@ -45,6 +47,7 @@ internal abstract class DeviceItemFactory {
internal class ActiveMediaDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
+ context: Context,
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager?
): Boolean {
@@ -71,6 +74,7 @@ internal class ActiveMediaDeviceItemFactory : DeviceItemFactory() {
internal class AvailableMediaDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
+ context: Context,
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager?
): Boolean {
@@ -99,10 +103,18 @@ internal class AvailableMediaDeviceItemFactory : DeviceItemFactory() {
internal class ConnectedDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
+ context: Context,
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager?
): Boolean {
- return BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+ return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
+ !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
+ context,
+ cachedDevice.getDevice()
+ ) && BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+ } else {
+ BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, audioManager)
+ }
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
@@ -125,10 +137,18 @@ internal class ConnectedDeviceItemFactory : DeviceItemFactory() {
internal class SavedDeviceItemFactory : DeviceItemFactory() {
override fun isFilterMatched(
+ context: Context,
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager?
): Boolean {
- return cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+ return if (Flags.enableHideExclusivelyManagedBluetoothDevice()) {
+ !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
+ context,
+ cachedDevice.getDevice()
+ ) && cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+ } else {
+ cachedDevice.bondState == BluetoothDevice.BOND_BONDED && !cachedDevice.isConnected
+ }
}
override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
index fcd45a6431bb..1df496b17aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractor.kt
@@ -133,7 +133,7 @@ constructor(
bluetoothTileDialogRepository.cachedDevices
.mapNotNull { cachedDevice ->
deviceItemFactoryList
- .firstOrNull { it.isFilterMatched(cachedDevice, audioManager) }
+ .firstOrNull { it.isFilterMatched(context, cachedDevice, audioManager) }
?.create(context, cachedDevice)
}
.sort(displayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices)
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 000f3c09d84c..5f8b5ddc9de6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -221,7 +221,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
// If scene framework is enabled, set the scene container window to
// visible and let the touch "slip" into that window.
if (mSceneContainerFlags.isEnabled()) {
- mSceneInteractor.get().setVisible(true, "swipe down on launcher");
+ mSceneInteractor.get().onRemoteUserInteractionStarted("launcher swipe");
} else {
mShadeViewControllerLazy.get().startInputFocusTransfer();
}
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 a3021946713f..e60dff183148 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
@@ -49,6 +49,13 @@ constructor(
private val _isVisible = MutableStateFlow(true)
val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
+ /**
+ * Whether there's an ongoing remotely-initiated user interaction.
+ *
+ * For more information see the logic in `SceneInteractor` that mutates this.
+ */
+ val isRemoteUserInteractionOngoing = MutableStateFlow(false)
+
private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey)
private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
val transitionState: StateFlow<ObservableTransitionState> =
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractor.kt
new file mode 100644
index 000000000000..36350f8af455
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/PanelExpansionInteractor.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.shade.data.repository.ShadeRepository
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class PanelExpansionInteractor
+@Inject
+constructor(
+ sceneInteractor: SceneInteractor,
+ shadeRepository: ShadeRepository,
+) {
+
+ /**
+ * The amount by which the "panel" has been expanded (`0` when fully collapsed, `1` when fully
+ * expanded).
+ *
+ * This is a legacy concept from the time when the "panel" included the notification/QS shades
+ * as well as the keyguard (lockscreen and bouncer). This value is meant only for
+ * backwards-compatibility and should not be consumed by newer code.
+ */
+ @Deprecated("Use SceneInteractor.currentScene instead.")
+ val legacyPanelExpansion: Flow<Float> =
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor.transitionState.flatMapLatest { state ->
+ when (state) {
+ is ObservableTransitionState.Idle ->
+ flowOf(
+ if (state.scene != SceneKey.Gone) {
+ // When resting on a non-Gone scene, the panel is fully expanded.
+ 1f
+ } else {
+ // When resting on the Gone scene, the panel is considered fully
+ // collapsed.
+ 0f
+ }
+ )
+ is ObservableTransitionState.Transition ->
+ when {
+ state.fromScene == SceneKey.Gone ->
+ if (state.toScene.isExpandable()) {
+ // Moving from Gone to a scene that can animate-expand has a
+ // panel
+ // expansion
+ // that tracks with the transition.
+ state.progress
+ } else {
+ // Moving from Gone to a scene that doesn't animate-expand
+ // immediately makes
+ // the panel fully expanded.
+ flowOf(1f)
+ }
+ state.toScene == SceneKey.Gone ->
+ if (state.fromScene.isExpandable()) {
+ // Moving to Gone from a scene that can animate-expand has a
+ // panel
+ // expansion
+ // that tracks with the transition.
+ state.progress.map { 1 - it }
+ } else {
+ // Moving to Gone from a scene that doesn't animate-expand
+ // immediately makes
+ // the panel fully collapsed.
+ flowOf(0f)
+ }
+ else -> flowOf(1f)
+ }
+ }
+ }
+ } else {
+ shadeRepository.legacyShadeExpansion
+ }
+
+ private fun SceneKey.isExpandable(): Boolean {
+ return this == SceneKey.Shade || this == SceneKey.QuickSettings
+ }
+}
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 0add4443fa7a..6b7c672fbfe0 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
@@ -31,6 +31,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -121,7 +122,21 @@ constructor(
)
/** Whether the scene container is visible. */
- val isVisible: StateFlow<Boolean> = repository.isVisible
+ val isVisible: StateFlow<Boolean> =
+ combine(
+ repository.isVisible,
+ repository.isRemoteUserInteractionOngoing,
+ ) { isVisible, isRemoteUserInteractionOngoing ->
+ isVisibleInternal(
+ raw = isVisible,
+ isRemoteUserInteractionOngoing = isRemoteUserInteractionOngoing,
+ )
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = isVisibleInternal()
+ )
/**
* Returns the keys of all scenes in the container.
@@ -164,7 +179,14 @@ constructor(
repository.changeScene(toScene, transitionKey)
}
- /** Sets the visibility of the container. */
+ /**
+ * Sets the visibility of the container.
+ *
+ * Please do not call this from outside of the scene framework. If you are trying to force the
+ * visibility to visible or invisible, prefer making changes to the existing caller of this
+ * method or to upstream state used to calculate [isVisible]; for an example of the latter,
+ * please see [onRemoteUserInteractionStarted] and [onUserInteractionFinished].
+ */
fun setVisible(isVisible: Boolean, loggingReason: String) {
val wasVisible = repository.isVisible.value
if (wasVisible == isVisible) {
@@ -180,6 +202,31 @@ constructor(
}
/**
+ * Notifies that a remote user interaction has begun.
+ *
+ * This is a user interaction that originates outside of the UI of the scene container and
+ * possibly outside of the System UI process itself.
+ *
+ * As an example, consider the dragging that can happen in the launcher that expands the shade.
+ * This is a user interaction that begins remotely (as it starts in the launcher process) and is
+ * then rerouted by window manager to System UI. While the user interaction definitely continues
+ * within the System UI process and code, it also originates remotely.
+ */
+ fun onRemoteUserInteractionStarted(loggingReason: String) {
+ logger.logRemoteUserInteractionStarted(loggingReason)
+ repository.isRemoteUserInteractionOngoing.value = true
+ }
+
+ /**
+ * Notifies that the current user interaction (internally or remotely started, see
+ * [onRemoteUserInteractionStarted]) has finished.
+ */
+ fun onUserInteractionFinished() {
+ logger.logUserInteractionFinished()
+ repository.isRemoteUserInteractionOngoing.value = false
+ }
+
+ /**
* Binds the given flow so the system remembers it.
*
* Note that you must call is with `null` when the UI is done or risk a memory leak.
@@ -187,4 +234,11 @@ constructor(
fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
repository.setTransitionState(transitionState)
}
+
+ private fun isVisibleInternal(
+ raw: Boolean = repository.isVisible.value,
+ isRemoteUserInteractionOngoing: Boolean = repository.isRemoteUserInteractionOngoing.value,
+ ): Boolean {
+ return raw || isRemoteUserInteractionOngoing
+ }
}
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 b642d38289fe..034f87f4c72f 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
@@ -26,6 +26,7 @@ import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorActual
+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.DisplayId
@@ -34,6 +35,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.model.SceneContainerPlugin
import com.android.systemui.model.SysUiState
import com.android.systemui.model.updateFlags
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.FalsingManager.FalsingBeliefListener
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
@@ -53,6 +56,7 @@ import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -82,6 +86,7 @@ constructor(
@DisplayId private val displayId: Int,
private val sceneLogger: SceneLogger,
@FalsingCollectorActual private val falsingCollector: FalsingCollector,
+ private val falsingManager: FalsingManager,
private val powerInteractor: PowerInteractor,
private val simBouncerInteractor: Lazy<SimBouncerInteractor>,
private val authenticationInteractor: Lazy<AuthenticationInteractor>,
@@ -98,6 +103,7 @@ constructor(
automaticallySwitchScenes()
hydrateSystemUiState()
collectFalsingSignals()
+ respondToFalsingDetections()
hydrateWindowFocus()
hydrateInteractionState()
} else {
@@ -376,6 +382,18 @@ constructor(
}
}
+ /** Switches to the lockscreen when falsing is detected. */
+ private fun respondToFalsingDetections() {
+ applicationScope.launch {
+ conflatedCallbackFlow {
+ val listener = FalsingBeliefListener { trySend(Unit) }
+ falsingManager.addFalsingBeliefListener(listener)
+ awaitClose { falsingManager.removeFalsingBeliefListener(listener) }
+ }
+ .collect { switchToScene(SceneKey.Lockscreen, "Falsing detected.") }
+ }
+ }
+
/** Keeps the focus state of the window view up-to-date. */
private fun hydrateWindowFocus() {
applicationScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index d59fcff34796..cbf7b3e7a971 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -95,6 +95,26 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer:
)
}
+ fun logRemoteUserInteractionStarted(
+ reason: String,
+ ) {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = { str1 = reason },
+ messagePrinter = { "remote user interaction started, reason: $str3" },
+ )
+ }
+
+ fun logUserInteractionFinished() {
+ logBuffer.log(
+ tag = TAG,
+ level = LogLevel.INFO,
+ messageInitializer = {},
+ messagePrinter = { "user interaction finished" },
+ )
+ }
+
companion object {
private const val TAG = "SceneFramework"
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 45b6f65d5d67..ee76c0582b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -16,11 +16,9 @@
package com.android.systemui.scene.ui.view
-import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
-import android.widget.FrameLayout
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
@@ -39,7 +37,6 @@ import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
-import java.time.Instant
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
@@ -98,7 +95,7 @@ object SceneWindowRootViewBinder {
)
val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
- view.addView(createVisibilityToggleView(legacyView))
+ legacyView.isVisible = false
// This moves the SharedNotificationContainer to the WindowRootView just after
// the SceneContainerView. This SharedNotificationContainer should contain NSSL
@@ -123,29 +120,4 @@ object SceneWindowRootViewBinder {
}
}
}
-
- private var clickCount = 0
- private var lastClick = Instant.now()
-
- /**
- * A temporary UI to toggle on/off the visibility of the given [otherView]. It is toggled by
- * tapping 5 times in quick succession on the device camera (top center).
- */
- // TODO(b/291321285): Remove this when the Flexiglass UI is mature enough to turn off legacy
- // SysUI altogether.
- private fun createVisibilityToggleView(otherView: View): View {
- val toggleView = View(otherView.context)
- otherView.isVisible = false
- toggleView.layoutParams = FrameLayout.LayoutParams(200, 200, Gravity.CENTER_HORIZONTAL)
- toggleView.setOnClickListener {
- val now = Instant.now()
- clickCount = if (now.minusSeconds(2) > lastClick) 1 else clickCount + 1
- if (clickCount == 5) {
- otherView.isVisible = !otherView.isVisible
- clickCount = 0
- }
- lastClick = now
- }
- return toggleView
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 4cd3baa7a52e..91861aa5c29a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -68,6 +68,12 @@ constructor(
fun onMotionEvent(event: MotionEvent) {
powerInteractor.onUserTouch()
falsingInteractor.onTouchEvent(event)
+ if (
+ event.actionMasked == MotionEvent.ACTION_UP ||
+ event.actionMasked == MotionEvent.ACTION_CANCEL
+ ) {
+ sceneInteractor.onUserInteractionFinished()
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index fb5339df7212..1f9853b17a28 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -16,6 +16,8 @@
package com.android.systemui.screenshot
+import android.app.ActivityOptions
+import android.app.ExitTransitionCoordinator
import android.content.Context
import android.content.Intent
import android.os.Bundle
@@ -23,6 +25,7 @@ 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
@@ -64,18 +67,18 @@ constructor(
*/
fun launchIntentAsync(
intent: Intent,
- options: Bundle?,
+ transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
user: UserHandle,
overrideTransition: Boolean,
) {
applicationScope.launch("$TAG#launchIntentAsync") {
- launchIntent(intent, options, user, overrideTransition)
+ launchIntent(intent, transition, user, overrideTransition)
}
}
suspend fun launchIntent(
intent: Intent,
- options: Bundle?,
+ transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
user: UserHandle,
overrideTransition: Boolean,
) {
@@ -87,11 +90,14 @@ constructor(
} else {
dismissKeyguard()
}
+ transition?.second?.startExit()
if (user == myUserHandle()) {
- withContext(mainDispatcher) { context.startActivity(intent, options) }
+ withContext(mainDispatcher) {
+ context.startActivity(intent, transition?.first?.toBundle())
+ }
} else {
- launchCrossProfileIntent(user, intent, options)
+ launchCrossProfileIntent(user, intent, transition?.first?.toBundle())
}
if (overrideTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
index 0588fe2bba82..30f5e8b50bf4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/OverlayActionChip.java
@@ -18,6 +18,7 @@ package com.android.systemui.screenshot;
import static java.util.Objects.requireNonNull;
+import android.app.ActivityOptions;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.content.Context;
@@ -100,7 +101,7 @@ public class OverlayActionChip extends FrameLayout {
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
options.setPendingIntentBackgroundActivityStartMode(
- BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
intent.send(options.toBundle());
finisher.run();
} catch (PendingIntent.CanceledException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 31086d85f747..bbf7ed529220 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -16,40 +16,29 @@
package com.android.systemui.screenshot;
-import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS;
import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE;
import static com.android.systemui.screenshot.LogConfig.logTag;
import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType;
-import android.app.ActivityTaskManager;
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.content.pm.UserInfo;
-import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Process;
-import android.os.RemoteException;
import android.os.UserHandle;
-import android.os.UserManager;
import android.provider.DeviceConfig;
-import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.systemui.res.R;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.google.common.util.concurrent.ListenableFuture;
@@ -60,7 +49,6 @@ import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
-import java.util.function.Supplier;
/**
* An AsyncTask that saves an image to the media store in the background.
@@ -81,7 +69,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
private String mScreenshotId;
private final Random mRandom = new Random();
- private final Supplier<ActionTransition> mSharedElementTransition;
private final ImageExporter mImageExporter;
private long mImageTime;
@@ -91,7 +78,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
ImageExporter exporter,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotController.SaveImageInBackgroundData data,
- Supplier<ActionTransition> sharedElementTransition,
ScreenshotNotificationSmartActionsProvider
screenshotNotificationSmartActionsProvider
) {
@@ -100,7 +86,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mScreenshotSmartActions = screenshotSmartActions;
mImageData = new ScreenshotController.SavedImageData();
mQuickShareData = new ScreenshotController.QuickShareData();
- mSharedElementTransition = sharedElementTransition;
mImageExporter = exporter;
// Prepare all the output metadata
@@ -176,12 +161,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mImageData.uri = uri;
mImageData.owner = mParams.owner;
mImageData.smartActions = smartActions;
- mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri,
- smartActionsEnabled);
- mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri,
- smartActionsEnabled);
- mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri,
- smartActionsEnabled);
mImageData.quickShareAction = createQuickShareAction(
mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image,
mParams.owner);
@@ -234,164 +213,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mParams.clearImage();
}
- /**
- * Assumes that the action intent is sent immediately after being supplied.
- */
- @VisibleForTesting
- Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri,
- boolean smartActionsEnabled) {
- return () -> {
- ActionTransition transition = mSharedElementTransition.get();
-
- // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
- // order to do some common work like dismissing the keyguard and sending
- // closeSystemWindows
-
- // Create a share intent, this will always go through the chooser activity first
- // which should not trigger auto-enter PiP
- Intent sharingIntent = new Intent(Intent.ACTION_SEND);
- sharingIntent.setDataAndType(uri, "image/png");
- sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
- // Include URI in ClipData also, so that grantPermission picks it up.
- // We don't use setData here because some apps interpret this as "to:".
- ClipData clipdata = new ClipData(new ClipDescription("content",
- new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
- new ClipData.Item(uri));
- sharingIntent.setClipData(clipdata);
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(mImageTime));
- sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
-
- // Make sure pending intents for the system user are still unique across users
- // by setting the (otherwise unused) request code to the current user id.
- int requestCode = context.getUserId();
-
- Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
-
- // cancel current pending intent (if any) since clipData isn't used for matching
- PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
- context, 0, sharingChooserIntent,
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
- transition.bundle, UserHandle.CURRENT);
-
- // Create a share action for the notification
- PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
- new Intent(context, ActionProxyReceiver.class)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
- .putExtra(ScreenshotController.EXTRA_DISALLOW_ENTER_PIP, true)
- .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
- .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
- smartActionsEnabled)
- .setAction(Intent.ACTION_SEND)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
- UserHandle.SYSTEM);
-
- Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
- Icon.createWithResource(r, R.drawable.ic_screenshot_share),
- r.getString(com.android.internal.R.string.share), shareAction);
-
- transition.action = shareActionBuilder.build();
- return transition;
- };
- }
-
- @VisibleForTesting
- Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri,
- boolean smartActionsEnabled) {
- return () -> {
- ActionTransition transition = mSharedElementTransition.get();
- // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
- // order to do some common work like dismissing the keyguard and sending
- // closeSystemWindows
-
- // Create an edit intent, if a specific package is provided as the editor, then
- // launch that directly
- String editorPackage = context.getString(R.string.config_screenshotEditor);
- Intent editIntent = new Intent(Intent.ACTION_EDIT);
- if (!TextUtils.isEmpty(editorPackage)) {
- editIntent.setComponent(ComponentName.unflattenFromString(editorPackage));
- }
- editIntent.setDataAndType(uri, "image/png");
- editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-
- PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
- context, 0, editIntent, PendingIntent.FLAG_IMMUTABLE,
- transition.bundle, UserHandle.CURRENT);
-
- // Make sure pending intents for the system user are still unique across users
- // by setting the (otherwise unused) request code to the current user id.
- int requestCode = mContext.getUserId();
-
- // Create an edit action
- PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
- new Intent(context, ActionProxyReceiver.class)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
- .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
- .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
- smartActionsEnabled)
- .putExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, true)
- .setAction(Intent.ACTION_EDIT)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
- UserHandle.SYSTEM);
- Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
- Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
- r.getString(com.android.internal.R.string.screenshot_edit), editAction);
-
- transition.action = editActionBuilder.build();
- return transition;
- };
- }
-
- @VisibleForTesting
- Notification.Action createDeleteAction(Context context, Resources r, Uri uri,
- boolean smartActionsEnabled) {
- // Make sure pending intents for the system user are still unique across users
- // by setting the (otherwise unused) request code to the current user id.
- int requestCode = mContext.getUserId();
-
- // Create a delete action for the notification
- PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
- new Intent(context, DeleteScreenshotReceiver.class)
- .putExtra(ScreenshotController.SCREENSHOT_URI_ID, uri.toString())
- .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
- .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
- smartActionsEnabled)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT
- | PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_IMMUTABLE);
- Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
- Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
- r.getString(com.android.internal.R.string.delete), deleteAction);
-
- return deleteActionBuilder.build();
- }
-
- private UserHandle getUserHandleOfForegroundApplication(Context context) {
- UserManager manager = UserManager.get(context);
- int result;
- // This logic matches
- // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
- try {
- result = ActivityTaskManager.getService().getLastResumedActivityUserId();
- } catch (RemoteException e) {
- if (DEBUG_ACTIONS) {
- Log.d(TAG, "Failed to get UserHandle of foreground app: ", e);
- }
- result = context.getUserId();
- }
- UserInfo userInfo = manager.getUserInfo(result);
- return userInfo.getUserHandle();
- }
-
private List<Notification.Action> buildSmartActions(
List<Notification.Action> actions, Context context) {
List<Notification.Action> broadcastActions = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 21a08a9a4980..ee3e7ba9e8b3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -39,7 +39,6 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ExitTransitionCoordinator;
-import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;
import android.app.ICompatCameraControlCallback;
import android.app.Notification;
import android.app.assist.AssistContent;
@@ -54,7 +53,6 @@ import android.graphics.Insets;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.net.Uri;
-import android.os.Bundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -87,13 +85,12 @@ import com.android.internal.app.ChooserActivity;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.applications.InterestingConfigChanges;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
-import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
+import com.android.systemui.res.R;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
import com.android.systemui.util.Assert;
@@ -111,7 +108,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Consumer;
-import java.util.function.Supplier;
import javax.inject.Provider;
@@ -171,31 +167,16 @@ public class ScreenshotController {
*/
static class SavedImageData {
public Uri uri;
- public Supplier<ActionTransition> shareTransition;
- public Supplier<ActionTransition> editTransition;
- public Notification.Action deleteAction;
public List<Notification.Action> smartActions;
public Notification.Action quickShareAction;
public UserHandle owner;
public String subject; // Title for sharing
/**
- * POD for shared element transition.
- */
- static class ActionTransition {
- public Bundle bundle;
- public Notification.Action action;
- public Runnable onCancelRunnable;
- }
-
- /**
* Used to reset the return data on error
*/
public void reset() {
uri = null;
- shareTransition = null;
- editTransition = null;
- deleteAction = null;
smartActions = null;
quickShareAction = null;
subject = null;
@@ -469,8 +450,9 @@ public class ScreenshotController {
if (!shouldShowUi()) {
saveScreenshotInWorkerThread(
- screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady,
- (ignored) -> {});
+ screenshot.getUserHandle(), finisher, this::logSuccessOnActionsReady,
+ (ignored) -> {
+ });
return;
}
@@ -489,7 +471,7 @@ public class ScreenshotController {
if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
if (screenshot.getScreenBounds() != null
&& aspectRatiosMatch(screenshot.getBitmap(), screenshot.getInsets(),
- screenshot.getScreenBounds())) {
+ screenshot.getScreenBounds())) {
showFlash = false;
} else {
showFlash = true;
@@ -643,6 +625,12 @@ public class ScreenshotController {
}
@Override
+ public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) {
+ mActionExecutor.launchIntentAsync(
+ intent, createWindowTransition(), owner, overrideTransition);
+ }
+
+ @Override
public void onDismiss() {
finishDismiss();
}
@@ -652,7 +640,7 @@ public class ScreenshotController {
// TODO(159460485): Remove this when focus is handled properly in the system
setWindowFocusable(false);
}
- }, mActionExecutor, mFlags);
+ }, mFlags);
mScreenshotView.setDefaultDisplay(mDisplayId);
mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
@@ -964,6 +952,35 @@ public class ScreenshotController {
mScreenshotAnimation.start();
}
+ /**
+ * 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 Pair<ActivityOptions, ExitTransitionCoordinator> createWindowTransition() {
+ ExitTransitionCoordinator.ExitTransitionCallbacks callbacks =
+ new ExitTransitionCoordinator.ExitTransitionCallbacks() {
+ @Override
+ public boolean isReturnTransitionAllowed() {
+ return false;
+ }
+
+ @Override
+ public void hideSharedElements() {
+ finishDismiss();
+ }
+
+ @Override
+ public void onFinish() {
+ }
+ };
+ Pair<ActivityOptions, ExitTransitionCoordinator> transition =
+ ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
+ Pair.create(mScreenshotView.getScreenshotPreview(),
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
+
+ return transition;
+ }
+
/** Reset screenshot view and then call onCompleteRunnable */
private void finishDismiss() {
Log.d(TAG, "finishDismiss");
@@ -1011,7 +1028,7 @@ public class ScreenshotController {
}
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mFlags, mImageExporter,
- mScreenshotSmartActions, data, getActionTransitionSupplier(),
+ mScreenshotSmartActions, data,
mScreenshotNotificationSmartActionsProvider);
mSaveInBgTask.execute();
}
@@ -1078,26 +1095,6 @@ public class ScreenshotController {
}
/**
- * Supplies the necessary bits for the shared element transition to share sheet.
- * Note that once supplied, the action intent to share must be sent immediately after.
- */
- private Supplier<ActionTransition> getActionTransitionSupplier() {
- return () -> {
- Pair<ActivityOptions, ExitTransitionCoordinator> transition =
- ActivityOptions.startSharedElementAnimation(
- mWindow, new ScreenshotExitTransitionCallbacksSupplier(true).get(),
- null, Pair.create(mScreenshotView.getScreenshotPreview(),
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
- transition.second.startExit();
-
- ActionTransition supply = new ActionTransition();
- supply.bundle = transition.first.toBundle();
- supply.onCancelRunnable = () -> ActivityOptions.stopSharedElementAnimation(mWindow);
- return supply;
- };
- }
-
- /**
* Logs success/failure of the screenshot saving task, and shows an error if it failed.
*/
private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) {
@@ -1186,36 +1183,6 @@ public class ScreenshotController {
return matchWithinTolerance;
}
- private class ScreenshotExitTransitionCallbacksSupplier implements
- Supplier<ExitTransitionCallbacks> {
- final boolean mDismissOnHideSharedElements;
-
- ScreenshotExitTransitionCallbacksSupplier(boolean dismissOnHideSharedElements) {
- mDismissOnHideSharedElements = dismissOnHideSharedElements;
- }
-
- @Override
- public ExitTransitionCallbacks get() {
- return new ExitTransitionCallbacks() {
- @Override
- public boolean isReturnTransitionAllowed() {
- return false;
- }
-
- @Override
- public void hideSharedElements() {
- if (mDismissOnHideSharedElements) {
- finishDismiss();
- }
- }
-
- @Override
- public void onFinish() {
- }
- };
- }
- }
-
/** Injectable factory to create screenshot controller instances for a specific display. */
@AssistedFactory
public interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 31c928406aac..be30a1576b21 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -57,6 +57,7 @@ import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -86,9 +87,9 @@ import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.res.R;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.res.R;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.QuickStepContract;
@@ -104,6 +105,8 @@ public class ScreenshotView extends FrameLayout implements
interface ScreenshotViewCallback {
void onUserInteraction();
+ void onAction(Intent intent, UserHandle owner, boolean overrideTransition);
+
void onDismiss();
/** DOWN motion event was observed outside of the touchable areas of this view. */
@@ -166,7 +169,6 @@ public class ScreenshotView extends FrameLayout implements
private final InteractionJankMonitor mInteractionJankMonitor;
private long mDefaultTimeoutOfTimeoutHandler;
- private ActionIntentExecutor mActionExecutor;
private FeatureFlags mFlags;
private final Bundle mInteractiveBroadcastOption;
@@ -430,11 +432,9 @@ public class ScreenshotView extends FrameLayout implements
* Note: must be called before any other (non-constructor) method or null pointer exceptions
* may occur.
*/
- void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks,
- ActionIntentExecutor actionExecutor, FeatureFlags flags) {
+ void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, FeatureFlags flags) {
mUiEventLogger = uiEventLogger;
mCallbacks = callbacks;
- mActionExecutor = actionExecutor;
mFlags = flags;
}
@@ -800,24 +800,21 @@ public class ScreenshotView extends FrameLayout implements
shareIntent = ActionIntentCreator.INSTANCE.createShareWithSubject(
imageData.uri, imageData.subject);
}
- mActionExecutor.launchIntentAsync(shareIntent,
- imageData.shareTransition.get().bundle,
- imageData.owner, false);
+ mCallbacks.onAction(shareIntent, imageData.owner, false);
+
});
mEditChip.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
prepareSharedTransition();
- mActionExecutor.launchIntentAsync(
+ mCallbacks.onAction(
ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
- imageData.editTransition.get().bundle,
imageData.owner, true);
});
mScreenshotPreview.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
prepareSharedTransition();
- mActionExecutor.launchIntentAsync(
+ mCallbacks.onAction(
ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
- imageData.editTransition.get().bundle,
imageData.owner, true);
});
if (mQuickShareChip != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 7068f5fcc32b..fd3c480a334f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1201,7 +1201,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
// Primary bouncer->Gone (ensures lockscreen content is not visible on successful auth)
if (!migrateClocksToBlueprint()) {
collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha(),
- setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+ setTransitionAlpha(mNotificationStackScrollLayoutController,
+ /* excludeNotifications=*/ true), mMainDispatcher);
+ collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getNotificationAlpha(),
+ (Float alpha) -> {
+ mNotificationStackScrollLayoutController.setMaxAlphaForExpansion(alpha);
+ }, mMainDispatcher);
}
}
@@ -1539,6 +1544,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@Override
public void setOpenCloseListener(OpenCloseListener openCloseListener) {
+ SceneContainerFlag.assertInLegacyMode();
mOpenCloseListener = openCloseListener;
}
@@ -3068,7 +3074,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
private void onClosingFinished() {
- mOpenCloseListener.onClosingFinished();
+ if (mOpenCloseListener != null) {
+ mOpenCloseListener.onClosingFinished();
+ }
setClosingWithAlphaFadeout(false);
mMediaHierarchyManager.closeGuts();
}
@@ -4699,7 +4707,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
if (mSplitShadeEnabled && !isKeyguardShowing()) {
mQsController.setExpandImmediate(true);
}
- mOpenCloseListener.onOpenStarted();
+ if (mOpenCloseListener != null) {
+ mOpenCloseListener.onOpenStarted();
+ }
}
if (state == STATE_CLOSED) {
mQsController.setExpandImmediate(false);
@@ -4724,9 +4734,17 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private Consumer<Float> setTransitionAlpha(
NotificationStackScrollLayoutController stackScroller) {
+ return setTransitionAlpha(stackScroller, /* excludeNotifications= */ false);
+ }
+
+ private Consumer<Float> setTransitionAlpha(
+ NotificationStackScrollLayoutController stackScroller,
+ boolean excludeNotifications) {
return (Float alpha) -> {
mKeyguardStatusViewController.setAlpha(alpha);
- stackScroller.setMaxAlphaForExpansion(alpha);
+ if (!excludeNotifications) {
+ stackScroller.setMaxAlphaForExpansion(alpha);
+ }
if (keyguardBottomAreaRefactor()) {
mKeyguardInteractor.setAlpha(alpha);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index e7f970050033..a5c055350999 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -282,12 +282,6 @@ public class QuickSettingsController implements Dumpable {
/** The duration of the notification bounds animation. */
private long mNotificationBoundsAnimationDuration;
- /** TODO(b/273591201): remove after bug resolved */
- private int mLastClippingTopBound;
- private int mLastNotificationsTopPadding;
- private int mLastNotificationsClippingTopBound;
- private int mLastNotificationsClippingTopBoundNssl;
-
private final Region mInterceptRegion = new Region();
/** The end bounds of a clipping animation. */
private final Rect mClippingAnimationEndBounds = new Rect();
@@ -2141,14 +2135,6 @@ public class QuickSettingsController implements Dumpable {
ipw.println(mNotificationBoundsAnimationDelay);
ipw.print("mNotificationBoundsAnimationDuration=");
ipw.println(mNotificationBoundsAnimationDuration);
- ipw.print("mLastClippingTopBound=");
- ipw.println(mLastClippingTopBound);
- ipw.print("mLastNotificationsTopPadding=");
- ipw.println(mLastNotificationsTopPadding);
- ipw.print("mLastNotificationsClippingTopBound=");
- ipw.println(mLastNotificationsClippingTopBound);
- ipw.print("mLastNotificationsClippingTopBoundNssl=");
- ipw.println(mLastNotificationsClippingTopBoundNssl);
ipw.print("mInterceptRegion=");
ipw.println(mInterceptRegion);
ipw.print("mClippingAnimationEndBounds=");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index ea419127d7c1..e6555f2e0993 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -32,6 +32,7 @@ import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.dagger.ShadeTouchLog;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
@@ -98,6 +99,7 @@ public final class ShadeControllerImpl extends BaseShadeControllerImpl {
statusBarKeyguardViewManager,
notificationShadeWindowController,
assistManagerLazy);
+ SceneContainerFlag.assertInLegacyMode();
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 971507055873..84afbed51faa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
@@ -19,8 +19,11 @@ package com.android.systemui.shade.transition
import android.content.Context
import android.content.res.Configuration
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
+import com.android.systemui.scene.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.PanelState
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionStateManager
@@ -31,21 +34,26 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.SplitShadeStateController
+import dagger.Lazy
import java.io.PrintWriter
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/** Controls the shade expansion transition on non-lockscreen. */
@SysUISingleton
class ShadeTransitionController
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
configurationController: ConfigurationController,
shadeExpansionStateManager: ShadeExpansionStateManager,
dumpManager: DumpManager,
private val context: Context,
private val scrimShadeTransitionController: ScrimShadeTransitionController,
private val statusBarStateController: SysuiStatusBarStateController,
- private val splitShadeStateController: SplitShadeStateController
+ private val splitShadeStateController: SplitShadeStateController,
+ private val panelExpansionInteractor: Lazy<PanelExpansionInteractor>,
) {
lateinit var shadeViewController: ShadeViewController
@@ -63,11 +71,27 @@ constructor(
override fun onConfigChanged(newConfig: Configuration?) {
updateResources()
}
- })
- val currentState =
- shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
- onPanelExpansionChanged(currentState)
- shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
+ }
+ )
+ if (SceneContainerFlag.isEnabled) {
+ applicationScope.launch {
+ panelExpansionInteractor.get().legacyPanelExpansion.collect { panelExpansion ->
+ onPanelExpansionChanged(
+ ShadeExpansionChangeEvent(
+ fraction = panelExpansion,
+ expanded = panelExpansion > 0f,
+ tracking = true,
+ dragDownPxAmount = 0f,
+ )
+ )
+ }
+ }
+ } else {
+ val currentState =
+ shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
+ onPanelExpansionChanged(currentState)
+ shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
+ }
dumpManager.registerCriticalDumpable("ShadeTransitionController") { printWriter, _ ->
dump(printWriter)
}
@@ -98,7 +122,9 @@ constructor(
qs.isInitialized: ${this::qs.isInitialized}
npvc.isInitialized: ${this::shadeViewController.isInitialized}
nssl.isInitialized: ${this::notificationStackScrollLayoutController.isInitialized}
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
private fun isScreenUnlocked() =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
index 835225009110..a58ce4162ddc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
@@ -22,7 +22,9 @@ import android.util.AttributeSet
import android.view.View
import android.widget.FrameLayout
import android.widget.LinearLayout
+import com.android.settingslib.flags.Flags.newStatusBarIcons
import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.unified.BatteryColors
import com.android.systemui.res.R
import com.android.systemui.statusbar.events.BackgroundAnimatableView
@@ -39,8 +41,12 @@ class BatteryStatusChip @JvmOverloads constructor(context: Context, attrs: Attri
roundedContainer = requireViewById(R.id.rounded_container)
batteryMeterView = requireViewById(R.id.battery_meter_view)
batteryMeterView.setStaticColor(true)
- val primaryColor = context.resources.getColor(android.R.color.black, context.theme)
- batteryMeterView.updateColors(primaryColor, primaryColor, primaryColor)
+ if (newStatusBarIcons()) {
+ batteryMeterView.setUnifiedBatteryColors(BatteryColors.LightThemeColors)
+ } else {
+ val primaryColor = context.resources.getColor(android.R.color.black, context.theme)
+ batteryMeterView.updateColors(primaryColor, primaryColor, primaryColor)
+ }
updateResources()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index ca19f71bd391..bb8168335b60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -168,7 +168,7 @@ public class CommandQueue extends IStatusBar.Stub implements
private static final int MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 67 << MSG_SHIFT;
private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT;
private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT;
- private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT;
+ private static final int MSG_MOVE_FOCUSED_TASK_TO_FULLSCREEN = 70 << MSG_SHIFT;
private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
private static final int MSG_SHOW_MEDIA_OUTPUT_SWITCHER = 72 << MSG_SHIFT;
private static final int MSG_TOGGLE_TASKBAR = 73 << MSG_SHIFT;
@@ -498,9 +498,9 @@ public class CommandQueue extends IStatusBar.Stub implements
default void showRearDisplayDialog(int currentBaseState) {}
/**
- * @see IStatusBar#goToFullscreenFromSplit
+ * @see IStatusBar#moveFocusedTaskToFullscreen
*/
- default void goToFullscreenFromSplit() {}
+ default void moveFocusedTaskToFullscreen(int displayId) {}
/**
* @see IStatusBar#enterStageSplitFromRunningApp
@@ -1422,8 +1422,10 @@ public class CommandQueue extends IStatusBar.Stub implements
}
@Override
- public void goToFullscreenFromSplit() {
- mHandler.obtainMessage(MSG_GO_TO_FULLSCREEN_FROM_SPLIT).sendToTarget();
+ public void moveFocusedTaskToFullscreen(int displayId) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = displayId;
+ mHandler.obtainMessage(MSG_MOVE_FOCUSED_TASK_TO_FULLSCREEN, args).sendToTarget();
}
@Override
@@ -1897,11 +1899,14 @@ public class CommandQueue extends IStatusBar.Stub implements
mCallbacks.get(i).showRearDisplayDialog((Integer) msg.obj);
}
break;
- case MSG_GO_TO_FULLSCREEN_FROM_SPLIT:
+ case MSG_MOVE_FOCUSED_TASK_TO_FULLSCREEN: {
+ args = (SomeArgs) msg.obj;
+ int displayId = args.argi1;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).goToFullscreenFromSplit();
+ mCallbacks.get(i).moveFocusedTaskToFullscreen(displayId);
}
break;
+ }
case MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).enterStageSplitFromRunningApp((Boolean) msg.obj);
@@ -1927,13 +1932,14 @@ public class CommandQueue extends IStatusBar.Stub implements
mCallbacks.get(i).immersiveModeChanged(rootDisplayAreaId, isImmersiveMode);
}
break;
- case MSG_ENTER_DESKTOP:
+ case MSG_ENTER_DESKTOP: {
args = (SomeArgs) msg.obj;
int displayId = args.argi1;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).enterDesktop(displayId);
}
break;
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
index e582a01ea88b..dbcda418496e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiIcons.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.connectivity;
+import static com.android.settingslib.flags.Flags.newStatusBarIcons;
+
import com.android.settingslib.AccessibilityContentDescriptions;
import com.android.settingslib.R;
import com.android.settingslib.SignalIcon.IconGroup;
@@ -23,21 +25,52 @@ import com.android.settingslib.SignalIcon.IconGroup;
/** */
public class WifiIcons {
- public static final int[] WIFI_FULL_ICONS = {
- com.android.internal.R.drawable.ic_wifi_signal_0,
- com.android.internal.R.drawable.ic_wifi_signal_1,
- com.android.internal.R.drawable.ic_wifi_signal_2,
- com.android.internal.R.drawable.ic_wifi_signal_3,
- com.android.internal.R.drawable.ic_wifi_signal_4
- };
+ public static final int[] WIFI_FULL_ICONS = getIconsBasedOnFlag();
- public static final int[] WIFI_NO_INTERNET_ICONS = {
- R.drawable.ic_no_internet_wifi_signal_0,
- R.drawable.ic_no_internet_wifi_signal_1,
- R.drawable.ic_no_internet_wifi_signal_2,
- R.drawable.ic_no_internet_wifi_signal_3,
- R.drawable.ic_no_internet_wifi_signal_4
- };
+ /**
+ * Check the aconfig flag to decide on which icons to use. Can be removed once the flag is gone
+ */
+ private static int[] getIconsBasedOnFlag() {
+ if (newStatusBarIcons()) {
+ return new int[] {
+ R.drawable.ic_wifi_0,
+ R.drawable.ic_wifi_1,
+ R.drawable.ic_wifi_2,
+ R.drawable.ic_wifi_3,
+ R.drawable.ic_wifi_4
+ };
+ } else {
+ return new int[] {
+ com.android.internal.R.drawable.ic_wifi_signal_0,
+ com.android.internal.R.drawable.ic_wifi_signal_1,
+ com.android.internal.R.drawable.ic_wifi_signal_2,
+ com.android.internal.R.drawable.ic_wifi_signal_3,
+ com.android.internal.R.drawable.ic_wifi_signal_4
+ };
+ }
+ }
+
+ public static final int[] WIFI_NO_INTERNET_ICONS = getErrorIconsBasedOnFlag();
+
+ private static int [] getErrorIconsBasedOnFlag() {
+ if (newStatusBarIcons()) {
+ return new int[] {
+ R.drawable.ic_wifi_0_error,
+ R.drawable.ic_wifi_1_error,
+ R.drawable.ic_wifi_2_error,
+ R.drawable.ic_wifi_3_error,
+ R.drawable.ic_wifi_4_error
+ };
+ } else {
+ return new int[] {
+ R.drawable.ic_no_internet_wifi_signal_0,
+ R.drawable.ic_no_internet_wifi_signal_1,
+ R.drawable.ic_no_internet_wifi_signal_2,
+ R.drawable.ic_no_internet_wifi_signal_3,
+ R.drawable.ic_no_internet_wifi_signal_4
+ };
+ }
+ }
public static final int[][] QS_WIFI_SIGNAL_STRENGTH = {
WIFI_NO_INTERNET_ICONS,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 599600d61976..0fd05550dbe3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -39,7 +39,6 @@ import android.view.ViewGroup
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.settingslib.Utils
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -54,6 +53,7 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN
@@ -68,11 +68,14 @@ import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import java.time.Instant
+import java.util.Deque
+import java.util.LinkedList
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Named
+
/** Controller for managing the smartspace view on the lockscreen */
@SysUISingleton
class LockscreenSmartspaceController
@@ -106,6 +109,8 @@ constructor(
) : Dumpable {
companion object {
private const val TAG = "LockscreenSmartspaceController"
+
+ private const val MAX_RECENT_SMARTSPACE_DATA_FOR_DUMP = 5
}
private var session: SmartspaceSession? = null
@@ -114,6 +119,9 @@ constructor(
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null)
+ // This stores recently received Smartspace pushes to be included in dumpsys.
+ private val recentSmartspaceData: Deque<List<SmartspaceTarget>> = LinkedList()
+
// Smartspace can be used on multiple displays, such as when the user casts their screen
private var smartspaceViews = mutableSetOf<SmartspaceView>()
private var regionSamplers =
@@ -173,6 +181,7 @@ constructor(
// The weather data plugin takes unfiltered targets and performs the filtering internally.
weatherPlugin?.onTargetsAvailable(targets)
+
val now = Instant.ofEpochMilli(systemClock.currentTimeMillis())
val weatherTarget = targets.find { t ->
t.featureType == SmartspaceTarget.FEATURE_WEATHER &&
@@ -201,6 +210,14 @@ constructor(
}
val filteredTargets = targets.filter(::filterSmartspaceTarget)
+
+ synchronized(recentSmartspaceData) {
+ recentSmartspaceData.offerLast(filteredTargets)
+ if (recentSmartspaceData.size > MAX_RECENT_SMARTSPACE_DATA_FOR_DUMP) {
+ recentSmartspaceData.pollFirst()
+ }
+ }
+
plugin?.onTargetsAvailable(filteredTargets)
}
@@ -588,9 +605,26 @@ constructor(
return null
}
- override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run {
- printCollection("Region Samplers", regionSamplers.values) {
- it.dump(this)
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.asIndenting().run {
+ printCollection("Region Samplers", regionSamplers.values) {
+ it.dump(this)
+ }
+ }
+
+ pw.println("Recent BC Smartspace Targets (most recent first)")
+ synchronized(recentSmartspaceData) {
+ if (recentSmartspaceData.size === 0) {
+ pw.println(" No data\n")
+ return
+ }
+ recentSmartspaceData.descendingIterator().forEachRemaining { smartspaceTargets ->
+ pw.println(" Number of targets: ${smartspaceTargets.size}")
+ for (target in smartspaceTargets) {
+ pw.println(" $target")
+ }
+ pw.println()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt
index 12de339871bb..4a7b7ca51ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinator.kt
@@ -54,7 +54,7 @@ class RowAlertTimeCoordinator @Inject constructor() : Coordinator {
}
private fun GroupEntry.calculateLatestAlertTime(): Long {
- val lastChildAlertedTime = children.maxOf { it.lastAudiblyAlertedMs }
+ val lastChildAlertedTime = children.maxOfOrNull { it.lastAudiblyAlertedMs } ?: 0
val summaryAlertedTime = checkNotNull(summary).lastAudiblyAlertedMs
return max(lastChildAlertedTime, summaryAlertedTime)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 510086d4892b..dc9eeb35565a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -191,11 +191,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
public boolean shouldBubbleUp(NotificationEntry entry) {
final StatusBarNotification sbn = entry.getSbn();
- if (!canAlertCommon(entry, true)) {
+ if (!canAlertCommon(entry, false)) {
return false;
}
- if (!canAlertAwakeCommon(entry, true)) {
+ if (!canAlertAwakeCommon(entry, false)) {
return false;
}
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 052e35c44bbe..b4c88c5f9e79 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
@@ -79,6 +79,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.isActive
/** View-model for the shared notification container, used by both the shade and keyguard spaces */
@@ -89,7 +90,7 @@ constructor(
private val interactor: SharedNotificationContainerInteractor,
@Application applicationScope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
communalInteractor: CommunalInteractor,
private val alternateBouncerToGoneTransitionViewModel:
@@ -222,19 +223,60 @@ constructor(
initialValue = false,
)
+ /**
+ * Fade in if the user swipes the shade back up, not if collapsed by going to AOD. This is
+ * needed due to the lack of a SHADE state with existing keyguard transitions.
+ */
+ private fun awaitCollapse(): Flow<Boolean> {
+ var aodTransitionIsComplete = true
+ return combine(
+ isOnLockscreenWithoutShade,
+ keyguardTransitionInteractor
+ .isInTransitionWhere(
+ fromStatePredicate = { it == LOCKSCREEN },
+ toStatePredicate = { it == AOD }
+ )
+ .onStart { emit(false) },
+ ::Pair
+ )
+ .transformWhile { (isOnLockscreenWithoutShade, aodTransitionIsRunning) ->
+ // Wait until the AOD transition is complete before terminating
+ if (!aodTransitionIsComplete && !aodTransitionIsRunning) {
+ aodTransitionIsComplete = true
+ emit(false) // do not fade in
+ false
+ } else if (aodTransitionIsRunning) {
+ aodTransitionIsComplete = false
+ true
+ } else if (isOnLockscreenWithoutShade) {
+ // Shade is closed, fade in and terminate
+ emit(true)
+ false
+ } else {
+ true
+ }
+ }
+ }
+
/** Fade in only for use after the shade collapses */
val shadeCollapseFadeIn: Flow<Boolean> =
flow {
while (currentCoroutineContext().isActive) {
+ // Ensure shade is collapsed
+ isShadeLocked.first { !it }
emit(false)
// Wait for shade to be fully expanded
isShadeLocked.first { it }
- // ... and then for it to be collapsed
- isOnLockscreenWithoutShade.first { it }
- emit(true)
- // ... and then for the animation to complete
- shadeCollapseFadeInComplete.first { it }
- shadeCollapseFadeInComplete.value = false
+ // ... and then for it to be collapsed OR a transition to AOD begins.
+ // If AOD, do not fade in (a fade out occurs instead).
+ awaitCollapse().collect { doFadeIn ->
+ if (doFadeIn) {
+ emit(true)
+ // ... and then for the animation to complete
+ shadeCollapseFadeInComplete.first { it }
+ shadeCollapseFadeInComplete.value = false
+ }
+ }
}
}
.stateIn(
@@ -333,7 +375,7 @@ constructor(
lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
occludedToAodTransitionViewModel.lockscreenAlpha,
occludedToLockscreenTransitionViewModel.lockscreenAlpha,
- primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
+ primaryBouncerToGoneTransitionViewModel.notificationAlpha,
primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha,
)
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 9052409b4d8d..a1fae03a2090 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -39,11 +39,11 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AuthKeyguardMessageArea;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityTransitionAnimator;
+import com.android.systemui.animation.RemoteAnimationRunnerCompat;
import com.android.systemui.display.data.repository.DisplayMetricsRepository;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.qs.QSPanelController;
-import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.util.Compile;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
index 6f992ac815cb..d513f8da06e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
@@ -15,10 +15,12 @@
package com.android.systemui.statusbar.phone;
import static com.android.systemui.plugins.DarkIconDispatcher.getTint;
+import static com.android.settingslib.flags.Flags.newStatusBarIcons;
import android.animation.ArgbEvaluator;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.Color;
import android.graphics.Rect;
import android.util.ArrayMap;
import android.widget.ImageView;
@@ -66,10 +68,16 @@ public class DarkIconDispatcherImpl implements SysuiDarkIconDispatcher,
Context context,
LightBarTransitionsController.Factory lightBarTransitionsControllerFactory,
DumpManager dumpManager) {
- mDarkModeIconColorSingleTone = context.getColor(
- com.android.settingslib.R.color.dark_mode_icon_color_single_tone);
- mLightModeIconColorSingleTone = context.getColor(
- com.android.settingslib.R.color.light_mode_icon_color_single_tone);
+
+ if (newStatusBarIcons()) {
+ mDarkModeIconColorSingleTone = Color.BLACK;
+ mLightModeIconColorSingleTone = Color.WHITE;
+ } else {
+ mDarkModeIconColorSingleTone = context.getColor(
+ com.android.settingslib.R.color.dark_mode_icon_color_single_tone);
+ mLightModeIconColorSingleTone = context.getColor(
+ com.android.settingslib.R.color.light_mode_icon_color_single_tone);
+ }
mTransitionsController = lightBarTransitionsControllerFactory.create(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index b07ba3c5b326..a155e94584e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -67,7 +67,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
private static final String TAG = "HeadsUpManagerPhone";
@VisibleForTesting
- final int mExtensionTime;
+ public final int mExtensionTime;
private final KeyguardBypassController mBypassController;
private final GroupMembershipManager mGroupMembershipManager;
private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 5a8b636e54fc..d975009017e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.AlarmManager.AlarmClockInfo;
+import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -379,7 +380,7 @@ public class PhoneStatusBarPolicy
}
@Override
- public void onConfigChanged(ZenModeConfig config) {
+ public void onConsolidatedPolicyChanged(NotificationManager.Policy policy) {
updateVolumeZen();
}
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 a608be39f7f7..8f3b0e788dae 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
+import android.telephony.CellSignalStrength
import android.telephony.SubscriptionInfo
import android.telephony.TelephonyManager
import com.android.systemui.log.table.TableLogBuffer
@@ -149,6 +150,6 @@ interface MobileConnectionRepository {
companion object {
/** The default number of levels to use for [numberOfLevels]. */
- const val DEFAULT_NUM_LEVELS = 4
+ val DEFAULT_NUM_LEVELS = CellSignalStrength.getNumSignalStrengthLevels()
}
}
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 6de7a00b5a10..3cb138bd75a9 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
@@ -25,6 +25,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionS
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_ID
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_NETWORK_CHANGE
@@ -223,6 +224,9 @@ class DemoMobileConnectionRepository(
_carrierId.value = event.carrierId ?: INVALID_SUBSCRIPTION_ID
+ numberOfLevels.value =
+ if (event.inflateStrength) DEFAULT_NUM_LEVELS + 1 else DEFAULT_NUM_LEVELS
+
cdmaRoaming.value = event.roaming
_isRoaming.value = event.roaming
// TODO(b/261029387): not yet supported
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
index 11a61a9fd319..dbfc576496b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
@@ -71,7 +71,7 @@ constructor(
val dataType = getString("datatype")?.toDataType()
val slot = getString("slot")?.toInt()
val carrierId = getString("carrierid")?.toInt()
- val inflateStrength = getString("inflate")?.toBoolean()
+ val inflateStrength = getString("inflate").toBoolean()
val activity = getString("activity")?.toActivity()
val carrierNetworkChange = getString("carriernetworkchange") == "show"
val roaming = getString("roam") == "show"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
index 4836abe73f86..42171d0dc2b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
@@ -31,7 +31,7 @@ sealed interface FakeNetworkEventModel {
// Null means the default (chosen by the repository)
val subId: Int?,
val carrierId: Int?,
- val inflateStrength: Boolean?,
+ val inflateStrength: Boolean = false,
@DataActivityType val activity: Int?,
val carrierNetworkChange: Boolean,
val roaming: Boolean,
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 6b303263d4b0..adcf736bba01 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
@@ -69,6 +69,9 @@ interface MobileIconInteractor {
/** True if we consider this connection to be in service, i.e. can make calls */
val isInService: StateFlow<Boolean>
+ /** True if this connection is emergency only */
+ val isEmergencyOnly: StateFlow<Boolean>
+
/** Observable for the data enabled state of this connection */
val isDataEnabled: StateFlow<Boolean>
@@ -292,12 +295,7 @@ class MobileIconInteractorImpl(
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
- private val numberOfLevels: StateFlow<Int> =
- connectionRepository.numberOfLevels.stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- connectionRepository.numberOfLevels.value,
- )
+ private val numberOfLevels: StateFlow<Int> = connectionRepository.numberOfLevels
override val isDataConnected: StateFlow<Boolean> =
connectionRepository.dataConnectionState
@@ -306,6 +304,8 @@ class MobileIconInteractorImpl(
override val isInService = connectionRepository.isInService
+ override val isEmergencyOnly: StateFlow<Boolean> = connectionRepository.isEmergencyOnly
+
override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode
/** Whether or not to show the error state of [SignalDrawable] */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
index 900a92052153..fd5ab135a1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
+import android.widget.ImageView
+import com.android.settingslib.flags.Flags.newStatusBarIcons
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView.getVisibleStateString
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
@@ -59,6 +61,18 @@ class ModernStatusBarMobileView(
.inflate(R.layout.status_bar_mobile_signal_group_new, null)
as ModernStatusBarMobileView)
.also {
+ // Flag-specific configuration
+ if (newStatusBarIcons()) {
+ // New icon (with no embedded whitespace) is slightly shorter
+ // (but actually taller)
+ val iconView = it.requireViewById<ImageView>(R.id.mobile_signal)
+ val lp = iconView.layoutParams
+ lp.height =
+ context.resources.getDimensionPixelSize(
+ R.dimen.status_bar_mobile_signal_size_updated
+ )
+ }
+
it.subId = viewModel.subscriptionId
it.initView(slot) {
MobileIconBinder.bind(view = it, viewModel = viewModel, logger = logger)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index 6e1114c57e87..3f89d04bf492 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -72,9 +72,16 @@ constructor(
/** When all connections are considered OOS, satellite connectivity is potentially valid */
val areAllConnectionsOutOfService =
if (Flags.oemEnabledSatelliteFlag()) {
- iconsInteractor.icons.aggregateOver(selector = { intr -> intr.isInService }) {
- isInServiceList ->
- isInServiceList.all { !it }
+ iconsInteractor.icons.aggregateOver(
+ selector = { intr ->
+ combine(intr.isInService, intr.isEmergencyOnly) {
+ isInService,
+ isEmergencyOnly ->
+ !isInService && !isEmergencyOnly
+ }
+ }
+ ) { isOosAndIsNotEmergencyOnly ->
+ isOosAndIsNotEmergencyOnly.all { it }
}
} else {
flowOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 4cd348412f63..ba55aadace0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -20,6 +20,9 @@ import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.ImageView
+import com.android.settingslib.flags.Flags.newStatusBarIcons
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
@@ -57,7 +60,18 @@ class ModernStatusBarWifiView(
): ModernStatusBarWifiView {
return (LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
as ModernStatusBarWifiView)
- .also { it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) } }
+ .also {
+ // Flag-specific configuration
+ if (newStatusBarIcons()) {
+ // The newer asset does not embed whitespace around it, and is therefore
+ // rectangular. Use wrap_content for the width in this case
+ val iconView = it.requireViewById<ImageView>(R.id.wifi_signal)
+ val lp = iconView.layoutParams
+ lp.width = ViewGroup.LayoutParams.WRAP_CONTENT
+ }
+
+ it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
index 5c53ff98b777..d19a3364d502 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -16,6 +16,8 @@
package com.android.systemui.unfold
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.annotation.BinderThread
import android.content.Context
@@ -23,7 +25,6 @@ import android.os.Handler
import android.os.SystemProperties
import android.util.Log
import android.view.animation.DecelerateInterpolator
-import androidx.core.animation.addListener
import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -36,17 +37,25 @@ import com.android.systemui.unfold.FullscreenLightRevealAnimationController.Comp
import com.android.systemui.unfold.dagger.UnfoldBg
import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
import javax.inject.Inject
+import kotlin.coroutines.resume
import kotlinx.coroutines.CompletableDeferred
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
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class FoldLightRevealOverlayAnimation
@Inject
constructor(
@@ -61,6 +70,9 @@ constructor(
private val revealProgressValueAnimator: ValueAnimator =
ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT)
+ private val areAnimationEnabled: Flow<Boolean>
+ get() = animationStatusRepository.areAnimationsEnabled()
+
private lateinit var controller: FullscreenLightRevealAnimationController
@Volatile private var readyCallback: CompletableDeferred<Runnable>? = null
@@ -89,33 +101,30 @@ constructor(
applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
deviceStateRepository.state
- .map { it != DeviceStateRepository.DeviceState.FOLDED }
+ .map { it == DeviceStateRepository.DeviceState.FOLDED }
.distinctUntilChanged()
- .filter { isUnfolded -> isUnfolded }
- .collect { controller.ensureOverlayRemoved() }
- }
-
- applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
- deviceStateRepository.state
- .filter {
- animationStatusRepository.areAnimationsEnabled().first() &&
- it == DeviceStateRepository.DeviceState.FOLDED
- }
- .collect {
- try {
- withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
- readyCallback = CompletableDeferred()
- val onReady = readyCallback?.await()
- readyCallback = null
- controller.addOverlay(ALPHA_OPAQUE, onReady)
- waitForScreenTurnedOn()
+ .flatMapLatest { isFolded ->
+ flow<Nothing> {
+ 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()
}
- } catch (e: TimeoutCancellationException) {
- Log.e(TAG, "Fold light reveal animation timed out")
- ensureOverlayRemovedInternal()
- }
+ .catchTimeoutAndLog()
+ .onCompletion {
+ val onReady = readyCallback?.takeIf { it.isCompleted }?.getCompleted()
+ onReady?.run()
+ readyCallback = null
+ }
}
+ .collect {}
}
}
@@ -128,19 +137,34 @@ constructor(
powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
}
- private fun ensureOverlayRemovedInternal() {
- revealProgressValueAnimator.cancel()
- controller.ensureOverlayRemoved()
- }
-
- private fun playFoldLightRevealOverlayAnimation() {
+ private suspend fun playFoldLightRevealOverlayAnimation() {
revealProgressValueAnimator.duration = ANIMATION_DURATION
revealProgressValueAnimator.interpolator = DecelerateInterpolator()
revealProgressValueAnimator.addUpdateListener { animation ->
controller.updateRevealAmount(animation.animatedFraction)
}
- revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() })
- revealProgressValueAnimator.start()
+ revealProgressValueAnimator.startAndAwaitCompletion()
+ }
+
+ private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit =
+ suspendCancellableCoroutine { continuation ->
+ val listener =
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ continuation.resume(Unit)
+ removeListener(this)
+ }
+ }
+ addListener(listener)
+ continuation.invokeOnCancellation { removeListener(listener) }
+ start()
+ }
+
+ private fun <T> Flow<T>.catchTimeoutAndLog() = catch { exception ->
+ when (exception) {
+ is TimeoutCancellationException -> Log.e(TAG, "Fold light reveal animation timed out")
+ else -> throw exception
+ }
}
private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
new file mode 100644
index 000000000000..db300ebe6cae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.wakelock
+
+import android.os.PowerManager
+import android.util.Log
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * [PowerManager.WakeLock] wrapper that tracks acquire/release reasons and logs them if owning
+ * logger is enabled.
+ */
+class ClientTrackingWakeLock(
+ private val pmWakeLock: PowerManager.WakeLock,
+ private val logger: WakeLockLogger?,
+ private val maxTimeout: Long
+) : WakeLock {
+
+ private val activeClients = ConcurrentHashMap<String, AtomicInteger>()
+
+ override fun acquire(why: String) {
+ val count = activeClients.computeIfAbsent(why) { _ -> AtomicInteger(0) }.incrementAndGet()
+ logger?.logAcquire(pmWakeLock, why, count)
+ pmWakeLock.acquire(maxTimeout)
+ }
+
+ override fun release(why: String) {
+ val count = activeClients[why]?.decrementAndGet() ?: -1
+ if (count < 0) {
+ Log.wtf(WakeLock.TAG, "Releasing WakeLock with invalid reason: $why")
+ // Restore count just in case.
+ activeClients[why]?.incrementAndGet()
+ return
+ }
+
+ logger?.logRelease(pmWakeLock, why, count)
+ pmWakeLock.release()
+ }
+
+ override fun wrap(r: Runnable): Runnable = WakeLock.wrapImpl(this, r)
+
+ fun activeClients(): Int =
+ activeClients.reduceValuesToInt(Long.MAX_VALUE, AtomicInteger::get, 0, Integer::sum)
+
+ override fun toString(): String {
+ return "active clients=${activeClients()}"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
index 039109e9ddc6..d2ed71cc3af1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/DelayedWakeLock.java
@@ -19,8 +19,11 @@ package com.android.systemui.util.wakelock;
import android.content.Context;
import android.os.Handler;
+import com.android.systemui.Flags;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import dagger.Lazy;
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
@@ -37,10 +40,13 @@ public class DelayedWakeLock implements WakeLock {
private final WakeLock mInner;
@AssistedInject
- public DelayedWakeLock(@Background Handler handler, Context context, WakeLockLogger logger,
+ public DelayedWakeLock(@Background Lazy<Handler> bgHandler,
+ @Main Lazy<Handler> mainHandler,
+ Context context, WakeLockLogger logger,
@Assisted String tag) {
mInner = WakeLock.createPartial(context, logger, tag);
- mHandler = handler;
+ mHandler = Flags.delayedWakelockReleaseOnBackgroundThread() ? bgHandler.get()
+ : mainHandler.get();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
index 6128feee8116..707751a58d84 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -22,6 +22,8 @@ import android.util.Log;
import androidx.annotation.VisibleForTesting;
+import com.android.systemui.Flags;
+
import java.util.HashMap;
import javax.inject.Inject;
@@ -112,6 +114,11 @@ public interface WakeLock {
@VisibleForTesting
static WakeLock wrap(
final PowerManager.WakeLock inner, WakeLockLogger logger, long maxTimeout) {
+ if (Flags.delayedWakelockReleaseOnBackgroundThread()) {
+ return new ClientTrackingWakeLock(inner, logger, maxTimeout);
+ }
+
+ // Non-thread safe implementation, remove when flag above is removed.
return new WakeLock() {
private final HashMap<String, Integer> mActiveClients = new HashMap<>();
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
index 18a9161ac0e3..593b90aa3c68 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
@@ -21,15 +21,19 @@ import android.media.Spatializer
import com.android.settingslib.media.data.repository.SpatializerRepository
import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import dagger.Module
import dagger.Provides
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
/** Spatializer module. */
@Module
interface SpatializerModule {
+
companion object {
+
@Provides
fun provideSpatializer(
audioManager: AudioManager,
@@ -38,8 +42,9 @@ interface SpatializerModule {
@Provides
fun provdieSpatializerRepository(
spatializer: Spatializer,
+ @Application scope: CoroutineScope,
@Background backgroundContext: CoroutineContext,
- ): SpatializerRepository = SpatializerRepositoryImpl(spatializer, backgroundContext)
+ ): SpatializerRepository = SpatializerRepositoryImpl(spatializer, scope, backgroundContext)
@Provides
fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.kt
new file mode 100644
index 000000000000..71bce5e470f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteria.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.volume.panel.component.spatial.domain
+
+import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+@VolumePanelScope
+class SpatialAudioAvailabilityCriteria
+@Inject
+constructor(private val interactor: SpatialAudioComponentInteractor) :
+ ComponentAvailabilityCriteria {
+
+ override fun isAvailable(): Flow<Boolean> =
+ interactor.isAvailable.map { it is SpatialAudioAvailabilityModel.SpatialAudio }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
new file mode 100644
index 000000000000..4358611694b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.domain.interactor
+
+import android.media.AudioDeviceAttributes
+import android.media.AudioDeviceInfo
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.media.BluetoothMediaDevice
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Provides an ability to access and update spatial audio and head tracking state.
+ *
+ * Head tracking is a sub-feature of spatial audio. This means that it requires spatial audio to be
+ * available for it to be available. And spatial audio to be enabled for it to be enabled.
+ */
+@VolumePanelScope
+class SpatialAudioComponentInteractor
+@Inject
+constructor(
+ mediaOutputInteractor: MediaOutputInteractor,
+ private val spatializerInteractor: SpatializerInteractor,
+ @VolumePanelScope private val coroutineScope: CoroutineScope,
+) {
+
+ private val changes = MutableSharedFlow<Unit>()
+ private val currentAudioDeviceAttributes: StateFlow<AudioDeviceAttributes?> =
+ mediaOutputInteractor.currentConnectedDevice
+ .map { mediaDevice ->
+ mediaDevice ?: return@map null
+ val btDevice: CachedBluetoothDevice =
+ (mediaDevice as? BluetoothMediaDevice)?.cachedDevice ?: return@map null
+ btDevice.getAudioDeviceAttributes()
+ }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
+
+ /**
+ * Returns spatial audio availability model. It can be:
+ * - unavailable
+ * - only spatial audio is available
+ * - spatial audio and head tracking are available
+ */
+ val isAvailable: StateFlow<SpatialAudioAvailabilityModel> =
+ combine(
+ currentAudioDeviceAttributes,
+ changes.onStart { emit(Unit) },
+ spatializerInteractor.isHeadTrackingAvailable,
+ ) { attributes, _, isHeadTrackingAvailable ->
+ attributes ?: return@combine SpatialAudioAvailabilityModel.Unavailable
+ if (isHeadTrackingAvailable) {
+ return@combine SpatialAudioAvailabilityModel.HeadTracking
+ }
+ if (spatializerInteractor.isSpatialAudioAvailable(attributes)) {
+ return@combine SpatialAudioAvailabilityModel.SpatialAudio
+ }
+ SpatialAudioAvailabilityModel.Unavailable
+ }
+ .stateIn(
+ coroutineScope,
+ SharingStarted.Eagerly,
+ SpatialAudioAvailabilityModel.Unavailable,
+ )
+
+ /**
+ * Returns spatial audio enabled/disabled model. It can be
+ * - disabled
+ * - only spatial audio is enabled
+ * - spatial audio and head tracking are enabled
+ */
+ val isEnabled: StateFlow<SpatialAudioEnabledModel> =
+ combine(
+ changes.onStart { emit(Unit) },
+ currentAudioDeviceAttributes,
+ isAvailable,
+ ) { _, attributes, isAvailable ->
+ if (isAvailable is SpatialAudioAvailabilityModel.Unavailable) {
+ return@combine SpatialAudioEnabledModel.Disabled
+ }
+ attributes ?: return@combine SpatialAudioEnabledModel.Disabled
+ if (spatializerInteractor.isHeadTrackingEnabled(attributes)) {
+ return@combine SpatialAudioEnabledModel.HeadTrackingEnabled
+ }
+ if (spatializerInteractor.isSpatialAudioEnabled(attributes)) {
+ return@combine SpatialAudioEnabledModel.SpatialAudioEnabled
+ }
+ SpatialAudioEnabledModel.Disabled
+ }
+ .stateIn(
+ coroutineScope,
+ SharingStarted.Eagerly,
+ SpatialAudioEnabledModel.Disabled,
+ )
+
+ /**
+ * Sets current [isEnabled] to a specific [SpatialAudioEnabledModel]. It
+ * - disables both spatial audio and head tracking
+ * - enables only spatial audio
+ * - enables both spatial audio and head tracking
+ */
+ suspend fun setEnabled(model: SpatialAudioEnabledModel) {
+ val attributes = currentAudioDeviceAttributes.value ?: return
+ spatializerInteractor.setSpatialAudioEnabled(
+ attributes,
+ model is SpatialAudioEnabledModel.SpatialAudioEnabled,
+ )
+ spatializerInteractor.setHeadTrackingEnabled(
+ attributes,
+ model is SpatialAudioEnabledModel.HeadTrackingEnabled,
+ )
+ changes.emit(Unit)
+ }
+
+ private suspend fun CachedBluetoothDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? {
+ return listOf(
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_HEADSET,
+ address
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_SPEAKER,
+ address
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLE_BROADCAST,
+ address
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+ address
+ ),
+ AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_HEARING_AID,
+ address
+ )
+ )
+ .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioAvailabilityModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioAvailabilityModel.kt
new file mode 100644
index 000000000000..cf1454618367
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioAvailabilityModel.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.volume.panel.component.spatial.domain.model
+
+/** Models spatial audio and head tracking availability. */
+interface SpatialAudioAvailabilityModel {
+
+ /** Spatial audio is unavailable. */
+ data object Unavailable : SpatialAudioAvailabilityModel
+
+ /** Spatial audio is available. */
+ interface SpatialAudio : SpatialAudioAvailabilityModel {
+ companion object : SpatialAudio
+ }
+
+ /** Head tracking is available. This also means that [SpatialAudio] is available. */
+ data object HeadTracking : SpatialAudio
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
new file mode 100644
index 000000000000..4e65f60aa0e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.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.volume.panel.component.spatial.domain.model
+
+/** Models spatial audio and head tracking enabled/disabled state. */
+interface SpatialAudioEnabledModel {
+
+ /** Spatial audio is disabled. */
+ data object Disabled : SpatialAudioEnabledModel
+
+ /** Spatial audio is enabled. */
+ interface SpatialAudioEnabled : SpatialAudioEnabledModel {
+ companion object : SpatialAudioEnabled
+ }
+
+ /** Head tracking is enabled. This also means that [SpatialAudioEnabled]. */
+ data object HeadTrackingEnabled : SpatialAudioEnabled
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
index f498520f219b..1a399341f12c 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletView.java
@@ -20,6 +20,7 @@ import static com.android.systemui.wallet.ui.WalletCardCarousel.CARD_ANIM_ALPHA_
import static com.android.systemui.wallet.ui.WalletCardCarousel.CARD_ANIM_ALPHA_DURATION;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
import android.content.Context;
@@ -40,8 +41,8 @@ import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.res.R;
import java.util.List;
@@ -308,7 +309,7 @@ public class WalletView extends FrameLayout implements WalletCardCarousel.OnCard
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
options.setPendingIntentBackgroundActivityStartMode(
- BroadcastOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
walletCard.getPendingIntent().send(options.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.w(TAG, "Error sending pending intent for wallet card.");
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 65dede83f3d6..cb61534b2255 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -28,6 +28,7 @@ import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_N
import static com.android.server.notification.Flags.screenshareNotificationHiding;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.app.INotificationManager;
import android.app.Notification;
@@ -50,6 +51,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlags;
@@ -97,6 +99,7 @@ public class BubblesManager {
private final Context mContext;
private final Bubbles mBubbles;
private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final KeyguardStateController mKeyguardStateController;
private final ShadeController mShadeController;
private final IStatusBarService mBarService;
private final INotificationManager mNotificationManager;
@@ -109,6 +112,7 @@ public class BubblesManager {
private final NotifPipeline mNotifPipeline;
private final NotifPipelineFlags mNotifPipelineFlags;
private final Executor mSysuiMainExecutor;
+ private final Executor mSysuiUiBgExecutor;
private final Bubbles.SysuiProxy mSysuiProxy;
// TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
@@ -116,6 +120,8 @@ public class BubblesManager {
private final StatusBarWindowCallback mStatusBarWindowCallback;
private final Runnable mSensitiveStateChangedListener;
private boolean mPanelExpanded;
+ private boolean mKeyguardShowing;
+ private boolean mDreamingOrInPreview;
/**
* Creates {@link BubblesManager}, returns {@code null} if Optional {@link Bubbles} not present
@@ -140,7 +146,8 @@ public class BubblesManager {
SysUiState sysUiState,
FeatureFlags featureFlags,
NotifPipelineFlags notifPipelineFlags,
- Executor sysuiMainExecutor) {
+ Executor sysuiMainExecutor,
+ Executor sysuiUiBgExecutor) {
if (bubblesOptional.isPresent()) {
return new BubblesManager(context,
bubblesOptional.get(),
@@ -160,7 +167,8 @@ public class BubblesManager {
sysUiState,
featureFlags,
notifPipelineFlags,
- sysuiMainExecutor);
+ sysuiMainExecutor,
+ sysuiUiBgExecutor);
} else {
return null;
}
@@ -185,10 +193,12 @@ public class BubblesManager {
SysUiState sysUiState,
FeatureFlags featureFlags,
NotifPipelineFlags notifPipelineFlags,
- Executor sysuiMainExecutor) {
+ Executor sysuiMainExecutor,
+ Executor sysuiUiBgExecutor) {
mContext = context;
mBubbles = bubbles;
mNotificationShadeWindowController = notificationShadeWindowController;
+ mKeyguardStateController = keyguardStateController;
mShadeController = shadeController;
mNotificationManager = notificationManager;
mDreamManager = dreamManager;
@@ -200,6 +210,7 @@ public class BubblesManager {
mNotifPipeline = notifPipeline;
mNotifPipelineFlags = notifPipelineFlags;
mSysuiMainExecutor = sysuiMainExecutor;
+ mSysuiUiBgExecutor = sysuiUiBgExecutor;
mBarService = statusBarService == null
? IStatusBarService.Stub.asInterface(
@@ -208,12 +219,11 @@ public class BubblesManager {
setupNotifPipeline();
- keyguardStateController.addCallback(new KeyguardStateController.Callback() {
+ // TODO(b/327410864): use KeyguardTransitionInteractor to listen for keyguard changes
+ mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onKeyguardShowingChanged() {
- boolean isUnlockedShade = !keyguardStateController.isShowing()
- && !isDreamingOrInPreview();
- bubbles.onStatusBarStateChanged(isUnlockedShade);
+ updateKeyguardAndDreamingState();
}
});
@@ -256,6 +266,14 @@ public class BubblesManager {
mPanelExpanded = panelExpanded;
mBubbles.onNotificationPanelExpandedChanged(panelExpanded);
}
+ if (!mKeyguardShowing && mDreamingOrInPreview && !isDreaming) {
+ // We check for dreaming state changes when keyguard status changes.
+ // This causes us to miss events if dreaming state changes after keyguard.
+ // Add a check here for the case where keyguard is dismissed before
+ // dreaming state changes. Otherwise bubbles remain invisible.
+ // TODO(b/327410864): use KeyguardTransitionInteractor for dreaming changes
+ updateKeyguardAndDreamingState();
+ }
};
notificationShadeWindowController.registerCallback(mStatusBarWindowCallback);
@@ -395,6 +413,19 @@ public class BubblesManager {
mBubbles.setSysuiProxy(mSysuiProxy);
}
+ private void updateKeyguardAndDreamingState() {
+ mSysuiUiBgExecutor.execute(() -> {
+ mKeyguardShowing = mKeyguardStateController.isShowing();
+ mDreamingOrInPreview = isDreamingOrInPreview();
+ boolean isUnlockedShade = !mKeyguardShowing && !mDreamingOrInPreview;
+ ProtoLog.d(WM_SHELL_BUBBLES,
+ "handleKeyguardOrDreamChange isUnlockedShade=%b keyguardShowing=%b "
+ + "dreamingOrInPreview=%b",
+ isUnlockedShade, mKeyguardShowing, mDreamingOrInPreview);
+ mBubbles.onStatusBarStateChanged(isUnlockedShade);
+ });
+ }
+
private boolean isDreamingOrInPreview() {
try {
return mDreamManager.isDreamingOrInPreview();
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 15e0965c16fe..324d723207cd 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -256,7 +256,7 @@ public final class WMShell implements
});
mCommandQueue.addCallback(new CommandQueue.Callbacks() {
@Override
- public void goToFullscreenFromSplit() {
+ public void moveFocusedTaskToFullscreen(int displayId) {
splitScreen.goToFullscreenFromSplit();
}
});
@@ -362,6 +362,12 @@ public final class WMShell implements
desktopMode.enterDesktop(displayId);
}
});
+ mCommandQueue.addCallback(new CommandQueue.Callbacks() {
+ @Override
+ public void moveFocusedTaskToFullscreen(int displayId) {
+ desktopMode.moveFocusedTaskToFullscreen(displayId);
+ }
+ });
}
@VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 30269664a559..0f8a81399be8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -336,48 +336,6 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
- fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() =
- runBlocking(IMMEDIATE) {
- val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
- .thenReturn(transitionStep)
-
- val job = underTest.listenForAnyStateToAodTransition(this)
- transitionStep.value =
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- transitionState = TransitionState.STARTED,
- )
- yield()
-
- verify(animations, times(2)).doze(1f)
-
- job.cancel()
- }
-
- @Test
- fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
- runBlocking(IMMEDIATE) {
- val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD))
- .thenReturn(transitionStep)
-
- val job = underTest.listenForAnyStateToAodTransition(this)
- transitionStep.value =
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- transitionState = TransitionState.STARTED,
- )
- yield()
-
- verify(animations, never()).doze(1f)
-
- job.cancel()
- }
-
- @Test
fun unregisterListeners_validate() =
runBlocking(IMMEDIATE) {
underTest.unregisterListeners()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 538daee52377..336a97ef09f4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -26,6 +26,8 @@ import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_DEFAULT;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
@@ -187,6 +189,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID,
TEST_CARRIER_ID, 0);
+ private static final SubscriptionInfo TEST_SUBSCRIPTION_PROVISIONING = new SubscriptionInfo(
+ 1, "", 0,
+ TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
+ DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
+ TEST_CARRIER_ID, PROFILE_CLASS_PROVISIONING);
private static final int FINGERPRINT_SENSOR_ID = 1;
@Mock
@@ -1204,7 +1211,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(mKeyguardUpdateMonitor.mSimDatas.get(TEST_SUBSCRIPTION.getSubscriptionId()))
.isNotNull();
- when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(null);
+ when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList())
+ .thenReturn(new ArrayList<>());
mKeyguardUpdateMonitor.mPhoneStateListener.onActiveDataSubscriptionIdChanged(
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
mTestableLooper.processAllMessages();
@@ -1216,6 +1224,37 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void testActiveSubscriptionList_filtersProvisioningNetworks() {
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION_PROVISIONING);
+ when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(list);
+ mKeyguardUpdateMonitor.mSubscriptionListener.onSubscriptionsChanged();
+
+ assertThat(mKeyguardUpdateMonitor.getSubscriptionInfo(true)).isEmpty();
+
+ SubscriptionInfo.Builder b = new SubscriptionInfo.Builder(TEST_SUBSCRIPTION_PROVISIONING);
+ b.setProfileClass(PROFILE_CLASS_DEFAULT);
+ SubscriptionInfo validInfo = b.build();
+
+ list.clear();
+ list.add(validInfo);
+ mKeyguardUpdateMonitor.mSubscriptionListener.onSubscriptionsChanged();
+
+ assertThat(mKeyguardUpdateMonitor.getSubscriptionInfo(true)).hasSize(1);
+ }
+
+ @Test
+ public void testActiveSubscriptionList_filtersProvisioningNetworks_untilValid() {
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION_PROVISIONING);
+ when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(list);
+ mKeyguardUpdateMonitor.mSubscriptionListener.onSubscriptionsChanged();
+
+ assertThat(mKeyguardUpdateMonitor.getSubscriptionInfo(true)).isEmpty();
+
+ }
+
+ @Test
public void testIsUserUnlocked() {
// mUserManager will report the user as unlocked on @Before
assertThat(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index 4ab7ab450fae..043dcaa0d919 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -15,10 +15,13 @@
*/
package com.android.systemui.battery
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.widget.ImageView
import androidx.test.filters.SmallTest
+import com.android.settingslib.flags.Flags.FLAG_NEW_STATUS_BAR_ICONS
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.battery.BatteryMeterView.BatteryEstimateFetcher
@@ -141,7 +144,8 @@ class BatteryMeterViewTest : SysuiTestCase() {
}
@Test
- fun changesFromEstimateToPercent_textAndContentDescriptionChanges() {
+ @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun changesFromEstimateToPercent_textAndContentDescriptionChanges_flagOff() {
mBatteryMeterView.onBatteryLevelChanged(15, false)
mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
@@ -164,6 +168,31 @@ class BatteryMeterViewTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun changesFromEstimateToPercent_textAndContentDescriptionChanges_flagOn() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(
+ R.string.accessibility_battery_level_with_estimate, 15, ESTIMATE
+ )
+ )
+
+ // Update the show mode from estimate to percent
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+
+ assertThat(mBatteryMeterView.batteryPercentView).isNull()
+ assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+ context.getString(R.string.accessibility_battery_level, 15)
+ )
+ assertThat(mBatteryMeterView.unifiedBatteryState.showPercent).isTrue()
+ }
+
+ @Test
fun contentDescription_manyUpdates_alwaysUpdated() {
// BatteryDefender
mBatteryMeterView.onBatteryLevelChanged(90, false)
@@ -208,7 +237,8 @@ class BatteryMeterViewTest : SysuiTestCase() {
}
@Test
- fun isBatteryDefenderChanged_true_drawableGetsTrue() {
+ @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun isBatteryDefenderChanged_true_drawableGetsTrue_flagOff() {
mBatteryMeterView.setDisplayShieldEnabled(true)
val drawable = getBatteryDrawable()
@@ -218,7 +248,18 @@ class BatteryMeterViewTest : SysuiTestCase() {
}
@Test
- fun isBatteryDefenderChanged_false_drawableGetsFalse() {
+ @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun isBatteryDefenderChanged_true_drawableGetsTrue_flagOn() {
+ mBatteryMeterView.setDisplayShieldEnabled(true)
+
+ mBatteryMeterView.onIsBatteryDefenderChanged(true)
+
+ assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNotNull()
+ }
+
+ @Test
+ @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun isBatteryDefenderChanged_false_drawableGetsFalse_flagOff() {
mBatteryMeterView.setDisplayShieldEnabled(true)
val drawable = getBatteryDrawable()
@@ -232,7 +273,22 @@ class BatteryMeterViewTest : SysuiTestCase() {
}
@Test
- fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse() {
+ @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun isBatteryDefenderChanged_false_drawableGetsFalse_flagOn() {
+ mBatteryMeterView.setDisplayShieldEnabled(true)
+
+ // Start as true
+ mBatteryMeterView.onIsBatteryDefenderChanged(true)
+
+ // Update to false
+ mBatteryMeterView.onIsBatteryDefenderChanged(false)
+
+ assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNull()
+ }
+
+ @Test
+ @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse_flagOff() {
mBatteryMeterView.setDisplayShieldEnabled(false)
val drawable = getBatteryDrawable()
@@ -242,17 +298,41 @@ class BatteryMeterViewTest : SysuiTestCase() {
}
@Test
- fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse() {
+ @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun isBatteryDefenderChanged_true_featureflagOff_drawableGetsFalse_flagOn() {
+ mBatteryMeterView.setDisplayShieldEnabled(false)
+
+ mBatteryMeterView.onIsBatteryDefenderChanged(true)
+
+ assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNull()
+ }
+
+ @Test
+ @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse_flagOff() {
mBatteryMeterView.onBatteryLevelChanged(45, true)
val drawable = getBatteryDrawable()
mBatteryMeterView.onIsIncompatibleChargingChanged(true)
assertThat(drawable.getCharging()).isFalse()
+ assertThat(mBatteryMeterView.isCharging).isFalse()
}
@Test
- fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue() {
+ @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse_flagOn() {
+ mBatteryMeterView.onBatteryLevelChanged(45, true)
+
+ mBatteryMeterView.onIsIncompatibleChargingChanged(true)
+
+ assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNull()
+ assertThat(mBatteryMeterView.isCharging).isFalse()
+ }
+
+ @Test
+ @DisableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue_flagOff() {
mBatteryMeterView.onBatteryLevelChanged(45, true)
val drawable = getBatteryDrawable()
@@ -261,6 +341,17 @@ class BatteryMeterViewTest : SysuiTestCase() {
assertThat(drawable.getCharging()).isTrue()
}
+ @Test
+ @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue_flagOn() {
+ mBatteryMeterView.onBatteryLevelChanged(45, true)
+
+ mBatteryMeterView.onIsIncompatibleChargingChanged(false)
+
+ assertThat(mBatteryMeterView.isCharging).isTrue()
+ assertThat(mBatteryMeterView.unifiedBatteryState.attribution).isNotNull()
+ }
+
private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
return (mBatteryMeterView.getChildAt(0) as ImageView)
.drawable as AccessorizedBatteryDrawable
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
index 9f24d5dfea79..f5e96c93271a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
@@ -237,6 +237,35 @@ class FacePropertyRepositoryImplTest : SysuiTestCase() {
}
}
+ @Test
+ fun providesTheCameraInfoOnCameraAvailableChange() {
+ testScope.runTest {
+ runCurrent()
+ collectLastValue(underTest.cameraInfo)
+
+ verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture())
+ callback.value.onAllAuthenticatorsRegistered(
+ listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG))
+ )
+ runCurrent()
+ verify(cameraManager)
+ .registerAvailabilityCallback(any(Executor::class.java), cameraCallback.capture())
+
+ cameraCallback.value.onPhysicalCameraAvailable("0", PHYSICAL_CAMERA_ID_OUTER_FRONT)
+ runCurrent()
+
+ val cameraInfo by collectLastValue(underTest.cameraInfo)
+ assertThat(cameraInfo)
+ .isEqualTo(
+ CameraInfo(
+ "0",
+ PHYSICAL_CAMERA_ID_OUTER_FRONT,
+ Point(OUTER_FRONT_SENSOR_LOCATION[0], OUTER_FRONT_SENSOR_LOCATION[1])
+ )
+ )
+ }
+ }
+
private fun createSensorProperties(id: Int, strength: Int) =
FaceSensorPropertiesInternal(id, strength, 0, emptyList(), 1, false, false, false)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index e30dd35d74c1..e1e9fcb2d059 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.keyguardUpdateMonitor
import com.android.keyguard.trustManager
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.CameraInfo
import com.android.systemui.biometrics.data.repository.FaceSensorInfo
import com.android.systemui.biometrics.data.repository.facePropertyRepository
import com.android.systemui.biometrics.shared.model.LockoutMode
@@ -490,6 +491,47 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
verify(trustManager).clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt())
}
+ @Test
+ fun faceAuthIsRequestedWhenAuthIsRunningWhileCameraInfoChanged() =
+ testScope.runTest {
+ facePropertyRepository.setCameraIno(null)
+ underTest.start()
+
+ faceAuthRepository.requestAuthenticate(
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED,
+ true
+ )
+ facePropertyRepository.setCameraIno(CameraInfo("0", "1", null))
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_CAMERA_AVAILABLE_CHANGED, true))
+ }
+
+ @Test
+ fun faceAuthIsNotRequestedWhenNoAuthRunningWhileCameraInfoChanged() =
+ testScope.runTest {
+ facePropertyRepository.setCameraIno(null)
+ underTest.start()
+
+ facePropertyRepository.setCameraIno(CameraInfo("0", "1", null))
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
+ }
+
+ @Test
+ fun faceAuthIsNotRequestedWhenAuthIsRunningWhileCameraInfoIsNull() =
+ testScope.runTest {
+ facePropertyRepository.setCameraIno(null)
+ underTest.start()
+
+ facePropertyRepository.setCameraIno(null)
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value).isNull()
+ }
+
companion object {
private const val primaryUserId = 1
private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY)
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 e97edcbdc68a..9b8cf592d496 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
@@ -27,7 +27,7 @@ import com.android.systemui.deviceentry.data.ui.viewmodel.deviceEntryUdfpsAccess
import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -39,6 +39,7 @@ import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
@@ -52,7 +53,7 @@ class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() {
}
private val deviceEntryIconTransition = kosmos.fakeDeviceEntryIconViewModelTransition
private val testScope = kosmos.testScope
- private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val biometricSettingsRepository = kosmos.fakeBiometricSettingsRepository
private val accessibilityRepository = kosmos.fakeAccessibilityRepository
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
@@ -85,22 +86,12 @@ class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() {
setupVisibleStateOnLockscreen()
// AOD
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED,
- )
- )
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED,
- )
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ this,
)
+ runCurrent()
assertThat(visible).isFalse()
}
fun fpNotRunning_overlayNotVisible() =
@@ -129,6 +120,7 @@ class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() {
// Listening for UDFPS
fingerprintPropertyRepository.supportsUdfps()
+ biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
deviceEntryFingerprintAuthRepository.setIsRunning(true)
deviceEntryRepository.setUnlocked(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
index 9d9b263c5df5..2d3ca6095835 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
@@ -19,7 +19,13 @@ package com.android.systemui.globalactions;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.nearby.NearbyManager;
+import android.net.platform.flags.Flags;
import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -32,6 +38,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@SmallTest
@@ -41,10 +48,13 @@ public class ShutdownUiTest extends SysuiTestCase {
ShutdownUi mShutdownUi;
@Mock
BlurUtils mBlurUtils;
+ @Mock
+ NearbyManager mNearbyManager;
@Before
public void setUp() throws Exception {
- mShutdownUi = new ShutdownUi(getContext(), mBlurUtils);
+ MockitoAnnotations.initMocks(this);
+ mShutdownUi = new ShutdownUi(getContext(), mBlurUtils, mNearbyManager);
}
@Test
@@ -82,4 +92,53 @@ public class ShutdownUiTest extends SysuiTestCase {
String message = mShutdownUi.getReasonMessage("anything-else");
assertNull(message);
}
+
+ @EnableFlags(Flags.FLAG_POWERED_OFF_FINDING_PLATFORM)
+ @Test
+ public void getDialog_whenPowerOffFindingModeEnabled_returnsFinderDialog() {
+ when(mNearbyManager.getPoweredOffFindingMode()).thenReturn(
+ NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED);
+
+ int actualLayout = mShutdownUi.getShutdownDialogContent(false);
+
+ int expectedLayout = com.android.systemui.res.R.layout.shutdown_dialog_finder_active;
+ assertEquals(actualLayout, expectedLayout);
+ }
+
+ @DisableFlags(Flags.FLAG_POWERED_OFF_FINDING_PLATFORM)
+ @Test
+ public void getDialog_whenPowerOffFindingModeEnabledFlagDisabled_returnsFinderDialog() {
+ when(mNearbyManager.getPoweredOffFindingMode()).thenReturn(
+ NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED);
+
+ int actualLayout = mShutdownUi.getShutdownDialogContent(false);
+
+ int expectedLayout = R.layout.shutdown_dialog;
+ assertEquals(actualLayout, expectedLayout);
+ }
+
+ @EnableFlags(Flags.FLAG_POWERED_OFF_FINDING_PLATFORM)
+ @Test
+ public void getDialog_whenPowerOffFindingModeDisabled_returnsDefaultDialog() {
+ when(mNearbyManager.getPoweredOffFindingMode()).thenReturn(
+ NearbyManager.POWERED_OFF_FINDING_MODE_DISABLED);
+
+ int actualLayout = mShutdownUi.getShutdownDialogContent(false);
+
+ int expectedLayout = R.layout.shutdown_dialog;
+ assertEquals(actualLayout, expectedLayout);
+ }
+
+ @EnableFlags(Flags.FLAG_POWERED_OFF_FINDING_PLATFORM)
+ @Test
+ public void getDialog_whenPowerOffFindingModeEnabledAndIsReboot_returnsDefaultDialog() {
+ when(mNearbyManager.getPoweredOffFindingMode()).thenReturn(
+ NearbyManager.POWERED_OFF_FINDING_MODE_ENABLED);
+
+ int actualLayout = mShutdownUi.getShutdownDialogContent(true);
+
+ int expectedLayout = R.layout.shutdown_dialog;
+ assertEquals(actualLayout, expectedLayout);
+ }
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
new file mode 100644
index 000000000000..02bd810a43d5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryIconViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+ private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository
+ private lateinit var deviceEntryIconTransition: FakeDeviceEntryIconTransition
+ private lateinit var underTest: DeviceEntryIconViewModel
+
+ @Before
+ fun setUp() {
+ keyguardRepository = kosmos.fakeKeyguardRepository
+ fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ fingerprintAuthRepository = kosmos.fakeDeviceEntryFingerprintAuthRepository
+ deviceEntryIconTransition = kosmos.fakeDeviceEntryIconViewModelTransition
+ underTest = kosmos.deviceEntryIconViewModel
+ }
+
+ @Test
+ fun isLongPressEnabled_udfpsRunning() =
+ testScope.runTest {
+ val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+ fingerprintPropertyRepository.supportsUdfps()
+ fingerprintAuthRepository.setIsRunning(true)
+ keyguardRepository.setKeyguardDismissible(false)
+ assertThat(isLongPressEnabled).isFalse()
+ }
+
+ @Test
+ fun isLongPressEnabled_unlocked() =
+ testScope.runTest {
+ val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+ keyguardRepository.setKeyguardDismissible(true)
+ assertThat(isLongPressEnabled).isTrue()
+ }
+
+ @Test
+ fun isLongPressEnabled_lock() =
+ testScope.runTest {
+ val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+ keyguardRepository.setKeyguardDismissible(false)
+
+ // udfps supported
+ fingerprintPropertyRepository.supportsUdfps()
+ assertThat(isLongPressEnabled).isTrue()
+
+ // udfps isn't supported
+ fingerprintPropertyRepository.supportsRearFps()
+ assertThat(isLongPressEnabled).isFalse()
+ }
+
+ @Test
+ fun isVisible() =
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isVisible)
+ deviceEntryIconTransitionAlpha(1f)
+ assertThat(isVisible).isTrue()
+
+ deviceEntryIconTransitionAlpha(0f)
+ assertThat(isVisible).isFalse()
+
+ deviceEntryIconTransitionAlpha(.5f)
+ assertThat(isVisible).isTrue()
+ }
+
+ private fun deviceEntryIconTransitionAlpha(alpha: Float) {
+ deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
deleted file mode 100644
index e044eeca8303..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.mediaprojection.permission
-
-import android.app.AlertDialog
-import android.media.projection.MediaProjectionConfig
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.view.WindowManager
-import android.widget.Spinner
-import android.widget.TextView
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
-import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.util.mockito.mock
-import junit.framework.Assert.assertEquals
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.`when` as whenever
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
-
- private lateinit var dialog: AlertDialog
-
- private val flags = mock<FeatureFlagsClassic>()
- private val onStartRecordingClicked = mock<Runnable>()
- private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
-
- private val mediaProjectionConfig: MediaProjectionConfig =
- MediaProjectionConfig.createConfigForDefaultDisplay()
- private val appName: String = "testApp"
- private val hostUid: Int = 12345
-
- private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
- private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
- private val resIdSingleAppDisabled =
- R.string.media_projection_entry_app_permission_dialog_single_app_disabled
-
- @Before
- fun setUp() {
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
- }
-
- @After
- fun teardown() {
- if (::dialog.isInitialized) {
- dialog.dismiss()
- }
- }
-
- @Test
- fun showDialog_forceShowPartialScreenShareFalse() {
- // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
- // overrideDisableSingleAppOption = false
- val overrideDisableSingleAppOption = false
- setUpAndShowDialog(overrideDisableSingleAppOption)
-
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
- val secondOptionText =
- spinner.adapter
- .getDropDownView(1, null, spinner)
- .findViewById<TextView>(android.R.id.text2)
- ?.text
-
- // check that the first option is full screen and enabled
- assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
-
- // check that the second option is single app and disabled
- assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText)
- }
-
- @Test
- fun showDialog_forceShowPartialScreenShareTrue() {
- // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
- // overrideDisableSingleAppOption = true
- val overrideDisableSingleAppOption = true
- setUpAndShowDialog(overrideDisableSingleAppOption)
-
- val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
- val secondOptionText =
- spinner.adapter
- .getDropDownView(1, null, spinner)
- .findViewById<TextView>(android.R.id.text1)
- ?.text
-
- // check that the first option is single app and enabled
- assertEquals(context.getString(resIdSingleApp), spinner.selectedItem)
-
- // check that the second option is full screen and enabled
- assertEquals(context.getString(resIdFullScreen), secondOptionText)
- }
-
- private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) {
- val delegate =
- MediaProjectionPermissionDialogDelegate(
- context,
- mediaProjectionConfig,
- {},
- onStartRecordingClicked,
- appName,
- overrideDisableSingleAppOption,
- hostUid,
- mediaProjectionMetricsLogger
- )
-
- dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
- SystemUIDialog.applyFlags(dialog)
- SystemUIDialog.setDialogSize(dialog)
-
- dialog.window?.addSystemFlags(
- WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
- )
-
- delegate.onCreate(dialog, savedInstanceState = null)
- dialog.show()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt
index 70b04175da91..8ecb95334bc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogDelegateTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.tiles.dialog.bluetooth
+import android.content.Context
import android.graphics.drawable.Drawable
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -31,7 +32,13 @@ import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.model.SysUiState
import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
@@ -43,6 +50,8 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
@@ -51,7 +60,7 @@ import org.mockito.junit.MockitoRule
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class BluetoothTileDialogTest : SysuiTestCase() {
+class BluetoothTileDialogDelegateTest : SysuiTestCase() {
companion object {
const val DEVICE_NAME = "device"
const val DEVICE_CONNECTION_SUMMARY = "active"
@@ -76,6 +85,10 @@ class BluetoothTileDialogTest : SysuiTestCase() {
isBluetoothEnabled = ENABLED,
isAutoOnToggleFeatureAvailable = ENABLED
)
+ @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory
+ @Mock private lateinit var dialogManager: SystemUIDialogManager
+ @Mock private lateinit var sysuiState: SysUiState
+ @Mock private lateinit var dialogTransitionAnimator: DialogTransitionAnimator
private val fakeSystemClock = FakeSystemClock()
@@ -83,7 +96,7 @@ class BluetoothTileDialogTest : SysuiTestCase() {
private lateinit var dispatcher: CoroutineDispatcher
private lateinit var testScope: TestScope
private lateinit var icon: Pair<Drawable, String>
- private lateinit var bluetoothTileDialog: BluetoothTileDialog
+ private lateinit var mBluetoothTileDialogDelegate: BluetoothTileDialogDelegate
private lateinit var deviceItem: DeviceItem
@Before
@@ -91,18 +104,44 @@ class BluetoothTileDialogTest : SysuiTestCase() {
scheduler = TestCoroutineScheduler()
dispatcher = UnconfinedTestDispatcher(scheduler)
testScope = TestScope(dispatcher)
- bluetoothTileDialog =
- BluetoothTileDialog(
- ENABLED,
+
+ whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+
+ mBluetoothTileDialogDelegate =
+ BluetoothTileDialogDelegate(
+ mContext,
uiProperties,
CONTENT_HEIGHT,
+ ENABLED,
bluetoothTileDialogCallback,
+ {},
dispatcher,
fakeSystemClock,
uiEventLogger,
logger,
- mContext
+ sysuiDialogFactory,
+ LayoutInflater.from(mContext)
)
+
+ whenever(
+ sysuiDialogFactory.create(
+ any(SystemUIDialog.Delegate::class.java),
+ any(Context::class.java)
+ )
+ )
+ .thenAnswer {
+ SystemUIDialog(
+ mContext,
+ 0,
+ SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
+ dialogManager,
+ sysuiState,
+ fakeBroadcastDispatcher,
+ dialogTransitionAnimator,
+ it.getArgument(0)
+ )
+ }
+
icon = Pair(drawable, DEVICE_NAME)
deviceItem =
DeviceItem(
@@ -118,11 +157,11 @@ class BluetoothTileDialogTest : SysuiTestCase() {
@Test
fun testShowDialog_createRecyclerViewWithAdapter() {
- bluetoothTileDialog.show()
+ val dialog = mBluetoothTileDialogDelegate.createDialog()
+ dialog.show()
- val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
+ val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list)
- assertThat(bluetoothTileDialog.isShowing).isTrue()
assertThat(recyclerView).isNotNull()
assertThat(recyclerView.visibility).isEqualTo(VISIBLE)
assertThat(recyclerView.adapter).isNotNull()
@@ -132,28 +171,18 @@ class BluetoothTileDialogTest : SysuiTestCase() {
@Test
fun testShowDialog_displayBluetoothDevice() {
testScope.runTest {
- bluetoothTileDialog =
- BluetoothTileDialog(
- ENABLED,
- uiProperties,
- CONTENT_HEIGHT,
- bluetoothTileDialogCallback,
- dispatcher,
- fakeSystemClock,
- uiEventLogger,
- logger,
- mContext
- )
- bluetoothTileDialog.show()
+ val dialog = mBluetoothTileDialogDelegate.createDialog()
+ dialog.show()
fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
- bluetoothTileDialog.onDeviceItemUpdated(
+ mBluetoothTileDialogDelegate.onDeviceItemUpdated(
+ dialog,
listOf(deviceItem),
showSeeAll = false,
showPairNewDevice = false
)
- val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
- val adapter = recyclerView.adapter as BluetoothTileDialog.Adapter
+ val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list)
+ val adapter = recyclerView?.adapter as BluetoothTileDialogDelegate.Adapter
assertThat(adapter.itemCount).isEqualTo(1)
assertThat(adapter.getItem(0).deviceName).isEqualTo(DEVICE_NAME)
assertThat(adapter.getItem(0).connectionSummary).isEqualTo(DEVICE_CONNECTION_SUMMARY)
@@ -168,17 +197,7 @@ class BluetoothTileDialogTest : SysuiTestCase() {
val view =
LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
val viewHolder =
- BluetoothTileDialog(
- ENABLED,
- uiProperties,
- CONTENT_HEIGHT,
- bluetoothTileDialogCallback,
- dispatcher,
- fakeSystemClock,
- uiEventLogger,
- logger,
- mContext
- )
+ mBluetoothTileDialogDelegate
.Adapter(bluetoothTileDialogCallback)
.DeviceItemViewHolder(view)
viewHolder.bind(deviceItem, bluetoothTileDialogCallback)
@@ -196,16 +215,19 @@ class BluetoothTileDialogTest : SysuiTestCase() {
val view =
LayoutInflater.from(mContext).inflate(R.layout.bluetooth_device_item, null, false)
val viewHolder =
- BluetoothTileDialog(
- ENABLED,
+ BluetoothTileDialogDelegate(
+ mContext,
uiProperties,
CONTENT_HEIGHT,
+ ENABLED,
bluetoothTileDialogCallback,
+ {},
dispatcher,
fakeSystemClock,
uiEventLogger,
logger,
- mContext
+ sysuiDialogFactory,
+ LayoutInflater.from(mContext)
)
.Adapter(bluetoothTileDialogCallback)
.DeviceItemViewHolder(view)
@@ -220,32 +242,21 @@ class BluetoothTileDialogTest : SysuiTestCase() {
@Test
fun testOnDeviceUpdated_hideSeeAll_showPairNew() {
testScope.runTest {
- bluetoothTileDialog =
- BluetoothTileDialog(
- ENABLED,
- uiProperties,
- CONTENT_HEIGHT,
- bluetoothTileDialogCallback,
- dispatcher,
- fakeSystemClock,
- uiEventLogger,
- logger,
- mContext
- )
- bluetoothTileDialog.show()
+ val dialog = mBluetoothTileDialogDelegate.createDialog()
+ dialog.show()
fakeSystemClock.setElapsedRealtime(Long.MAX_VALUE)
- bluetoothTileDialog.onDeviceItemUpdated(
+ mBluetoothTileDialogDelegate.onDeviceItemUpdated(
+ dialog,
listOf(deviceItem),
showSeeAll = false,
showPairNewDevice = true
)
- val seeAllButton = bluetoothTileDialog.requireViewById<View>(R.id.see_all_button)
- val pairNewButton =
- bluetoothTileDialog.requireViewById<View>(R.id.pair_new_device_button)
- val recyclerView = bluetoothTileDialog.requireViewById<RecyclerView>(R.id.device_list)
- val adapter = recyclerView.adapter as BluetoothTileDialog.Adapter
- val scrollViewContent = bluetoothTileDialog.requireViewById<View>(R.id.scroll_view)
+ val seeAllButton = dialog.requireViewById<View>(R.id.see_all_button)
+ val pairNewButton = dialog.requireViewById<View>(R.id.pair_new_device_button)
+ val recyclerView = dialog.requireViewById<RecyclerView>(R.id.device_list)
+ val adapter = recyclerView?.adapter as BluetoothTileDialogDelegate.Adapter
+ val scrollViewContent = dialog.requireViewById<View>(R.id.scroll_view)
assertThat(seeAllButton).isNotNull()
assertThat(seeAllButton.visibility).isEqualTo(GONE)
@@ -260,22 +271,24 @@ class BluetoothTileDialogTest : SysuiTestCase() {
fun testShowDialog_cachedHeightLargerThanMinHeight_displayFromCachedHeight() {
testScope.runTest {
val cachedHeight = Int.MAX_VALUE
- bluetoothTileDialog =
- BluetoothTileDialog(
- ENABLED,
- uiProperties,
- cachedHeight,
- bluetoothTileDialogCallback,
- dispatcher,
- fakeSystemClock,
- uiEventLogger,
- logger,
- mContext
- )
- bluetoothTileDialog.show()
- assertThat(
- bluetoothTileDialog.requireViewById<View>(R.id.scroll_view).layoutParams.height
- )
+ val dialog =
+ BluetoothTileDialogDelegate(
+ mContext,
+ BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
+ cachedHeight,
+ ENABLED,
+ bluetoothTileDialogCallback,
+ {},
+ dispatcher,
+ fakeSystemClock,
+ uiEventLogger,
+ logger,
+ sysuiDialogFactory,
+ LayoutInflater.from(mContext)
+ )
+ .createDialog()
+ dialog.show()
+ assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
.isEqualTo(cachedHeight)
}
}
@@ -283,22 +296,24 @@ class BluetoothTileDialogTest : SysuiTestCase() {
@Test
fun testShowDialog_cachedHeightLessThanMinHeight_displayFromUiProperties() {
testScope.runTest {
- bluetoothTileDialog =
- BluetoothTileDialog(
- ENABLED,
- uiProperties,
- MATCH_PARENT,
- bluetoothTileDialogCallback,
- dispatcher,
- fakeSystemClock,
- uiEventLogger,
- logger,
- mContext
- )
- bluetoothTileDialog.show()
- assertThat(
- bluetoothTileDialog.requireViewById<View>(R.id.scroll_view).layoutParams.height
- )
+ val dialog =
+ BluetoothTileDialogDelegate(
+ mContext,
+ BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
+ MATCH_PARENT,
+ ENABLED,
+ bluetoothTileDialogCallback,
+ {},
+ dispatcher,
+ fakeSystemClock,
+ uiEventLogger,
+ logger,
+ sysuiDialogFactory,
+ LayoutInflater.from(mContext)
+ )
+ .createDialog()
+ dialog.show()
+ assertThat(dialog.requireViewById<View>(R.id.scroll_view).layoutParams.height)
.isGreaterThan(MATCH_PARENT)
}
}
@@ -306,23 +321,25 @@ class BluetoothTileDialogTest : SysuiTestCase() {
@Test
fun testShowDialog_bluetoothEnabled_autoOnToggleGone() {
testScope.runTest {
- bluetoothTileDialog =
- BluetoothTileDialog(
- ENABLED,
- BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
- MATCH_PARENT,
- bluetoothTileDialogCallback,
- dispatcher,
- fakeSystemClock,
- uiEventLogger,
- logger,
- mContext
- )
- bluetoothTileDialog.show()
+ val dialog =
+ BluetoothTileDialogDelegate(
+ mContext,
+ BluetoothTileDialogViewModel.UiProperties.build(ENABLED, ENABLED),
+ MATCH_PARENT,
+ ENABLED,
+ bluetoothTileDialogCallback,
+ {},
+ dispatcher,
+ fakeSystemClock,
+ uiEventLogger,
+ logger,
+ sysuiDialogFactory,
+ LayoutInflater.from(mContext)
+ )
+ .createDialog()
+ dialog.show()
assertThat(
- bluetoothTileDialog
- .requireViewById<View>(R.id.bluetooth_auto_on_toggle_layout)
- .visibility
+ dialog.requireViewById<View>(R.id.bluetooth_auto_on_toggle_layout).visibility
)
.isEqualTo(GONE)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
index cb9f4b4e4810..39e2413be40e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.qs.tiles.dialog.bluetooth
-import android.content.SharedPreferences
import android.content.pm.UserInfo
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -31,10 +30,14 @@ import com.android.settingslib.flags.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.kotlin.getMutableStateFlow
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -50,12 +53,12 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
@@ -84,9 +87,15 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@Mock private lateinit var uiEventLogger: UiEventLogger
- @Mock private lateinit var logger: BluetoothTileDialogLogger
+ @Mock
+ private lateinit var mBluetoothTileDialogDelegateDelegateFactory:
+ BluetoothTileDialogDelegate.Factory
- @Mock private lateinit var sharedPreferences: SharedPreferences
+ @Mock private lateinit var bluetoothTileDialogDelegate: BluetoothTileDialogDelegate
+
+ @Mock private lateinit var sysuiDialog: SystemUIDialog
+
+ private val sharedPreferences = FakeSharedPreferences()
private lateinit var scheduler: TestCoroutineScheduler
private lateinit var dispatcher: CoroutineDispatcher
@@ -123,20 +132,38 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
),
mDialogTransitionAnimator,
activityStarter,
- fakeSystemClock,
uiEventLogger,
- logger,
testScope.backgroundScope,
dispatcher,
dispatcher,
sharedPreferences,
+ mBluetoothTileDialogDelegateDelegateFactory
)
- `when`(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
- `when`(bluetoothStateInteractor.bluetoothStateUpdate)
+ whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
+ whenever(bluetoothStateInteractor.bluetoothStateUpdate)
.thenReturn(MutableStateFlow(null).asStateFlow())
- `when`(deviceItemInteractor.deviceItemUpdateRequest)
+ whenever(deviceItemInteractor.deviceItemUpdateRequest)
.thenReturn(MutableStateFlow(Unit).asStateFlow())
- `when`(bluetoothStateInteractor.isBluetoothEnabled).thenReturn(true)
+ whenever(bluetoothStateInteractor.isBluetoothEnabled).thenReturn(true)
+ whenever(
+ mBluetoothTileDialogDelegateDelegateFactory.create(
+ any(),
+ any(),
+ anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ any(),
+ any()
+ )
+ )
+ .thenReturn(bluetoothTileDialogDelegate)
+ whenever(bluetoothTileDialogDelegate.createDialog()).thenReturn(sysuiDialog)
+ whenever(bluetoothTileDialogDelegate.bluetoothStateToggle)
+ .thenReturn(getMutableStateFlow(false))
+ whenever(bluetoothTileDialogDelegate.deviceItemClick)
+ .thenReturn(getMutableStateFlow(deviceItem))
+ whenever(bluetoothTileDialogDelegate.contentHeight).thenReturn(getMutableStateFlow(0))
+ whenever(bluetoothTileDialogDelegate.bluetoothAutoOnToggle)
+ .thenReturn(getMutableStateFlow(false))
}
@Test
@@ -145,7 +172,6 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
bluetoothTileDialogViewModel.showDialog(context, null)
verify(mDialogTransitionAnimator, never()).showFromView(any(), any(), any(), any())
- verify(uiEventLogger).log(BluetoothTileDialogUiEvent.BLUETOOTH_TILE_DIALOG_SHOWN)
}
}
@@ -191,7 +217,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@Test
fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
testScope.runTest {
- `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+ whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
bluetoothTileDialogViewModel.showDialog(context, null)
val clickedView = View(context)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
index 92c73261a95e..a8cd8c801a95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemFactoryTest.kt
@@ -16,10 +16,18 @@
package com.android.systemui.qs.tiles.dialog.bluetooth
+import android.bluetooth.BluetoothDevice
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.media.AudioManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.flags.Flags
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -35,19 +43,26 @@ import org.mockito.junit.MockitoRule
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class DeviceItemFactoryTest : SysuiTestCase() {
-
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var cachedDevice: CachedBluetoothDevice
+ @Mock private lateinit var bluetoothDevice: BluetoothDevice
+ @Mock private lateinit var packageManager: PackageManager
private val availableMediaDeviceItemFactory = AvailableMediaDeviceItemFactory()
private val connectedDeviceItemFactory = ConnectedDeviceItemFactory()
private val savedDeviceItemFactory = SavedDeviceItemFactory()
+ private val audioManager = context.getSystemService(AudioManager::class.java)!!
+
@Before
fun setup() {
`when`(cachedDevice.name).thenReturn(DEVICE_NAME)
+ `when`(cachedDevice.address).thenReturn(DEVICE_ADDRESS)
+ `when`(cachedDevice.device).thenReturn(bluetoothDevice)
`when`(cachedDevice.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+
+ context.setMockPackageManager(packageManager)
}
@Test
@@ -72,6 +87,225 @@ class DeviceItemFactoryTest : SysuiTestCase() {
assertThat(deviceItem.background).isNotNull()
}
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_bondedAndNotConnected_returnsTrue() {
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_connected_returnsFalse() {
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(true)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_notBonded_returnsFalse() {
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
+ val exclusiveManagerName =
+ BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+ `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(exclusiveManagerName.toByteArray())
+ `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_noExclusiveManager_returnsTrue() {
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+ `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
+ `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
+ .thenReturn(PackageInfo())
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
+ val exclusiveManagerName =
+ BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+ `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(exclusiveManagerName.toByteArray())
+ `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+ .thenThrow(PackageManager.NameNotFoundException("Test!"))
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_notExclusivelyManaged_notBonded_returnsFalse() {
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+ `when`(cachedDevice.isConnected).thenReturn(false)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testSavedFactory_isFilterMatched_notExclusivelyManaged_connected_returnsFalse() {
+ `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(cachedDevice.isConnected).thenReturn(true)
+
+ assertThat(savedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_bondedAndConnected_returnsTrue() {
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_notConnected_returnsFalse() {
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(false)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_notBonded_returnsFalse() {
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() {
+ val exclusiveManagerName =
+ BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+ `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(exclusiveManagerName.toByteArray())
+ `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo())
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_noExclusiveManager_returnsTrue() {
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() {
+ `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray())
+ `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0))
+ .thenReturn(PackageInfo())
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() {
+ val exclusiveManagerName =
+ BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME
+ `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER))
+ .thenReturn(exclusiveManagerName.toByteArray())
+ `when`(packageManager.getPackageInfo(exclusiveManagerName, 0))
+ .thenThrow(PackageManager.NameNotFoundException("Test!"))
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isTrue()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_notExclusivelyManaged_notBonded_returnsFalse() {
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
+ `when`(bluetoothDevice.isConnected).thenReturn(true)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE)
+ fun testConnectedFactory_isFilterMatched_notExclusivelyManaged_notConnected_returnsFalse() {
+ `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
+ `when`(bluetoothDevice.isConnected).thenReturn(false)
+ audioManager.setMode(AudioManager.MODE_NORMAL)
+
+ assertThat(connectedDeviceItemFactory.isFilterMatched(context, cachedDevice, audioManager))
+ .isFalse()
+ }
+
private fun assertDeviceItem(deviceItem: DeviceItem?, deviceItemType: DeviceItemType) {
assertThat(deviceItem).isNotNull()
assertThat(deviceItem!!.type).isEqualTo(deviceItemType)
@@ -83,5 +317,7 @@ class DeviceItemFactoryTest : SysuiTestCase() {
companion object {
const val DEVICE_NAME = "DeviceName"
const val CONNECTION_SUMMARY = "ConnectionSummary"
+ private const val FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name"
+ private const val DEVICE_ADDRESS = "04:52:C7:0B:D8:3C"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
index e236f4a7730f..ddf0b9a78165 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/DeviceItemInteractorTest.kt
@@ -279,6 +279,7 @@ class DeviceItemInteractorTest : SysuiTestCase() {
): DeviceItemFactory {
return object : DeviceItemFactory() {
override fun isFilterMatched(
+ context: Context,
cachedDevice: CachedBluetoothDevice,
audioManager: AudioManager?
) = isFilterMatchFunc(cachedDevice)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
index fbb77cdc3049..25dd9fedba7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
@@ -34,7 +34,6 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import java.util.concurrent.CompletableFuture
-import java.util.function.Supplier
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
@@ -46,8 +45,6 @@ class SaveImageInBackgroundTaskTest : SysuiTestCase() {
private val smartActions = mock<ScreenshotSmartActions>()
private val smartActionsProvider = mock<ScreenshotNotificationSmartActionsProvider>()
private val saveImageData = SaveImageInBackgroundData()
- private val sharedTransitionSupplier =
- mock<Supplier<ScreenshotController.SavedImageData.ActionTransition>>()
private val testScreenshotId: String = "testScreenshotId"
private val testBitmap = mock<Bitmap>()
private val testUser = UserHandle.getUserHandleForUid(0)
@@ -88,7 +85,6 @@ class SaveImageInBackgroundTaskTest : SysuiTestCase() {
imageExporter,
smartActions,
saveImageData,
- sharedTransitionSupplier,
smartActionsProvider,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 85c8ba7e77b3..2a9aca7f5a35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -20,8 +20,6 @@ import static com.android.systemui.screenshot.ScreenshotNotificationSmartActions
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doThrow;
@@ -32,19 +30,15 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
-import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
-import android.os.Bundle;
import android.os.Handler;
-import android.os.Looper;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import org.junit.Before;
import org.junit.Test;
@@ -84,7 +78,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
ScreenshotNotificationSmartActionsProvider smartActionsProvider = mock(
ScreenshotNotificationSmartActionsProvider.class);
when(smartActionsProvider.getActions(any(), any(), any(), any(), any(), any()))
- .thenThrow(RuntimeException.class);
+ .thenThrow(RuntimeException.class);
CompletableFuture<List<Notification.Action>> smartActionsFuture =
mScreenshotSmartActions.getSmartActionsFuture(
"", Uri.parse("content://authority/data"), bitmap, smartActionsProvider,
@@ -166,89 +160,4 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
assertEquals(smartActions.size(), 0);
}
-
- // Tests for share action extras
- @Test
- public void testShareActionExtras() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
- ScreenshotController.SaveImageInBackgroundData
- data = new ScreenshotController.SaveImageInBackgroundData();
- data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
- data.finisher = null;
- data.mActionsReadyListener = null;
- SaveImageInBackgroundTask task =
- new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
- ActionTransition::new, mSmartActionsProvider);
-
- Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
- Uri.parse("Screenshot_123.png"), true).get().action;
-
- Intent intent = shareAction.actionIntent.getIntent();
- assertNotNull(intent);
- Bundle bundle = intent.getExtras();
- assertTrue(bundle.containsKey(ScreenshotController.EXTRA_ID));
- assertTrue(bundle.containsKey(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED));
- assertEquals(ScreenshotController.ACTION_TYPE_SHARE, shareAction.title);
- assertEquals(Intent.ACTION_SEND, intent.getAction());
- }
-
- // Tests for edit action extras
- @Test
- public void testEditActionExtras() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
- ScreenshotController.SaveImageInBackgroundData
- data = new ScreenshotController.SaveImageInBackgroundData();
- data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
- data.finisher = null;
- data.mActionsReadyListener = null;
- SaveImageInBackgroundTask task =
- new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
- ActionTransition::new, mSmartActionsProvider);
-
- Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
- Uri.parse("Screenshot_123.png"), true).get().action;
-
- Intent intent = editAction.actionIntent.getIntent();
- assertNotNull(intent);
- Bundle bundle = intent.getExtras();
- assertTrue(bundle.containsKey(ScreenshotController.EXTRA_ID));
- assertTrue(bundle.containsKey(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED));
- assertEquals(ScreenshotController.ACTION_TYPE_EDIT, editAction.title);
- assertEquals(Intent.ACTION_EDIT, intent.getAction());
- }
-
- // Tests for share action extras
- @Test
- public void testDeleteActionExtras() {
- if (Looper.myLooper() == null) {
- Looper.prepare();
- }
-
- ScreenshotController.SaveImageInBackgroundData
- data = new ScreenshotController.SaveImageInBackgroundData();
- data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
- data.finisher = null;
- data.mActionsReadyListener = null;
- SaveImageInBackgroundTask task =
- new SaveImageInBackgroundTask(mContext, null, null, mScreenshotSmartActions, data,
- ActionTransition::new, mSmartActionsProvider);
-
- Notification.Action deleteAction = task.createDeleteAction(mContext,
- mContext.getResources(),
- Uri.parse("Screenshot_123.png"), true);
-
- Intent intent = deleteAction.actionIntent.getIntent();
- assertNotNull(intent);
- Bundle bundle = intent.getExtras();
- assertTrue(bundle.containsKey(ScreenshotController.EXTRA_ID));
- assertTrue(bundle.containsKey(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED));
- assertEquals(deleteAction.title, ScreenshotController.ACTION_TYPE_DELETE);
- assertNull(intent.getAction());
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 950a9dbc2ff3..fd7b1399d03f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -599,6 +599,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
// Primary Bouncer->Gone
when(mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha())
.thenReturn(emptyFlow());
+ when(mPrimaryBouncerToGoneTransitionViewModel.getNotificationAlpha())
+ .thenReturn(emptyFlow());
NotificationsKeyguardViewStateRepository notifsKeyguardViewStateRepository =
new NotificationsKeyguardViewStateRepository();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
index 7737b4313e3c..651006dfc953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
@@ -1,15 +1,46 @@
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.shade.transition
+import android.platform.test.annotations.DisableFlags
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.FakeSceneDataSource
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.shade.STATE_OPENING
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.panelExpansionInteractor
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -29,32 +60,95 @@ class ShadeTransitionControllerTest : SysuiTestCase() {
private val configurationController = FakeConfigurationController()
private val shadeExpansionStateManager = ShadeExpansionStateManager()
+ private val kosmos = testKosmos()
+ private lateinit var testScope: TestScope
+ private lateinit var applicationScope: CoroutineScope
+ private lateinit var panelExpansionInteractor: PanelExpansionInteractor
+ private lateinit var deviceEntryRepository: FakeDeviceEntryRepository
+ private lateinit var deviceUnlockedInteractor: DeviceUnlockedInteractor
+ private lateinit var sceneInteractor: SceneInteractor
+ private lateinit var fakeSceneDataSource: FakeSceneDataSource
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ testScope = kosmos.testScope
+ applicationScope = kosmos.applicationCoroutineScope
+ panelExpansionInteractor = kosmos.panelExpansionInteractor
+ deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+ deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor
+ sceneInteractor = kosmos.sceneInteractor
+ fakeSceneDataSource = kosmos.fakeSceneDataSource
+
controller =
ShadeTransitionController(
+ applicationScope,
configurationController,
shadeExpansionStateManager,
dumpManager,
context,
scrimShadeTransitionController,
statusBarStateController,
- ResourcesSplitShadeStateController()
- )
+ ResourcesSplitShadeStateController(),
+ ) {
+ panelExpansionInteractor
+ }
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
fun onPanelStateChanged_forwardsToScrimTransitionController() {
- startPanelExpansion()
+ startLegacyPanelExpansion()
verify(scrimShadeTransitionController).onPanelStateChanged(STATE_OPENING)
verify(scrimShadeTransitionController).onPanelExpansionChanged(DEFAULT_EXPANSION_EVENT)
}
- private fun startPanelExpansion() {
+ @Test
+ @EnableSceneContainer
+ fun sceneChanges_forwardsToScrimTransitionController() =
+ testScope.runTest {
+ var latestChangeEvent: ShadeExpansionChangeEvent? = null
+ whenever(scrimShadeTransitionController.onPanelExpansionChanged(any())).thenAnswer {
+ latestChangeEvent = it.arguments[0] as ShadeExpansionChangeEvent
+ Unit
+ }
+ setUnlocked(true)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(SceneKey.Gone)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+
+ changeScene(SceneKey.Gone, transitionState)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ assertThat(currentScene).isEqualTo(SceneKey.Gone)
+
+ assertThat(latestChangeEvent)
+ .isEqualTo(
+ ShadeExpansionChangeEvent(
+ fraction = 0f,
+ expanded = false,
+ tracking = true,
+ dragDownPxAmount = 0f,
+ )
+ )
+
+ changeScene(SceneKey.Shade, transitionState) { progress ->
+ assertThat(latestChangeEvent)
+ .isEqualTo(
+ ShadeExpansionChangeEvent(
+ fraction = progress,
+ expanded = progress > 0,
+ tracking = true,
+ dragDownPxAmount = 0f,
+ )
+ )
+ }
+ }
+
+ private fun startLegacyPanelExpansion() {
shadeExpansionStateManager.onPanelExpansionChanged(
DEFAULT_EXPANSION_EVENT.fraction,
DEFAULT_EXPANSION_EVENT.expanded,
@@ -63,6 +157,52 @@ class ShadeTransitionControllerTest : 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>,
+ assertDuringProgress: ((progress: Float) -> Unit) = {},
+ ) {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val progressFlow = MutableStateFlow(0f)
+ transitionState.value =
+ ObservableTransitionState.Transition(
+ fromScene = checkNotNull(currentScene),
+ toScene = toScene,
+ progress = progressFlow,
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ )
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ progressFlow.value = 0.2f
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ progressFlow.value = 0.6f
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ progressFlow.value = 1f
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ transitionState.value = ObservableTransitionState.Idle(toScene)
+ fakeSceneDataSource.changeScene(toScene)
+ runCurrent()
+ assertDuringProgress(progressFlow.value)
+
+ assertThat(currentScene).isEqualTo(toScene)
+ }
+
companion object {
private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f
private val DEFAULT_EXPANSION_EVENT =
@@ -70,6 +210,7 @@ class ShadeTransitionControllerTest : SysuiTestCase() {
fraction = 0.5f,
expanded = true,
tracking = true,
- dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT)
+ dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 460892af1154..660e8da72bf1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -49,6 +49,7 @@ import android.window.TransitionInfo;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.RemoteAnimationTargetCompat;
import com.android.wm.shell.shared.TransitionUtil;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index b958f35c0e53..a41bc0d87615 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -616,7 +616,7 @@ fun validMobileEvent(
dataType: SignalIcon.MobileIconGroup? = THREE_G,
subId: Int? = 1,
carrierId: Int? = UNKNOWN_CARRIER_ID,
- inflateStrength: Boolean? = false,
+ inflateStrength: Boolean = false,
activity: Int? = null,
carrierNetworkChange: Boolean = false,
roaming: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
index d465b47e93b9..c07f289dd4fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt
@@ -239,7 +239,9 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() {
// WHEN all of the connections are OOS
i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
i2.isInService.value = false
+ i2.isEmergencyOnly.value = false
// THEN the value is propagated to this interactor
assertThat(latest).isTrue()
@@ -256,6 +258,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() {
// WHEN all of the connections are OOS
i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
// THEN the value is propagated to this interactor
assertThat(latest).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index cd0652e53657..ec6642de839d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -80,6 +80,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
// GIVEN all icons are OOS
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
// GIVEN apm is disabled
airplaneModeRepository.setIsAirplaneMode(false)
@@ -99,6 +100,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
// GIVEN all icons are not OOS
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
i1.isInService.value = true
+ i1.isEmergencyOnly.value = false
// GIVEN apm is disabled
airplaneModeRepository.setIsAirplaneMode(false)
@@ -108,6 +110,35 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
}
@Test
+ fun icon_nullWhenShouldNotShow_isEmergencyOnly() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN satellite is allowed
+ repo.isSatelliteAllowedForCurrentLocation.value = true
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // Wait for delay to be completed
+ advanceTimeBy(10.seconds)
+
+ // THEN icon is set because we don't have service
+ assertThat(latest).isInstanceOf(Icon::class.java)
+
+ // GIVEN the connection is emergency only
+ i1.isEmergencyOnly.value = true
+
+ // THEN icon is null because we have emergency connection
+ assertThat(latest).isNull()
+ }
+
+ @Test
fun icon_nullWhenShouldNotShow_apmIsEnabled() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
@@ -118,6 +149,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
// GIVEN all icons are OOS
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
// GIVEN apm is enabled
airplaneModeRepository.setIsAirplaneMode(true)
@@ -138,6 +170,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
// GIVEN all icons are OOS
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
// GIVEN apm is disabled
airplaneModeRepository.setIsAirplaneMode(false)
@@ -161,6 +194,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
// GIVEN all icons are OOS
val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
// GIVEN apm is disabled
airplaneModeRepository.setIsAirplaneMode(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
index 23a9207ec643..ed07ad2a0976 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
@@ -21,21 +21,40 @@ import static org.junit.Assert.assertTrue;
import android.os.Build;
import android.os.PowerManager;
+import android.platform.test.flag.junit.FlagsParameterization;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
+import org.junit.Assume;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.List;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
public class WakeLockTest extends SysuiTestCase {
+ @Parameterized.Parameters(name = "{0}")
+ public static List<FlagsParameterization> getFlags() {
+ return FlagsParameterization.allCombinationsOf(
+ Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD);
+ }
+
+ @Rule public final SetFlagsRule mSetFlagsRule;
+
+ public WakeLockTest(FlagsParameterization flags) {
+ mSetFlagsRule = new SetFlagsRule(SetFlagsRule.DefaultInitValueType.NULL_DEFAULT, flags);
+ }
+
private static final String WHY = "test";
WakeLock mWakeLock;
PowerManager.WakeLock mInner;
@@ -91,10 +110,7 @@ public class WakeLockTest extends SysuiTestCase {
@Test
public void prodBuild_wakeLock_releaseWithoutAcquire_noThrow() {
- if (Build.IS_ENG) {
- return;
- }
-
+ Assume.assumeFalse(Build.IS_ENG);
// shouldn't throw an exception on production builds
mWakeLock.release(WHY);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index d2e03861b022..3a6324d3de53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -61,7 +61,6 @@ import android.widget.ImageButton;
import android.widget.SeekBar;
import androidx.test.core.view.MotionEventBuilder;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.jank.InteractionJankMonitor;
@@ -502,7 +501,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
}
@Test
- @FlakyTest(bugId = 326204750)
public void dialogDestroy_removesPostureControllerCallback() {
verify(mPostureController, never()).removeCallback(any());
mDialog.destroy();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index a9308601a314..fbefb0eedfa8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -609,6 +609,7 @@ public class BubblesTest extends SysuiTestCase {
mSysUiState,
mFeatureFlags,
mNotifPipelineFlags,
+ syncExecutor,
syncExecutor);
mBubblesManager.addNotifCallback(mNotifCallback);
@@ -651,7 +652,7 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
- public void dreamingHidesBubbles() throws RemoteException {
+ public void bubblesHiddenWhileDreaming() throws RemoteException {
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.VISIBLE);
@@ -662,7 +663,17 @@ public class BubblesTest extends SysuiTestCase {
mKeyguardStateControllerCallbackCaptor.getValue();
callback.onKeyguardShowingChanged();
+ // Dreaming should hide bubbles
assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // Finish dreaming should show bubbles
+ mNotificationShadeWindowController.setDreaming(false);
+ when(mIDreamManager.isDreamingOrInPreview()).thenReturn(false); // dreaming finished
+
+ // Dreaming updates come through mNotificationShadeWindowController
+ mNotificationShadeWindowController.notifyStateChangedCallbacks();
+
+ assertThat(mBubbleController.getStackView().getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
index f6ddfccfa532..185deea31747 100644
--- a/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/ContextKosmos.kt
@@ -20,5 +20,6 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.util.mockito.mock
-var Kosmos.applicationContext: Context by Kosmos.Fixture { testCase.context }
+var Kosmos.applicationContext: Context by
+ Kosmos.Fixture { testCase.context.apply { ensureTestableResources() } }
val Kosmos.mockedContext: Context by Kosmos.Fixture { mock<Context>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
index 77f501f550d7..68ef55573dc8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
@@ -33,6 +33,10 @@ class FakeFacePropertyRepository : FacePropertyRepository {
override val sensorLocation: StateFlow<Point?>
get() = faceSensorLocation
+ private val currentCameraInfo = MutableStateFlow<CameraInfo?>(null)
+ override val cameraInfo: StateFlow<CameraInfo?>
+ get() = currentCameraInfo
+
fun setLockoutMode(userId: Int, mode: LockoutMode) {
lockoutModesForUser[userId] = mode
}
@@ -47,4 +51,8 @@ class FakeFacePropertyRepository : FacePropertyRepository {
fun setSensorLocation(value: Point?) {
faceSensorLocation.value = value
}
+
+ fun setCameraIno(value: CameraInfo?) {
+ currentCameraInfo.value = value
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
index 5038285aef00..974a11cfaf77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/classifier/FalsingManagerFake.java
@@ -201,4 +201,13 @@ public class FalsingManagerFake implements FalsingManager {
public List<FalsingTapListener> getTapListeners() {
return mTapListeners;
}
+
+ /**
+ * Calls every registered {@link FalsingBeliefListener} as if false touch occurred.
+ */
+ public void sendFalsingBelief() {
+ for (FalsingBeliefListener listener : mFalsingBeliefListeners) {
+ listener.onFalse();
+ }
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index ecf66a297f0d..8ca53e6591c0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -42,6 +42,10 @@ val Kosmos.keyguardRootViewModel by Fixture {
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
+ goneToAodTransitionViewModel = goneToAodTransitionViewModel,
+ goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
+ lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
+ lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 96de4bae63d4..8da5dd46b62a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -20,6 +20,7 @@ import com.android.systemui.biometrics.authController
import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.splitShadeStateController
val Kosmos.lockscreenContentViewModel by
Kosmos.Fixture {
@@ -28,5 +29,6 @@ val Kosmos.lockscreenContentViewModel by
interactor = keyguardBlueprintInteractor,
authController = authController,
longPress = keyguardLongPressViewModel,
+ splitShadeStateController = splitShadeStateController,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/SpatializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/SpatializerKosmos.kt
new file mode 100644
index 000000000000..7001ea8b6427
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/SpatializerKosmos.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.media
+
+import com.android.settingslib.media.domain.interactor.SpatializerInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.data.repository.FakeSpatializerRepository
+
+val Kosmos.spatializerRepository by Kosmos.Fixture { FakeSpatializerRepository() }
+val Kosmos.spatializerInteractor by Kosmos.Fixture { SpatializerInteractor(spatializerRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
new file mode 100644
index 000000000000..0183b97090dc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.data.repository
+
+import android.media.AudioDeviceAttributes
+import com.android.settingslib.media.data.repository.SpatializerRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeSpatializerRepository : SpatializerRepository {
+
+ var defaultSpatialAudioAvailable: Boolean = false
+
+ private val spatialAudioAvailabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> =
+ mutableMapOf()
+ private val spatialAudioCompatibleDevices: MutableList<AudioDeviceAttributes> = mutableListOf()
+
+ private val mutableHeadTrackingAvailable = MutableStateFlow(false)
+ private val headTrackingEnabledByDevice = mutableMapOf<AudioDeviceAttributes, Boolean>()
+
+ override val isHeadTrackingAvailable: StateFlow<Boolean> =
+ mutableHeadTrackingAvailable.asStateFlow()
+
+ override suspend fun isSpatialAudioAvailableForDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ): Boolean =
+ spatialAudioAvailabilityByDevice.getOrDefault(
+ audioDeviceAttributes,
+ defaultSpatialAudioAvailable
+ )
+
+ override suspend fun getSpatialAudioCompatibleDevices(): Collection<AudioDeviceAttributes> =
+ spatialAudioCompatibleDevices
+
+ override suspend fun addSpatialAudioCompatibleDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ) {
+ spatialAudioCompatibleDevices.add(audioDeviceAttributes)
+ }
+
+ override suspend fun removeSpatialAudioCompatibleDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ) {
+ spatialAudioCompatibleDevices.remove(audioDeviceAttributes)
+ }
+
+ override suspend fun isHeadTrackingEnabled(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ): Boolean = headTrackingEnabledByDevice.getOrDefault(audioDeviceAttributes, false)
+
+ override suspend fun setHeadTrackingEnabled(
+ audioDeviceAttributes: AudioDeviceAttributes,
+ isEnabled: Boolean
+ ) {
+ headTrackingEnabledByDevice[audioDeviceAttributes] = isEnabled
+ }
+
+ fun setIsSpatialAudioAvailable(
+ audioDeviceAttributes: AudioDeviceAttributes,
+ isAvailable: Boolean,
+ ) {
+ spatialAudioAvailabilityByDevice[audioDeviceAttributes] = isAvailable
+ }
+
+ fun setIsHeadTrackingAvailable(isAvailable: Boolean) {
+ mutableHeadTrackingAvailable.value = isAvailable
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.kt
new file mode 100644
index 000000000000..a025846f74a3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/PanelExpansionInteractorKosmos.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.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.scene.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.data.repository.shadeRepository
+
+val Kosmos.panelExpansionInteractor by Fixture {
+ PanelExpansionInteractor(
+ sceneInteractor = sceneInteractor,
+ shadeRepository = shadeRepository,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index c01032757bb0..11acf1c9ff64 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -60,6 +60,8 @@ class FakeMobileIconInteractor(
override val isInService = MutableStateFlow(true)
+ override val isEmergencyOnly = MutableStateFlow(true)
+
override val isNonTerrestrial = MutableStateFlow(false)
private val _isDataEnabled = MutableStateFlow(true)
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 6b6736476210..371c3acab144 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -55,3 +55,5 @@ class android.content.Context stub
method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; stub
class android.content.pm.PackageManager stub
method <init> ()V stub
+class android.text.ClipboardManager stub
+ method <init> ()V stub
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
index 3668b03e58d3..c17d0903f856 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -16,18 +16,28 @@
package android.platform.test.ravenwood;
+import android.content.ClipboardManager;
import android.content.Context;
import android.hardware.ISerialManager;
import android.hardware.SerialManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
import android.os.PermissionEnforcer;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.test.mock.MockContext;
import android.util.ArrayMap;
import android.util.Singleton;
+import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
public class RavenwoodContext extends MockContext {
+ private final String mPackageName;
+ private final HandlerThread mMainThread;
+
private final RavenwoodPermissionEnforcer mEnforcer = new RavenwoodPermissionEnforcer();
private final ArrayMap<Class<?>, String> mClassToName = new ArrayMap<>();
@@ -39,7 +49,13 @@ public class RavenwoodContext extends MockContext {
mNameToFactory.put(serviceName, serviceSupplier);
}
- public RavenwoodContext() {
+ public RavenwoodContext(String packageName, HandlerThread mainThread) {
+ mPackageName = packageName;
+ mMainThread = mainThread;
+
+ registerService(ClipboardManager.class,
+ Context.CLIPBOARD_SERVICE, asSingleton(() ->
+ new ClipboardManager(this, getMainThreadHandler())));
registerService(PermissionEnforcer.class,
Context.PERMISSION_ENFORCER_SERVICE, () -> mEnforcer);
registerService(SerialManager.class,
@@ -73,18 +89,79 @@ public class RavenwoodContext extends MockContext {
}
}
+ @Override
+ public Looper getMainLooper() {
+ Objects.requireNonNull(mMainThread,
+ "Test must request setProvideMainThread() via RavenwoodRule");
+ return mMainThread.getLooper();
+ }
+
+ @Override
+ public Handler getMainThreadHandler() {
+ Objects.requireNonNull(mMainThread,
+ "Test must request setProvideMainThread() via RavenwoodRule");
+ return mMainThread.getThreadHandler();
+ }
+
+ @Override
+ public Executor getMainExecutor() {
+ Objects.requireNonNull(mMainThread,
+ "Test must request setProvideMainThread() via RavenwoodRule");
+ return mMainThread.getThreadExecutor();
+ }
+
+ @Override
+ public String getPackageName() {
+ return Objects.requireNonNull(mPackageName,
+ "Test must request setPackageName() via RavenwoodRule");
+ }
+
+ @Override
+ public String getOpPackageName() {
+ return Objects.requireNonNull(mPackageName,
+ "Test must request setPackageName() via RavenwoodRule");
+ }
+
+ @Override
+ public String getAttributionTag() {
+ return null;
+ }
+
+ @Override
+ public UserHandle getUser() {
+ return android.os.UserHandle.of(android.os.UserHandle.myUserId());
+ }
+
+ @Override
+ public int getUserId() {
+ return android.os.UserHandle.myUserId();
+ }
+
+ @Override
+ public int getDeviceId() {
+ return Context.DEVICE_ID_DEFAULT;
+ }
+
/**
* Wrap the given {@link Supplier} to become a memoized singleton.
*/
- private static <T> Supplier<T> asSingleton(Supplier<T> supplier) {
+ private static <T> Supplier<T> asSingleton(ThrowingSupplier<T> supplier) {
final Singleton<T> singleton = new Singleton<>() {
@Override
protected T create() {
- return supplier.get();
+ try {
+ return supplier.get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
};
return () -> {
return singleton.get();
};
}
+
+ public interface ThrowingSupplier<T> {
+ T get() throws Exception;
+ }
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 231cce95f353..56a3c64a5750 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -110,13 +110,16 @@ public class RavenwoodRuleImpl {
ActivityManager.init$ravenwood(rule.mCurrentUser);
+ final HandlerThread main;
if (rule.mProvideMainThread) {
- final HandlerThread main = new HandlerThread(MAIN_THREAD_NAME);
+ main = new HandlerThread(MAIN_THREAD_NAME);
main.start();
Looper.setMainLooperForTest(main.getLooper());
+ } else {
+ main = null;
}
- rule.mContext = new RavenwoodContext();
+ rule.mContext = new RavenwoodContext(rule.mPackageName, main);
rule.mInstrumentation = new Instrumentation();
rule.mInstrumentation.basicInit(rule.mContext);
InstrumentationRegistry.registerInstance(rule.mInstrumentation, Bundle.EMPTY);
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
index bb280f47ccd9..3de96c0990ea 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -16,6 +16,7 @@
package android.platform.test.ravenwood;
+import android.content.ClipboardManager;
import android.hardware.SerialManager;
import android.os.SystemClock;
import android.util.ArrayMap;
@@ -40,7 +41,10 @@ public class RavenwoodSystemServer {
// authors to exhaustively declare all transitive services
static {
- sKnownServices.put(SerialManager.class, "com.android.server.SerialService$Lifecycle");
+ sKnownServices.put(ClipboardManager.class,
+ "com.android.server.FakeClipboardService$Lifecycle");
+ sKnownServices.put(SerialManager.class,
+ "com.android.server.SerialService$Lifecycle");
}
private static TimingsTraceAndSlog sTimings;
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index a8c24fcbd7e0..a520d4ccafa1 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -121,6 +121,8 @@ public class RavenwoodRule implements TestRule {
int mUid = NOBODY_UID;
int mPid = sNextPid.getAndIncrement();
+ String mPackageName;
+
boolean mProvideMainThread = false;
final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
@@ -158,6 +160,15 @@ public class RavenwoodRule implements TestRule {
}
/**
+ * Configure the identity of this process to be the given package name for the duration
+ * of the test. Has no effect on non-Ravenwood environments.
+ */
+ public Builder setPackageName(/* @NonNull */ String packageName) {
+ mRule.mPackageName = Objects.requireNonNull(packageName);
+ return this;
+ }
+
+ /**
* Configure a "main" thread to be available for the duration of the test, as defined
* by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
*/
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index eb3c55cb4ff6..9b4d378cc7b7 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -186,6 +186,7 @@ android.os.WorkSource
android.content.ClipData
android.content.ClipData$Item
android.content.ClipDescription
+android.content.ClipboardManager
android.content.ComponentName
android.content.ContentUris
android.content.ContentValues
diff --git a/rs/java/android/renderscript/ScriptC.java b/rs/java/android/renderscript/ScriptC.java
index 4a2f3da0eb06..e9f6031e5048 100644
--- a/rs/java/android/renderscript/ScriptC.java
+++ b/rs/java/android/renderscript/ScriptC.java
@@ -38,12 +38,12 @@ public class ScriptC extends Script {
private static final String TAG = "ScriptC";
/**
- * In targetSdkVersion 35 and above, Renderscript's ScriptC stops being supported
+ * In targetSdkVersion 36 and above, Renderscript's ScriptC stops being supported
* and an exception is thrown when the class is instantiated.
- * In targetSdkVersion 34 and below, Renderscript's ScriptC still works.
+ * In targetSdkVersion 35 and below, Renderscript's ScriptC still works.
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = 35)
+ @EnabledAfter(targetSdkVersion = 36)
private static final long RENDERSCRIPT_SCRIPTC_DEPRECATION_CHANGE_ID = 297019750L;
/**
@@ -113,9 +113,9 @@ public class ScriptC extends Script {
throw new UnsupportedOperationException(s);
}
- // Throw an exception if the target API is 35 or above
+ // Throw an exception if the target API is 36 or above
String message =
- "ScriptC scripts are not supported when targeting an API Level >= 35. Please refer "
+ "ScriptC scripts are not supported when targeting an API Level >= 36. Please refer "
+ "to https://developer.android.com/guide/topics/renderscript/migration-guide "
+ "for proposed alternatives.";
Slog.w(TAG, message);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 16119d11ee1e..54e545d6d73a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility;
+import static android.view.InputDevice.SOURCE_CLASS_POINTER;
+import static android.view.MotionEvent.ACTION_SCROLL;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
@@ -425,6 +427,12 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
boolean shouldProcessMultiDeviceEvent(InputEvent event, int policyFlags) {
if (event instanceof MotionEvent motion) {
+ if (!motion.isFromSource(SOURCE_CLASS_POINTER) || motion.getAction() == ACTION_SCROLL) {
+ // Non-pointer events are focus-dispatched and don't require special logic.
+ // Scroll events are stand-alone and therefore can be considered to not be part of
+ // a stream.
+ return true;
+ }
// Only allow 1 device to be sending motion events at a time
// If the event is from an active device, let it through.
// If the event is not from an active device, only let it through if it starts a new
@@ -481,7 +489,7 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
private void processMotionEvent(EventStreamState state, MotionEvent event, int policyFlags) {
- if (!state.shouldProcessScroll() && event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
+ if (!state.shouldProcessScroll() && event.getActionMasked() == ACTION_SCROLL) {
super.onInputEvent(event, policyFlags);
return;
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index c7b844b991c4..d779fbf2eabc 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -193,7 +193,6 @@ import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.PrintWriter;
-import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -2821,8 +2820,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mSessionFlags.mExpiredResponse = false;
final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
- final Serializable exception = data.getSerializable(
- CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION);
+ final GetCredentialException exception = data.getSerializable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION,
+ GetCredentialException.class);
final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
if (sDebug) {
@@ -5126,6 +5126,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
toIpcFriendlyResultReceiver(resultReceiver);
Intent metadataIntent = dataset.getCredentialFillInIntent();
+ if (metadataIntent == null) {
+ metadataIntent = new Intent();
+ }
+
metadataIntent.putExtra(
android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER,
ipcFriendlyResultReceiver);
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index d0eb59d83f5a..1dab40ea5876 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -25,12 +25,12 @@ import static android.content.ComponentName.createRelative;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
-import static com.android.server.companion.MetricUtils.logCreateAssociation;
-import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature;
-import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation;
-import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation;
-import static com.android.server.companion.RolesUtils.isRoleHolder;
-import static com.android.server.companion.Utils.prepareForIpc;
+import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
+import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
+import static com.android.server.companion.utils.PermissionsUtils.enforcePermissionForCreatingAssociation;
+import static com.android.server.companion.utils.RolesUtils.addRoleHolderForAssociation;
+import static com.android.server.companion.utils.RolesUtils.isRoleHolder;
+import static com.android.server.companion.utils.Utils.prepareForIpc;
import static java.util.Objects.requireNonNull;
@@ -59,6 +59,7 @@ import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.R;
+import com.android.server.companion.utils.PackageUtils;
import java.util.List;
@@ -167,7 +168,7 @@ class AssociationRequestsProcessor {
}
// 1. Enforce permissions and other requirements.
- enforcePermissionsForAssociation(mContext, request, packageUid);
+ enforcePermissionForCreatingAssociation(mContext, request, packageUid);
enforceUsesCompanionDeviceFeature(mContext, userId, packageName);
// 2a. Check if association can be created without launching UI (i.e. CDM needs NEITHER
@@ -257,7 +258,7 @@ class AssociationRequestsProcessor {
// 1. Need to check permissions again in case something changed, since we first received
// this request.
try {
- enforcePermissionsForAssociation(mContext, request, packageUid);
+ enforcePermissionForCreatingAssociation(mContext, request, packageUid);
} catch (SecurityException e) {
// Since, at this point the caller is our own UI, we need to catch the exception on
// forward it back to the application via the callback.
@@ -316,6 +317,9 @@ class AssociationRequestsProcessor {
// If it is null, then the operation will succeed without granting any role.
addRoleHolderForAssociation(mService.getContext(), association, success -> {
if (success) {
+ Slog.i(TAG, "Added " + association.getDeviceProfile() + " role to userId="
+ + association.getUserId() + ", packageName="
+ + association.getPackageName());
addAssociationToStore(association);
sendCallbackAndFinish(association, callback, resultReceiver);
} else {
diff --git a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java b/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
index de6382e316df..10963ea37e8e 100644
--- a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
@@ -20,8 +20,8 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIB
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
import static com.android.internal.util.CollectionUtils.any;
-import static com.android.server.companion.MetricUtils.logRemoveAssociation;
-import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;
+import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation;
+import static com.android.server.companion.utils.RolesUtils.removeRoleHolderForAssociation;
import static com.android.server.companion.CompanionDeviceManagerService.PerUserAssociationSet;
import android.annotation.NonNull;
@@ -203,7 +203,8 @@ public class AssociationRevokeProcessor {
return false;
}
- removeRoleHolderForAssociation(mContext, association);
+ removeRoleHolderForAssociation(mContext, association.getUserId(),
+ association.getPackageName(), association.getDeviceProfile());
return true;
}
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index af0777c74605..559ebbc290f6 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -38,6 +38,9 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.PerUser;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.ObservableUuid;
+import com.android.server.companion.presence.ObservableUuidStore;
+import com.android.server.companion.utils.PackageUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -61,7 +64,7 @@ import java.util.Map;
* <ul>
* <li> {@link #bindCompanionApplication(int, String, boolean)}
* <li> {@link #unbindCompanionApplication(int, String)}
- * <li> {@link #notifyCompanionApplicationDevicePresenceEvent(AssociationInfo, int)}
+ * <li> {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)}
* <li> {@link #isCompanionApplicationBound(int, String)}
* <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
* </ul>
@@ -251,7 +254,13 @@ public class CompanionApplicationController {
serviceConnector.connect();
}
- void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) {
+ /**
+ * Notify the app that the device appeared.
+ *
+ * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
+ */
+ @Deprecated
+ public void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) {
final int userId = association.getUserId();
final String packageName = association.getPackageName();
@@ -273,7 +282,13 @@ public class CompanionApplicationController {
primaryServiceConnector.postOnDeviceAppeared(association);
}
- void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) {
+ /**
+ * Notify the app that the device disappeared.
+ *
+ * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
+ */
+ @Deprecated
+ public void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) {
final int userId = association.getUserId();
final String packageName = association.getPackageName();
@@ -295,7 +310,10 @@ public class CompanionApplicationController {
primaryServiceConnector.postOnDeviceDisappeared(association);
}
- void notifyCompanionApplicationDevicePresenceEvent(AssociationInfo association, int event) {
+ /**
+ * Notify the app that the device appeared.
+ */
+ public void notifyCompanionDevicePresenceEvent(AssociationInfo association, int event) {
final int userId = association.getUserId();
final String packageName = association.getPackageName();
final CompanionDeviceServiceConnector primaryServiceConnector =
@@ -318,7 +336,10 @@ public class CompanionApplicationController {
primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
}
- void notifyApplicationDevicePresenceEvent(ObservableUuid uuid, int event) {
+ /**
+ * Notify the app that the device disappeared.
+ */
+ public void notifyUuidDevicePresenceEvent(ObservableUuid uuid, int event) {
final int userId = uuid.getUserId();
final ParcelUuid parcelUuid = uuid.getUuid();
final String packageName = uuid.getPackageName();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 09c77939eb7b..a478a3d84161 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -38,15 +38,15 @@ import static com.android.internal.util.CollectionUtils.any;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
-import static com.android.server.companion.PackageUtils.isRestrictedSettingsAllowed;
-import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature;
-import static com.android.server.companion.PackageUtils.getPackageInfo;
-import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice;
-import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
-import static com.android.server.companion.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
-import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
-import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
-import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
+import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
+import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
+import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
+import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice;
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
+import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.DAYS;
@@ -123,6 +123,8 @@ 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.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.ObservableUuid;
+import com.android.server.companion.presence.ObservableUuidStore;
import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -435,7 +437,7 @@ public class CompanionDeviceManagerService extends SystemService {
bindApplicationIfNeeded(association);
- mCompanionAppController.notifyCompanionApplicationDevicePresenceEvent(
+ mCompanionAppController.notifyCompanionDevicePresenceEvent(
association, event);
break;
case EVENT_BLE_DISAPPEARED:
@@ -446,7 +448,7 @@ public class CompanionDeviceManagerService extends SystemService {
return;
}
if (association.shouldBindWhenPresent()) {
- mCompanionAppController.notifyCompanionApplicationDevicePresenceEvent(
+ mCompanionAppController.notifyCompanionDevicePresenceEvent(
association, event);
}
// Check if there are other devices associated to the app that are present.
@@ -475,7 +477,7 @@ public class CompanionDeviceManagerService extends SystemService {
Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
}
- mCompanionAppController.notifyApplicationDevicePresenceEvent(uuid, event);
+ mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event);
break;
case EVENT_BT_DISCONNECTED:
@@ -484,7 +486,7 @@ public class CompanionDeviceManagerService extends SystemService {
return;
}
- mCompanionAppController.notifyApplicationDevicePresenceEvent(uuid, event);
+ mCompanionAppController.notifyUuidDevicePresenceEvent(uuid, event);
// Check if there are other devices associated to the app or the UUID to be
// observed are present.
if (shouldBindPackage(userId, packageName)) return;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index de4f2b60170f..74b4cabbab67 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -18,7 +18,7 @@ package com.android.server.companion;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
-import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
+import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks;
import android.companion.AssociationInfo;
import android.companion.ContextSyncMessage;
@@ -36,6 +36,7 @@ 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.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.transport.CompanionTransportManager;
import java.io.PrintWriter;
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index 1ebe65c6aa5f..7527efb7b19a 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -27,11 +27,11 @@ import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static com.android.server.companion.CompanionDeviceManagerService.getFirstAssociationIdForUser;
import static com.android.server.companion.CompanionDeviceManagerService.getLastAssociationIdForUser;
-import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
-import static com.android.server.companion.DataStoreUtils.fileToByteArray;
-import static com.android.server.companion.DataStoreUtils.isEndOfTag;
-import static com.android.server.companion.DataStoreUtils.isStartOfTag;
-import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
+import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.utils.DataStoreUtils.fileToByteArray;
+import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag;
+import static com.android.server.companion.utils.DataStoreUtils.isStartOfTag;
+import static com.android.server.companion.utils.DataStoreUtils.writeToFileSafely;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -51,6 +51,7 @@ import android.util.Xml;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.companion.utils.DataStoreUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 260b21f109d0..74236a402244 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -23,7 +23,7 @@ import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSIO
import static android.content.ComponentName.createRelative;
import static android.content.pm.PackageManager.FEATURE_WATCH;
-import static com.android.server.companion.Utils.prepareForIpc;
+import static com.android.server.companion.utils.Utils.prepareForIpc;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -54,9 +54,9 @@ import android.util.Slog;
import com.android.internal.R;
import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionDeviceManagerService;
-import com.android.server.companion.PackageUtils;
-import com.android.server.companion.PermissionsUtils;
import com.android.server.companion.transport.CompanionTransportManager;
+import com.android.server.companion.utils.PackageUtils;
+import com.android.server.companion.utils.PermissionsUtils;
import java.util.List;
import java.util.concurrent.ExecutorService;
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
index c4c80f907b3a..ee816a0c2cc9 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
@@ -22,11 +22,11 @@ import static com.android.internal.util.XmlUtils.readBooleanAttribute;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
-import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
-import static com.android.server.companion.DataStoreUtils.fileToByteArray;
-import static com.android.server.companion.DataStoreUtils.isEndOfTag;
-import static com.android.server.companion.DataStoreUtils.isStartOfTag;
-import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
+import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.utils.DataStoreUtils.fileToByteArray;
+import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag;
+import static com.android.server.companion.utils.DataStoreUtils.isStartOfTag;
+import static com.android.server.companion.utils.DataStoreUtils.writeToFileSafely;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 7e3079073c90..679243496e95 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -34,7 +34,7 @@ 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.CompanionDevicePresenceMonitor.DEBUG;
-import static com.android.server.companion.presence.Utils.btDeviceToString;
+import static com.android.server.companion.utils.Utils.btDeviceToString;
import static java.util.Objects.requireNonNull;
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index c514f3ef29d0..0287f6258c06 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -20,7 +20,7 @@ import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
-import static com.android.server.companion.presence.Utils.btDeviceToString;
+import static com.android.server.companion.utils.Utils.btDeviceToString;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -40,8 +40,6 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.server.companion.AssociationStore;
-import com.android.server.companion.ObservableUuid;
-import com.android.server.companion.ObservableUuidStore;
import java.util.Arrays;
import java.util.Collections;
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 54a4692d964d..caca48d3aa02 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -43,8 +43,6 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.server.companion.AssociationStore;
-import com.android.server.companion.ObservableUuid;
-import com.android.server.companion.ObservableUuidStore;
import java.io.PrintWriter;
import java.util.HashSet;
diff --git a/services/companion/java/com/android/server/companion/ObservableUuid.java b/services/companion/java/com/android/server/companion/presence/ObservableUuid.java
index 6ab3188c8fd2..9cfa2705cb2f 100644
--- a/services/companion/java/com/android/server/companion/ObservableUuid.java
+++ b/services/companion/java/com/android/server/companion/presence/ObservableUuid.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.presence;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
diff --git a/services/companion/java/com/android/server/companion/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
index 94be22afd9e2..ee8b1065b42c 100644
--- a/services/companion/java/com/android/server/companion/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.presence;
import static com.android.internal.util.XmlUtils.readIntAttribute;
import static com.android.internal.util.XmlUtils.readLongAttribute;
@@ -22,10 +22,10 @@ import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
-import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
-import static com.android.server.companion.DataStoreUtils.isEndOfTag;
-import static com.android.server.companion.DataStoreUtils.isStartOfTag;
-import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
+import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag;
+import static com.android.server.companion.utils.DataStoreUtils.isStartOfTag;
+import static com.android.server.companion.utils.DataStoreUtils.writeToFileSafely;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -57,6 +57,9 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+/**
+ * This store manages the cache and disk data for observable uuids.
+ */
public class ObservableUuidStore {
private static final String TAG = "CDM_ObservableUuidStore";
private static final String FILE_NAME = "observing_uuids_presence.xml";
@@ -84,9 +87,9 @@ public class ObservableUuidStore {
}
/**
- * Remove the observable uuid from the disk.
+ * Remove the observable uuid.
*/
- void removeObservableUuid(@UserIdInt int userId, ParcelUuid uuid, String packageName) {
+ public void removeObservableUuid(@UserIdInt int userId, ParcelUuid uuid, String packageName) {
List<ObservableUuid> cachedObservableUuids;
synchronized (mLock) {
@@ -101,7 +104,10 @@ public class ObservableUuidStore {
mExecutor.execute(() -> writeObservableUuidToStore(userId, cachedObservableUuids));
}
- void writeObservableUuid(@UserIdInt int userId, ObservableUuid uuid) {
+ /**
+ * Write the observable uuid.
+ */
+ public void writeObservableUuid(@UserIdInt int userId, ObservableUuid uuid) {
Slog.i(TAG, "Writing uuid=" + uuid.getUuid() + " to store.");
List<ObservableUuid> cachedObservableUuids;
diff --git a/services/companion/java/com/android/server/companion/presence/Utils.java b/services/companion/java/com/android/server/companion/presence/Utils.java
deleted file mode 100644
index 583b443c8cb7..000000000000
--- a/services/companion/java/com/android/server/companion/presence/Utils.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.presence;
-
-import android.annotation.NonNull;
-import android.bluetooth.BluetoothDevice;
-
-/** Utilities for working with Bluetooth and BLE devices. */
-class Utils {
-
- /**
- * @return short String representation of {@link BluetoothDevice}.
- */
- static String btDeviceToString(@NonNull BluetoothDevice btDevice) {
- final StringBuilder sb = new StringBuilder(btDevice.getAddress());
-
- sb.append(" [name=");
- final String name = btDevice.getName();
- if (name != null) {
- sb.append('\'').append(name).append('\'');
- } else {
- sb.append("null");
- }
-
- final String alias = btDevice.getAlias();
- if (alias != null) {
- sb.append(", alias='").append(alias).append("'");
- }
-
- return sb.append(']').toString();
- }
-
- private Utils() {
- }
-}
diff --git a/services/companion/java/com/android/server/companion/DataStoreUtils.java b/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java
index 04ce1f673124..c75b1a57206e 100644
--- a/services/companion/java/com/android/server/companion/DataStoreUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/DataStoreUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.utils;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -80,7 +80,7 @@ public final class DataStoreUtils {
/**
* Writing to file could fail, for example, if the user has been recently removed and so was
- * their DE (/data/system_de/<user-id>/) directory.
+ * their DE (/data/system_de/[user-id]/) directory.
*/
public static void writeToFileSafely(
@NonNull AtomicFile file, @NonNull ThrowingConsumer<FileOutputStream> consumer) {
diff --git a/services/companion/java/com/android/server/companion/MetricUtils.java b/services/companion/java/com/android/server/companion/utils/MetricUtils.java
index cf867b67cbca..8ea5c89116eb 100644
--- a/services/companion/java/com/android/server/companion/MetricUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/MetricUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.utils;
import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
@@ -41,7 +41,7 @@ import android.util.ArrayMap;
import java.util.Map;
-final class MetricUtils {
+public final class MetricUtils {
private static final Map<String, Integer> METRIC_DEVICE_PROFILE;
static {
@@ -75,13 +75,19 @@ final class MetricUtils {
METRIC_DEVICE_PROFILE = unmodifiableMap(map);
}
- static void logCreateAssociation(String profile) {
+ /**
+ * Log association creation
+ */
+ public static void logCreateAssociation(String profile) {
write(CDM_ASSOCIATION_ACTION,
CDM_ASSOCIATION_ACTION__ACTION__CREATED,
METRIC_DEVICE_PROFILE.get(profile));
}
- static void logRemoveAssociation(String profile) {
+ /**
+ * Log association removal
+ */
+ public static void logRemoveAssociation(String profile) {
write(CDM_ASSOCIATION_ACTION,
CDM_ASSOCIATION_ACTION__ACTION__REMOVED,
METRIC_DEVICE_PROFILE.get(profile));
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/utils/PackageUtils.java
index 3aae1ec99f55..d38590e0a251 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PackageUtils.java
@@ -14,16 +14,13 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.utils;
import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
import static android.content.pm.PackageManager.GET_CONFIGURATIONS;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.os.Binder.getCallingUid;
-import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
-import static com.android.server.companion.CompanionDeviceManagerService.TAG;
-
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,13 +41,11 @@ import android.content.pm.ServiceInfo;
import android.content.pm.Signature;
import android.os.Binder;
import android.os.Process;
-import android.util.Log;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -58,16 +53,22 @@ import java.util.Map;
import java.util.Set;
/**
- * Utility methods for working with {@link PackageInfo}-s.
+ * Utility methods for working with {@link PackageInfo}.
*/
public final class PackageUtils {
+
+ private static final String TAG = "CDM_PackageUtils";
+
private static final Intent COMPANION_SERVICE_INTENT =
new Intent(CompanionDeviceService.SERVICE_INTERFACE);
private static final String PROPERTY_PRIMARY_TAG =
"android.companion.PROPERTY_PRIMARY_COMPANION_DEVICE_SERVICE";
+ /**
+ * Get package info
+ */
@Nullable
- static PackageInfo getPackageInfo(@NonNull Context context,
+ public static PackageInfo getPackageInfo(@NonNull Context context,
@UserIdInt int userId, @NonNull String packageName) {
final PackageManager pm = context.getPackageManager();
final PackageInfoFlags flags = PackageInfoFlags.of(GET_PERMISSIONS | GET_CONFIGURATIONS);
@@ -81,26 +82,32 @@ public final class PackageUtils {
});
}
- static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
+ /**
+ * Require the app to declare the companion device feature.
+ */
+ public static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
@UserIdInt int userId, @NonNull String packageName) {
// Allow system server to create CDM associations without FEATURE_COMPANION_DEVICE_SETUP
if (getCallingUid() == Process.SYSTEM_UID) {
return;
}
- String requiredFeature = FEATURE_COMPANION_DEVICE_SETUP;
+ PackageInfo packageInfo = getPackageInfo(context, userId, packageName);
+ if (packageInfo == null) {
+ throw new IllegalArgumentException("Package " + packageName + " doesn't exist.");
+ }
- FeatureInfo[] requestedFeatures = getPackageInfo(context, userId, packageName).reqFeatures;
+ FeatureInfo[] requestedFeatures = packageInfo.reqFeatures;
if (requestedFeatures != null) {
- for (int i = 0; i < requestedFeatures.length; i++) {
- if (requiredFeature.equals(requestedFeatures[i].name)) {
+ for (FeatureInfo requestedFeature : requestedFeatures) {
+ if (FEATURE_COMPANION_DEVICE_SETUP.equals(requestedFeature.name)) {
return;
}
}
}
throw new IllegalStateException("Must declare uses-feature "
- + requiredFeature
+ + FEATURE_COMPANION_DEVICE_SETUP
+ " in manifest to use this API");
}
@@ -109,7 +116,7 @@ public final class PackageUtils {
* Services marked as "primary" would always appear at the head of the lists, *before*
* all non-primary services.
*/
- static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser(
+ public static @NonNull Map<String, List<ComponentName>> getCompanionServicesForUser(
@NonNull Context context, @UserIdInt int userId) {
final PackageManager pm = context.getPackageManager();
final List<ResolveInfo> companionServices = pm.queryIntentServicesAsUser(
@@ -179,9 +186,7 @@ public final class PackageUtils {
final String[] allowlistedPackages = context.getResources()
.getStringArray(com.android.internal.R.array.config_companionDevicePackages);
if (!ArrayUtils.contains(allowlistedPackages, packageName)) {
- if (DEBUG) {
- Log.d(TAG, packageName + " is not allowlisted.");
- }
+ Slog.d(TAG, packageName + " is not allowlisted.");
return false;
}
@@ -212,13 +217,6 @@ public final class PackageUtils {
if (!requestingPackageSignatureAllowlisted) {
Slog.w(TAG, "Certificate mismatch for allowlisted package " + packageName);
- if (DEBUG) {
- Log.d(TAG, " > allowlisted signatures for " + packageName + ": ["
- + String.join(", ", allowlistedSignatureDigestsForRequestingPackage)
- + "]");
- Log.d(TAG, " > actual signatures for " + packageName + ": "
- + Arrays.toString(requestingPackageSignatureDigests));
- }
}
return requestingPackageSignatureAllowlisted;
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index 15bebbae05b1..2cf1f462a7d1 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.utils;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
@@ -75,16 +75,22 @@ public final class PermissionsUtils {
DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
}
- static void enforcePermissionsForAssociation(@NonNull Context context,
+ /**
+ * Require the app to declare necessary permission for creating association.
+ */
+ public static void enforcePermissionForCreatingAssociation(@NonNull Context context,
@NonNull AssociationRequest request, int packageUid) {
- enforceRequestDeviceProfilePermissions(context, request.getDeviceProfile(), packageUid);
+ enforcePermissionForRequestingProfile(context, request.getDeviceProfile(), packageUid);
if (request.isSelfManaged()) {
- enforceRequestSelfManagedPermission(context, packageUid);
+ enforcePermissionForRequestingSelfManaged(context, packageUid);
}
}
- static void enforceRequestDeviceProfilePermissions(
+ /**
+ * Require the app to declare necessary permission for creating association with profile.
+ */
+ public static void enforcePermissionForRequestingProfile(
@NonNull Context context, @Nullable String deviceProfile, int packageUid) {
// Device profile can be null.
if (deviceProfile == null) return;
@@ -101,7 +107,11 @@ public final class PermissionsUtils {
}
}
- static void enforceRequestSelfManagedPermission(@NonNull Context context, int packageUid) {
+ /**
+ * Require the app to declare necessary permission for creating self-managed association.
+ */
+ public static void enforcePermissionForRequestingSelfManaged(@NonNull Context context,
+ int packageUid) {
if (context.checkPermission(REQUEST_COMPANION_SELF_MANAGED, getCallingPid(), packageUid)
!= PERMISSION_GRANTED) {
throw new SecurityException("Application does not hold "
@@ -109,25 +119,39 @@ public final class PermissionsUtils {
}
}
- static boolean checkCallerCanInteractWithUserId(@NonNull Context context, int userId) {
+ /**
+ * Check if the caller can interact with the user.
+ */
+ public static boolean checkCallerCanInteractWithUserId(@NonNull Context context, int userId) {
if (getCallingUserId() == userId) return true;
return context.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
}
- static void enforceCallerCanInteractWithUserId(@NonNull Context context, int userId) {
+ /**
+ * Require the caller to be able to interact with the user.
+ */
+ public static void enforceCallerCanInteractWithUserId(@NonNull Context context, int userId) {
if (getCallingUserId() == userId) return;
context.enforceCallingPermission(INTERACT_ACROSS_USERS, null);
}
- static void enforceCallerIsSystemOrCanInteractWithUserId(@NonNull Context context, int userId) {
+ /**
+ * Require the caller to be system UID or to be able to interact with the user.
+ */
+ public static void enforceCallerIsSystemOrCanInteractWithUserId(@NonNull Context context,
+ int userId) {
if (getCallingUid() == SYSTEM_UID) return;
enforceCallerCanInteractWithUserId(context, userId);
}
- static boolean checkCallerIsSystemOr(@UserIdInt int userId, @NonNull String packageName) {
+ /**
+ * Check if the caller is system UID or the provided user.
+ */
+ public static boolean checkCallerIsSystemOr(@UserIdInt int userId,
+ @NonNull String packageName) {
final int callingUid = getCallingUid();
if (callingUid == SYSTEM_UID) return true;
@@ -158,13 +182,19 @@ public final class PermissionsUtils {
}
}
- static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) {
+ /**
+ * Check if the caller holds the necessary permission to manage companion devices.
+ */
+ public static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) {
if (getCallingUid() == SYSTEM_UID) return true;
return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
}
- static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context,
+ /**
+ * Require the caller to be able to manage the associations for the package.
+ */
+ public static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context,
@UserIdInt int userId, @NonNull String packageName,
@Nullable String actionDescription) {
if (checkCallerCanManageAssociationsForPackage(context, userId, packageName)) return;
@@ -175,7 +205,10 @@ public final class PermissionsUtils {
+ " for u" + userId + "/" + packageName);
}
- static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) {
+ /**
+ * Require the caller to hold necessary permission to observe device presence by UUID.
+ */
+ public static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) {
if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
!= PERMISSION_GRANTED) {
throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
@@ -193,7 +226,7 @@ public final class PermissionsUtils {
* </ul>
* @return whether the caller is one of the above.
*/
- static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context,
+ public static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context,
@UserIdInt int userId, @NonNull String packageName) {
if (checkCallerIsSystemOr(userId, packageName)) return true;
diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/utils/RolesUtils.java
index af9d2d783100..f798e218e8e0 100644
--- a/services/companion/java/com/android/server/companion/RolesUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/RolesUtils.java
@@ -14,13 +14,10 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.utils;
import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP;
-import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
-import static com.android.server.companion.CompanionDeviceManagerService.TAG;
-
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
@@ -29,7 +26,6 @@ import android.companion.AssociationInfo;
import android.content.Context;
import android.os.Binder;
import android.os.UserHandle;
-import android.util.Log;
import android.util.Slog;
import java.util.List;
@@ -37,9 +33,14 @@ import java.util.function.Consumer;
/** Utility methods for accessing {@link RoleManager} APIs. */
@SuppressLint("LongLogTag")
-final class RolesUtils {
+public final class RolesUtils {
+
+ private static final String TAG = "CDM_RolesUtils";
- static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId,
+ /**
+ * Check if the package holds the role.
+ */
+ public static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId,
@NonNull String packageName, @NonNull String role) {
final RoleManager roleManager = context.getSystemService(RoleManager.class);
final List<String> roleHolders = roleManager.getRoleHoldersAsUser(
@@ -58,13 +59,9 @@ final class RolesUtils {
* false if failed. If the association does not have any device profile
* specified, then the operation will always be successful as a no-op.
*/
- static void addRoleHolderForAssociation(
+ public static void addRoleHolderForAssociation(
@NonNull Context context, @NonNull AssociationInfo associationInfo,
@NonNull Consumer<Boolean> roleGrantResult) {
- if (DEBUG) {
- Log.d(TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo);
- }
-
final String deviceProfile = associationInfo.getDeviceProfile();
if (deviceProfile == null) {
// If no device profile is specified, then no-op and resolve callback with success.
@@ -83,33 +80,30 @@ final class RolesUtils {
roleGrantResult);
}
- static void removeRoleHolderForAssociation(
- @NonNull Context context, @NonNull AssociationInfo associationInfo) {
- if (DEBUG) {
- Log.d(TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo);
- }
-
- final String deviceProfile = associationInfo.getDeviceProfile();
+ /**
+ * Remove the role for the package association.
+ */
+ public static void removeRoleHolderForAssociation(
+ @NonNull Context context, int userId, String packageName, String deviceProfile) {
if (deviceProfile == null) return;
final RoleManager roleManager = context.getSystemService(RoleManager.class);
- final String packageName = associationInfo.getPackageName();
- final int userId = associationInfo.getUserId();
final UserHandle userHandle = UserHandle.of(userId);
- Slog.i(TAG, "Removing CDM role holder, role=" + deviceProfile
- + ", package=u" + userId + "\\" + packageName);
+ Slog.i(TAG, "Removing CDM role=" + deviceProfile
+ + " for userId=" + userId + ", packageName=" + packageName);
final long identity = Binder.clearCallingIdentity();
try {
roleManager.removeRoleHolderAsUser(deviceProfile, packageName,
- MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
- success -> {
- if (!success) {
- Slog.e(TAG, "Failed to remove u" + userId + "\\" + packageName
- + " from the list of " + deviceProfile + " holders.");
- }
- });
+ MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
+ success -> {
+ if (!success) {
+ Slog.e(TAG, "Failed to remove userId=" + userId + ", packageName="
+ + packageName + " from the list of " + deviceProfile
+ + " holders.");
+ }
+ });
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/companion/java/com/android/server/companion/Utils.java b/services/companion/java/com/android/server/companion/utils/Utils.java
index b9f61ecd8c4f..8302997a5705 100644
--- a/services/companion/java/com/android/server/companion/Utils.java
+++ b/services/companion/java/com/android/server/companion/utils/Utils.java
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.utils;
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.os.ResultReceiver;
@@ -43,5 +45,27 @@ public final class Utils {
return ipcFriendly;
}
+ /**
+ * Return a human-readable string for the BluetoothDevice.
+ */
+ public static String btDeviceToString(@NonNull BluetoothDevice btDevice) {
+ final StringBuilder sb = new StringBuilder(btDevice.getAddress());
+
+ sb.append(" [name=");
+ final String name = btDevice.getName();
+ if (name != null) {
+ sb.append('\'').append(name).append('\'');
+ } else {
+ sb.append("null");
+ }
+
+ final String alias = btDevice.getAlias();
+ if (alias != null) {
+ sb.append(", alias='").append(alias).append("'");
+ }
+
+ return sb.append(']').toString();
+ }
+
private Utils() {}
}
diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
index 3312be231516..96fee9296215 100644
--- a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
+++ b/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
@@ -32,8 +32,10 @@ import android.os.SystemClock;
import android.util.Log;
import android.util.Slog;
import android.util.SparseIntArray;
+import android.util.SparseLongArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.LockSettingsStateListener;
@@ -57,6 +59,8 @@ public class AdaptiveAuthService extends SystemService {
private static final int MSG_REPORT_BIOMETRIC_AUTH_ATTEMPT = 2;
private static final int AUTH_SUCCESS = 1;
private static final int AUTH_FAILURE = 0;
+ private static final int TYPE_PRIMARY_AUTH = 0;
+ private static final int TYPE_BIOMETRIC_AUTH = 1;
private final LockPatternUtils mLockPatternUtils;
private final LockSettingsInternal mLockSettings;
@@ -67,6 +71,7 @@ public class AdaptiveAuthService extends SystemService {
private final UserManagerInternal mUserManager;
@VisibleForTesting
final SparseIntArray mFailedAttemptsForUser = new SparseIntArray();
+ private final SparseLongArray mLastLockedTimestamp = new SparseLongArray();
public AdaptiveAuthService(Context context) {
this(context, new LockPatternUtils(context));
@@ -170,7 +175,7 @@ public class AdaptiveAuthService extends SystemService {
Slog.d(TAG, "handleReportPrimaryAuthAttempt: success=" + success
+ ", userId=" + userId);
}
- reportAuthAttempt(success, userId);
+ reportAuthAttempt(TYPE_PRIMARY_AUTH, success, userId);
}
private void handleReportBiometricAuthAttempt(boolean success, int userId) {
@@ -178,13 +183,24 @@ public class AdaptiveAuthService extends SystemService {
Slog.d(TAG, "handleReportBiometricAuthAttempt: success=" + success
+ ", userId=" + userId);
}
- reportAuthAttempt(success, userId);
+ reportAuthAttempt(TYPE_BIOMETRIC_AUTH, success, userId);
}
- private void reportAuthAttempt(boolean success, int userId) {
+ private void reportAuthAttempt(int authType, boolean success, int userId) {
if (success) {
// Deleting the entry effectively resets the counter of failed attempts for the user
mFailedAttemptsForUser.delete(userId);
+
+ // Collect metrics if the device was locked by adaptive auth before
+ if (mLastLockedTimestamp.indexOfKey(userId) >= 0) {
+ final long lastLockedTime = mLastLockedTimestamp.get(userId);
+ collectTimeElapsedSinceLastLocked(
+ lastLockedTime, SystemClock.elapsedRealtime(), authType);
+
+ // Remove the entry for the last locked time because a successful auth just happened
+ // and metrics have been collected
+ mLastLockedTimestamp.delete(userId);
+ }
return;
}
@@ -210,6 +226,34 @@ public class AdaptiveAuthService extends SystemService {
lockDevice(userId);
}
+ private static void collectTimeElapsedSinceLastLocked(long lastLockedTime, long authTime,
+ int authType) {
+ final int unlockType = switch (authType) {
+ case TYPE_PRIMARY_AUTH -> FrameworkStatsLog
+ .ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED__UNLOCK_TYPE__PRIMARY_AUTH;
+ case TYPE_BIOMETRIC_AUTH -> FrameworkStatsLog
+ .ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED__UNLOCK_TYPE__BIOMETRIC_AUTH;
+ default -> FrameworkStatsLog
+ .ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED__UNLOCK_TYPE__UNKNOWN;
+ };
+
+ if (DEBUG) {
+ Slog.d(TAG, "collectTimeElapsedSinceLastLockedForUser: "
+ + "lastLockedTime=" + lastLockedTime
+ + ", authTime=" + authTime
+ + ", unlockType=" + unlockType);
+ }
+
+ // This usually shouldn't happen, and just check out of an abundance of caution
+ if (lastLockedTime > authTime) {
+ return;
+ }
+
+ // Log to statsd
+ FrameworkStatsLog.write(FrameworkStatsLog.ADAPTIVE_AUTH_UNLOCK_AFTER_LOCK_REPORTED,
+ lastLockedTime, authTime, unlockType);
+ }
+
/**
* Locks the device and requires primary auth or biometric auth for unlocking
*/
@@ -234,5 +278,9 @@ public class AdaptiveAuthService extends SystemService {
// Lock the device
mWindowManager.lockNow();
+
+ // Record the time that the device is locked by adaptive auth to collect metrics when the
+ // next successful primary or biometric auth happens
+ mLastLockedTimestamp.put(userId, SystemClock.elapsedRealtime());
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b8f6b3f3a988..7d3af99b74d1 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -372,6 +372,15 @@ public final class ActiveServices {
@Overridable
public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L;
+ /**
+ * Disables foreground service background starts in System Alert Window for all types
+ * unless it already has a System Overlay Window.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+ @Overridable
+ public static final long FGS_SAW_RESTRICTIONS = 319471980L;
+
final ActivityManagerService mAm;
// Maximum number of services that we allow to start in the background
@@ -4133,7 +4142,7 @@ public final class ActiveServices {
|| (callerApp.mState.getCurProcState() <= PROCESS_STATE_TOP
&& c.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)),
b.client);
- if ((serviceBindingOomAdjPolicy
+ if (!s.mOomAdjBumpedInExec && (serviceBindingOomAdjPolicy
& SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) == 0) {
needOomAdj = true;
mAm.enqueueOomAdjTargetLocked(s.app);
@@ -4277,7 +4286,7 @@ public final class ActiveServices {
}
serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
- !Flags.serviceBindingOomAdjPolicy() || b == null || !b.mSkippedOomAdj
+ !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
? OOM_ADJ_REASON_EXECUTING_SERVICE : OOM_ADJ_REASON_NONE);
}
} finally {
@@ -4398,7 +4407,6 @@ public final class ActiveServices {
+ (b != null ? b.apps.size() : 0));
boolean inDestroying = mDestroyingServices.contains(r);
- boolean skipOomAdj = false;
if (b != null) {
if (b.apps.size() > 0 && !inDestroying) {
// Applications have already bound since the last
@@ -4423,11 +4431,11 @@ public final class ActiveServices {
// a new client.
b.doRebind = true;
}
- skipOomAdj = Flags.serviceBindingOomAdjPolicy() && b.mSkippedOomAdj;
}
serviceDoneExecutingLocked(r, inDestroying, false, false,
- skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE);
+ !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+ ? OOM_ADJ_REASON_UNBIND_SERVICE : OOM_ADJ_REASON_NONE);
}
} finally {
mAm.mInjector.restoreCallingIdentity(origId);
@@ -4905,9 +4913,8 @@ public final class ActiveServices {
* Bump the given service record into executing state.
* @param oomAdjReason The caller requests it to perform the oomAdjUpdate not {@link
* ActivityManagerInternal#OOM_ADJ_REASON_NONE}.
- * @return {@code true} if it performed oomAdjUpdate.
*/
- private boolean bumpServiceExecutingLocked(
+ private void bumpServiceExecutingLocked(
ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason,
boolean skipTimeoutIfPossible) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
@@ -4972,19 +4979,17 @@ public final class ActiveServices {
}
}
}
- boolean oomAdjusted = false;
if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null
&& r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) {
// Force an immediate oomAdjUpdate, so the client app could be in the correct process
// state before doing any service related transactions
mAm.enqueueOomAdjTargetLocked(r.app);
mAm.updateOomAdjPendingTargetsLocked(oomAdjReason);
- oomAdjusted = true;
+ r.mOomAdjBumpedInExec = true;
}
r.executeFg |= fg;
r.executeNesting++;
r.executingStart = SystemClock.uptimeMillis();
- return oomAdjusted;
}
private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
@@ -5001,7 +5006,7 @@ public final class ActiveServices {
& SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_BIND) != 0;
if ((!i.requested || rebind) && i.apps.size() > 0) {
try {
- i.mSkippedOomAdj = !bumpServiceExecutingLocked(r, execInFg, "bind",
+ bumpServiceExecutingLocked(r, execInFg, "bind",
skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_BIND_SERVICE,
skipOomAdj /* skipTimeoutIfPossible */);
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
@@ -5020,14 +5025,16 @@ public final class ActiveServices {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
- skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE);
+ !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+ ? OOM_ADJ_REASON_UNBIND_SERVICE : OOM_ADJ_REASON_NONE);
throw e;
} catch (RemoteException e) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
- skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE);
+ !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+ ? OOM_ADJ_REASON_UNBIND_SERVICE : OOM_ADJ_REASON_NONE);
return false;
}
}
@@ -5823,6 +5830,7 @@ public final class ActiveServices {
// process state before doing any service related transactions
mAm.enqueueOomAdjTargetLocked(app);
mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
+ r.mOomAdjBumpedInExec = true;
} else {
// Since we skipped the oom adj update, the Service#onCreate() might be running in
// the cached state, if the service process drops into the cached state after the call.
@@ -5863,7 +5871,8 @@ public final class ActiveServices {
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
- skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_STOP_SERVICE);
+ !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec
+ ? OOM_ADJ_REASON_STOP_SERVICE : OOM_ADJ_REASON_NONE);
// Cleanup.
if (newService) {
@@ -5898,7 +5907,7 @@ public final class ActiveServices {
null, null, 0, null, null, ActivityManager.PROCESS_STATE_UNKNOWN));
}
- sendServiceArgsLocked(r, execInFg, true);
+ sendServiceArgsLocked(r, execInFg, r.mOomAdjBumpedInExec);
if (r.delayed) {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "REM FR DELAY LIST (new proc): " + r);
@@ -6085,7 +6094,8 @@ public final class ActiveServices {
}
}
- boolean oomAdjusted = false;
+ boolean oomAdjusted = Flags.serviceBindingOomAdjPolicy() && r.mOomAdjBumpedInExec;
+
// Tell the service that it has been unbound.
if (r.app != null && r.app.isThreadReady()) {
for (int i = r.bindings.size() - 1; i >= 0; i--) {
@@ -6094,9 +6104,10 @@ public final class ActiveServices {
+ ": hasBound=" + ibr.hasBound);
if (ibr.hasBound) {
try {
- oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind",
- OOM_ADJ_REASON_UNBIND_SERVICE,
- false /* skipTimeoutIfPossible */);
+ bumpServiceExecutingLocked(r, false, "bring down unbind",
+ oomAdjusted ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE,
+ oomAdjusted /* skipTimeoutIfPossible */);
+ oomAdjusted |= r.mOomAdjBumpedInExec;
ibr.hasBound = false;
ibr.requested = false;
r.app.getThread().scheduleUnbindService(r,
@@ -6252,10 +6263,11 @@ public final class ActiveServices {
}
} else {
try {
- oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
- oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE,
- false /* skipTimeoutIfPossible */);
+ bumpServiceExecutingLocked(r, false, "destroy",
+ oomAdjusted ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE,
+ oomAdjusted /* skipTimeoutIfPossible */);
mDestroyingServices.add(r);
+ oomAdjusted |= r.mOomAdjBumpedInExec;
r.destroying = true;
r.app.getThread().scheduleStopService(r);
} catch (Exception e) {
@@ -6411,7 +6423,7 @@ public final class ActiveServices {
final boolean skipOomAdj = (serviceBindingOomAdjPolicy
& SERVICE_BIND_OOMADJ_POLICY_SKIP_OOM_UPDATE_ON_CONNECT) != 0;
try {
- b.intent.mSkippedOomAdj = !bumpServiceExecutingLocked(s, false, "unbind",
+ bumpServiceExecutingLocked(s, false, "unbind",
skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_UNBIND_SERVICE,
skipOomAdj /* skipTimeoutIfPossible */);
if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY)
@@ -6461,6 +6473,7 @@ public final class ActiveServices {
boolean inDestroying = mDestroyingServices.contains(r);
if (r != null) {
boolean skipOomAdj = false;
+ boolean needOomAdj = false;
if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) {
// This is a call from a service start... take care of
// book-keeping.
@@ -6536,15 +6549,13 @@ public final class ActiveServices {
// Fake it to keep from ANR due to orphaned entry.
r.executeNesting = 1;
}
- } else if (type == ActivityThread.SERVICE_DONE_EXECUTING_REBIND
- || type == ActivityThread.SERVICE_DONE_EXECUTING_UNBIND) {
- final Intent.FilterComparison filter = new Intent.FilterComparison(intent);
- final IntentBindRecord b = r.bindings.get(filter);
- skipOomAdj = Flags.serviceBindingOomAdjPolicy() && b != null && b.mSkippedOomAdj;
+ // The service is done, force an oom adj update.
+ needOomAdj = true;
}
final long origId = mAm.mInjector.clearCallingIdentity();
serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj,
- skipOomAdj ? OOM_ADJ_REASON_NONE : OOM_ADJ_REASON_EXECUTING_SERVICE);
+ !Flags.serviceBindingOomAdjPolicy() || r.mOomAdjBumpedInExec || needOomAdj
+ ? OOM_ADJ_REASON_EXECUTING_SERVICE : OOM_ADJ_REASON_NONE);
mAm.mInjector.restoreCallingIdentity(origId);
} else {
Slog.w(TAG, "Done executing unknown service from pid "
@@ -6600,18 +6611,16 @@ public final class ActiveServices {
mDestroyingServices.remove(r);
r.bindings.clear();
}
- boolean oomAdjusted = false;
if (oomAdjReason != OOM_ADJ_REASON_NONE) {
if (enqueueOomAdj) {
mAm.enqueueOomAdjTargetLocked(r.app);
} else {
mAm.updateOomAdjLocked(r.app, oomAdjReason);
}
- oomAdjusted = true;
} else {
// Skip oom adj if it wasn't bumped during the bumpServiceExecutingLocked()
- oomAdjusted = false;
}
+ r.mOomAdjBumpedInExec = false;
}
r.executeFg = false;
if (r.tracker != null) {
@@ -6995,6 +7004,7 @@ public final class ActiveServices {
sr.setProcess(null, null, 0, null);
sr.isolationHostProc = null;
sr.executeNesting = 0;
+ sr.mOomAdjBumpedInExec = false;
synchronized (mAm.mProcessStats.mLock) {
sr.forceClearTracker();
}
@@ -8525,10 +8535,31 @@ public final class ActiveServices {
}
}
+ // The flag being enabled isn't enough to deny background start: we need to also check
+ // if there is a system alert UI present.
if (ret == REASON_DENIED) {
- if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
- callingPackage)) {
- ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+ // Flag check: are we disabling SAW FGS background starts?
+ final boolean shouldDisableSaw = Flags.fgsDisableSaw()
+ && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, callingUid);
+ if (shouldDisableSaw) {
+ final ProcessRecord processRecord = mAm
+ .getProcessRecordLocked(targetService.processName,
+ targetService.appInfo.uid);
+ if (processRecord != null) {
+ if (processRecord.mState.hasOverlayUi()) {
+ if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+ callingPackage)) {
+ ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+ }
+ }
+ } else {
+ Slog.e(TAG, "Could not find process record for SAW check");
+ }
+ } else {
+ if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+ callingPackage)) {
+ ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+ }
}
}
diff --git a/services/core/java/com/android/server/am/IntentBindRecord.java b/services/core/java/com/android/server/am/IntentBindRecord.java
index db47e3f26a6d..1a3652e49d8e 100644
--- a/services/core/java/com/android/server/am/IntentBindRecord.java
+++ b/services/core/java/com/android/server/am/IntentBindRecord.java
@@ -49,14 +49,6 @@ final class IntentBindRecord {
String stringName; // caching of toString
- /**
- * Mark if we've skipped oom adj update before calling into the {@link Service#onBind()}
- * or {@link Service#onUnbind()}.
- *
- * <p>If it's true, we'll skip the oom adj update too during the serviceDoneExecuting.
- */
- boolean mSkippedOomAdj;
-
void dump(PrintWriter pw, String prefix) {
pw.print(prefix); pw.print("service="); pw.println(service);
dumpInService(pw, prefix);
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 3c8d7fc833dc..e3aac0251141 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -267,6 +267,11 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
int mAllowStart_byBindings = REASON_DENIED;
/**
+ * Whether or not we've bumped its oom adj scores during its execution.
+ */
+ boolean mOomAdjBumpedInExec;
+
+ /**
* Whether to use the new "while-in-use permission" logic for FGS start
*/
private boolean useNewWiuLogic_forStart() {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 966fe5bbeecc..d1bda7991f45 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -126,6 +126,7 @@ public class SettingsToPropertiesMapper {
"arc_next",
"avic",
"bluetooth",
+ "brownout_mitigation_audio",
"build",
"biometrics",
"biometrics_framework",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 16dbe18f1555..e955b00566b8 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -23,6 +23,13 @@ flag {
}
flag {
+ name: "fgs_disable_saw"
+ namespace: "backstage_power"
+ description: "Disable System Alert Window FGS start"
+ bug: "296558535"
+}
+
+flag {
name: "bfgs_managed_network_access"
namespace: "backstage_power"
description: "Restrict network access for certain applications in BFGS process state"
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
index 5f12ce1e4163..9f31f375dafe 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
@@ -73,8 +73,7 @@ public class AmbientContextManagerService extends
private static final Set<Integer> DEFAULT_EVENT_SET = Sets.newHashSet(
AmbientContextEvent.EVENT_COUGH,
AmbientContextEvent.EVENT_SNORE,
- AmbientContextEvent.EVENT_BACK_DOUBLE_TAP,
- AmbientContextEvent.EVENT_HEART_RATE);
+ AmbientContextEvent.EVENT_BACK_DOUBLE_TAP);
/** Default value in absence of {@link DeviceConfig} override. */
private static final boolean DEFAULT_SERVICE_ENABLED = true;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 0cc64941cfe6..fb627854209d 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -661,8 +661,12 @@ public class AppOpsService extends IAppOpsService.Stub {
@NonNull OpEntry createEntryLocked(String persistentDeviceId) {
// TODO(b/308201969): Update this method when we introduce disk persistence of events
// for accesses on external devices.
- final ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+ ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
persistentDeviceId);
+ if (attributedOps == null) {
+ attributedOps = new ArrayMap<>();
+ }
+
final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
new ArrayMap<>(attributedOps.size());
for (int i = 0; i < attributedOps.size(); i++) {
@@ -680,8 +684,12 @@ public class AppOpsService extends IAppOpsService.Stub {
@NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
// TODO(b/308201969): Update this method when we introduce disk persistence of events
// for accesses on external devices.
- final ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
+ ArrayMap<String, AttributedOp> attributedOps = mDeviceAttributedOps.get(
PERSISTENT_DEVICE_ID_DEFAULT);
+ if (attributedOps == null) {
+ attributedOps = new ArrayMap<>();
+ }
+
final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
if (attributedOps.get(attributionTag) != null) {
attributionEntries.put(attributionTag,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cb6d26f61314..19dd7b7ea2f6 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -12528,6 +12528,16 @@ public class AudioService extends IAudioService.Stub
if (app == null) {
return AudioManager.ERROR;
}
+ if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+ for (AudioMix mix : policyConfig.getMixes()) {
+ if (!app.getMixes().contains(mix)) {
+ Slog.e(TAG,
+ "removeMixForPolicy attempted to unregister AudioMix(es) not "
+ + "belonging to the AudioPolicy");
+ return AudioManager.ERROR;
+ }
+ }
+ }
return app.removeMixes(policyConfig.getMixes()) == AudioSystem.SUCCESS
? AudioManager.SUCCESS : AudioManager.ERROR;
}
@@ -13306,7 +13316,13 @@ public class AudioService extends IAudioService.Stub
}
final long identity = Binder.clearCallingIdentity();
try {
- mAudioSystem.registerPolicyMixes(mMixes, false);
+ if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+ synchronized (mMixes) {
+ removeMixes(new ArrayList(mMixes));
+ }
+ } else {
+ mAudioSystem.registerPolicyMixes(mMixes, false);
+ }
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -13350,6 +13366,17 @@ public class AudioService extends IAudioService.Stub
int addMixes(@NonNull ArrayList<AudioMix> mixes) {
synchronized (mMixes) {
+ if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+ for (AudioMix mix : mixes) {
+ setMixRegistration(mix);
+ }
+
+ int result = mAudioSystem.registerPolicyMixes(mixes, true);
+ if (result == AudioSystem.SUCCESS) {
+ this.add(mixes);
+ }
+ return result;
+ }
this.add(mixes);
return mAudioSystem.registerPolicyMixes(mixes, true);
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 082776ad6085..fb4976d3cef2 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import static com.android.server.display.BrightnessMappingStrategy.INVALID_LUX;
import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessModeToString;
import android.annotation.IntDef;
@@ -202,7 +203,7 @@ public class AutomaticBrightnessController {
private float mScreenBrighteningThreshold;
private float mScreenDarkeningThreshold;
// The most recent light sample.
- private float mLastObservedLux;
+ private float mLastObservedLux = INVALID_LUX;
// The time of the most light recent sample.
private long mLastObservedLuxTime;
@@ -403,8 +404,8 @@ public class AutomaticBrightnessController {
brightnessEvent.setFlags(brightnessEvent.getFlags()
| (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0)
| (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
- ? BrightnessEvent.FLAG_DOZE_SCALE : 0)
- | (isInIdleMode() ? BrightnessEvent.FLAG_IDLE_CURVE : 0));
+ ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
+ brightnessEvent.setAutoBrightnessMode(getMode());
}
if (!mAmbientLuxValid) {
@@ -420,6 +421,35 @@ public class AutomaticBrightnessController {
return mRawScreenAutoBrightness;
}
+ /**
+ * Get the automatic screen brightness based on the last observed lux reading. Used e.g. when
+ * entering doze - we disable the light sensor, invalidate the lux, but we still need to set
+ * the initial brightness in doze mode.
+ */
+ public float getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ BrightnessEvent brightnessEvent) {
+ if (mLastObservedLux == INVALID_LUX) {
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+
+ float brightness = mCurrentBrightnessMapper.getBrightness(mLastObservedLux,
+ mForegroundAppPackageName, mForegroundAppCategory);
+ if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
+ brightness *= mDozeScaleFactor;
+ }
+
+ if (brightnessEvent != null) {
+ brightnessEvent.setLux(mLastObservedLux);
+ brightnessEvent.setRecommendedBrightness(brightness);
+ brightnessEvent.setFlags(brightnessEvent.getFlags()
+ | (mLastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0)
+ | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
+ ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
+ brightnessEvent.setAutoBrightnessMode(getMode());
+ }
+ return brightness;
+ }
+
public boolean hasValidAmbientLux() {
return mAmbientLuxValid;
}
@@ -539,7 +569,7 @@ public class AutomaticBrightnessController {
}
private boolean setScreenBrightnessByUser(float lux, float brightness) {
- if (lux == BrightnessMappingStrategy.INVALID_LUX || Float.isNaN(brightness)) {
+ if (lux == INVALID_LUX || Float.isNaN(brightness)) {
return false;
}
mCurrentBrightnessMapper.addUserDataPoint(lux, brightness);
@@ -564,6 +594,15 @@ public class AutomaticBrightnessController {
return false;
}
+ /**
+ * @return The auto-brightness mode of the current mapping strategy. Different modes use
+ * different brightness curves.
+ */
+ @AutomaticBrightnessController.AutomaticBrightnessMode
+ public int getMode() {
+ return mCurrentBrightnessMapper.getMode();
+ }
+
public boolean isInIdleMode() {
return mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE;
}
@@ -1236,12 +1275,12 @@ public class AutomaticBrightnessController {
// light.
// The anchor determines what were the light levels when the user has set their preference,
// and we use a relative threshold to determine when to revert to the OEM curve.
- private float mAnchor = BrightnessMappingStrategy.INVALID_LUX;
+ private float mAnchor = INVALID_LUX;
private float mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
private boolean mIsValid = false;
private void reset() {
- mAnchor = BrightnessMappingStrategy.INVALID_LUX;
+ mAnchor = INVALID_LUX;
mBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mIsValid = false;
}
@@ -1265,7 +1304,7 @@ public class AutomaticBrightnessController {
private boolean maybeReset(float currentLux) {
// If the short term model was invalidated and the change is drastic enough, reset it.
// Otherwise, we revalidate it.
- if (!mIsValid && mAnchor != BrightnessMappingStrategy.INVALID_LUX) {
+ if (!mIsValid && mAnchor != INVALID_LUX) {
if (mCurrentBrightnessMapper.shouldResetShortTermModel(currentLux, mAnchor)) {
resetShortTermModel();
} else {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 3965d55b0c28..23ca81468294 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
@@ -37,7 +39,6 @@ import android.hardware.display.AmbientBrightnessDayStats;
import android.hardware.display.BrightnessChangeEvent;
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.BrightnessInfo;
-import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayManagerInternal.DisplayOffloadSession;
import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
@@ -1378,7 +1379,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// Switch to doze auto-brightness mode if needed
if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
&& !mAutomaticBrightnessController.isInIdleMode()) {
- setAutomaticScreenBrightnessMode(Display.isDozeState(state)
+ mAutomaticBrightnessController.switchMode(mPowerRequest.policy == POLICY_DOZE
? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
}
@@ -1466,6 +1467,23 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
}
+ // If there's an offload session and auto-brightness is on, we need to set the initial doze
+ // brightness using the doze auto-brightness curve before the offload session starts
+ // controlling the brightness.
+ if (Float.isNaN(brightnessState) && mFlags.areAutoBrightnessModesEnabled()
+ && mFlags.isDisplayOffloadEnabled()
+ && mPowerRequest.policy == POLICY_DOZE
+ && mDisplayOffloadSession != null
+ && mAutomaticBrightnessController != null
+ && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
+ rawBrightnessState = mAutomaticBrightnessController
+ .getAutomaticScreenBrightnessBasedOnLastObservedLux(mTempBrightnessEvent);
+ if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
+ brightnessState = clampScreenBrightness(rawBrightnessState);
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
+ }
+ }
+
// Use default brightness when dozing unless overridden.
if ((Float.isNaN(brightnessState))
&& Display.isDozeState(state)) {
@@ -1618,7 +1636,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// if doze or suspend state is requested, we want to finish brightnes animation fast
// to allow state animation to start
- if (mPowerRequest.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE
+ if (mPowerRequest.policy == POLICY_DOZE
&& (mPowerRequest.dozeScreenState == Display.STATE_UNKNOWN // dozing
|| mPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
|| mPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND)) {
@@ -1706,6 +1724,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mTempBrightnessEvent.setTime(System.currentTimeMillis());
mTempBrightnessEvent.setBrightness(brightnessState);
mTempBrightnessEvent.setPhysicalDisplayId(mUniqueDisplayId);
+ mTempBrightnessEvent.setDisplayState(state);
mTempBrightnessEvent.setReason(mBrightnessReason);
mTempBrightnessEvent.setHbmMax(mBrightnessRangeController.getCurrentBrightnessMax());
mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
@@ -2879,7 +2898,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
(flags & BrightnessEvent.FLAG_INVALID_LUX) > 0,
(flags & BrightnessEvent.FLAG_DOZE_SCALE) > 0,
(flags & BrightnessEvent.FLAG_USER_SET) > 0,
- (flags & BrightnessEvent.FLAG_IDLE_CURVE) > 0,
+ event.getAutoBrightnessMode() == AUTO_BRIGHTNESS_MODE_IDLE,
(flags & BrightnessEvent.FLAG_LOW_POWER_MODE) > 0);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index d4d1bae78a33..5423b74711a2 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -16,11 +16,16 @@
package com.android.server.display.brightness;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessModeToString;
+
import android.hardware.display.BrightnessInfo;
import android.os.PowerManager;
import android.os.SystemClock;
+import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.AutomaticBrightnessController;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -33,7 +38,6 @@ public final class BrightnessEvent {
public static final int FLAG_INVALID_LUX = 0x2;
public static final int FLAG_DOZE_SCALE = 0x4;
public static final int FLAG_USER_SET = 0x8;
- public static final int FLAG_IDLE_CURVE = 0x10;
public static final int FLAG_LOW_POWER_MODE = 0x20;
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
@@ -41,6 +45,7 @@ public final class BrightnessEvent {
private BrightnessReason mReason = new BrightnessReason();
private int mDisplayId;
private String mPhysicalDisplayId;
+ private int mDisplayState;
private long mTime;
private float mLux;
private float mPreThresholdLux;
@@ -58,6 +63,8 @@ public final class BrightnessEvent {
private int mAdjustmentFlags;
private boolean mAutomaticBrightnessEnabled;
private String mDisplayBrightnessStrategyName;
+ @AutomaticBrightnessController.AutomaticBrightnessMode
+ private int mAutoBrightnessMode;
public BrightnessEvent(BrightnessEvent that) {
copyFrom(that);
@@ -77,6 +84,7 @@ public final class BrightnessEvent {
mReason.set(that.getReason());
mDisplayId = that.getDisplayId();
mPhysicalDisplayId = that.getPhysicalDisplayId();
+ mDisplayState = that.mDisplayState;
mTime = that.getTime();
// Lux values
mLux = that.getLux();
@@ -98,6 +106,7 @@ public final class BrightnessEvent {
// Auto-brightness setting
mAutomaticBrightnessEnabled = that.isAutomaticBrightnessEnabled();
mDisplayBrightnessStrategyName = that.getDisplayBrightnessStrategyName();
+ mAutoBrightnessMode = that.mAutoBrightnessMode;
}
/**
@@ -107,6 +116,7 @@ public final class BrightnessEvent {
mReason = new BrightnessReason();
mTime = SystemClock.uptimeMillis();
mPhysicalDisplayId = "";
+ mDisplayState = Display.STATE_UNKNOWN;
// Lux values
mLux = 0;
mPreThresholdLux = 0;
@@ -127,6 +137,7 @@ public final class BrightnessEvent {
// Auto-brightness setting
mAutomaticBrightnessEnabled = true;
mDisplayBrightnessStrategyName = "";
+ mAutoBrightnessMode = AUTO_BRIGHTNESS_MODE_DEFAULT;
}
/**
@@ -143,6 +154,7 @@ public final class BrightnessEvent {
return mReason.equals(that.mReason)
&& mDisplayId == that.mDisplayId
&& mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
+ && mDisplayState == that.mDisplayState
&& Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
&& Float.floatToRawIntBits(mPreThresholdLux)
== Float.floatToRawIntBits(that.mPreThresholdLux)
@@ -163,7 +175,8 @@ public final class BrightnessEvent {
&& mFlags == that.mFlags
&& mAdjustmentFlags == that.mAdjustmentFlags
&& mAutomaticBrightnessEnabled == that.mAutomaticBrightnessEnabled
- && mDisplayBrightnessStrategyName.equals(that.mDisplayBrightnessStrategyName);
+ && mDisplayBrightnessStrategyName.equals(that.mDisplayBrightnessStrategyName)
+ && mAutoBrightnessMode == that.mAutoBrightnessMode;
}
/**
@@ -177,6 +190,7 @@ public final class BrightnessEvent {
+ "BrightnessEvent: "
+ "disp=" + mDisplayId
+ ", physDisp=" + mPhysicalDisplayId
+ + ", displayState=" + Display.stateToString(mDisplayState)
+ ", brt=" + mBrightness + ((mFlags & FLAG_USER_SET) != 0 ? "(user_set)" : "")
+ ", initBrt=" + mInitialBrightness
+ ", rcmdBrt=" + mRecommendedBrightness
@@ -192,7 +206,8 @@ public final class BrightnessEvent {
+ ", flags=" + flagsToString()
+ ", reason=" + mReason.toString(mAdjustmentFlags)
+ ", autoBrightness=" + mAutomaticBrightnessEnabled
- + ", strategy=" + mDisplayBrightnessStrategyName;
+ + ", strategy=" + mDisplayBrightnessStrategyName
+ + ", autoBrightnessMode=" + autoBrightnessModeToString(mAutoBrightnessMode);
}
@Override
@@ -232,6 +247,10 @@ public final class BrightnessEvent {
this.mPhysicalDisplayId = mPhysicalDisplayId;
}
+ public void setDisplayState(int state) {
+ mDisplayState = state;
+ }
+
public float getLux() {
return mLux;
}
@@ -374,6 +393,16 @@ public final class BrightnessEvent {
this.mAutomaticBrightnessEnabled = mAutomaticBrightnessEnabled;
}
+ @AutomaticBrightnessController.AutomaticBrightnessMode
+ public int getAutoBrightnessMode() {
+ return mAutoBrightnessMode;
+ }
+
+ public void setAutoBrightnessMode(
+ @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+ mAutoBrightnessMode = mode;
+ }
+
/**
* A utility to stringify flags from a BrightnessEvent
* @return Stringified flags from BrightnessEvent
@@ -384,7 +413,6 @@ public final class BrightnessEvent {
+ ((mFlags & FLAG_RBC) != 0 ? "rbc " : "")
+ ((mFlags & FLAG_INVALID_LUX) != 0 ? "invalid_lux " : "")
+ ((mFlags & FLAG_DOZE_SCALE) != 0 ? "doze_scale " : "")
- + ((mFlags & FLAG_IDLE_CURVE) != 0 ? "idle_curve " : "")
+ ((mFlags & FLAG_LOW_POWER_MODE) != 0 ? "low_power_mode " : "");
}
}
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
index bc443a8167ab..fc95d15ae6f4 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java
@@ -40,7 +40,8 @@ public final class BrightnessReason {
public static final int REASON_SCREEN_OFF_BRIGHTNESS_SENSOR = 9;
public static final int REASON_FOLLOWER = 10;
public static final int REASON_OFFLOAD = 11;
- public static final int REASON_MAX = REASON_OFFLOAD;
+ public static final int REASON_DOZE_INITIAL = 12;
+ public static final int REASON_MAX = REASON_DOZE_INITIAL;
public static final int MODIFIER_DIMMED = 0x1;
public static final int MODIFIER_LOW_POWER = 0x2;
@@ -207,6 +208,8 @@ public final class BrightnessReason {
return "follower";
case REASON_OFFLOAD:
return "offload";
+ case REASON_DOZE_INITIAL:
+ return "doze_initial";
default:
return Integer.toString(reason);
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 055f94a23363..babc36efcca8 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -71,6 +71,8 @@ public class DisplayBrightnessStrategySelector {
@Nullable
private final OffloadBrightnessStrategy mOffloadBrightnessStrategy;
+ private final DisplayBrightnessStrategy[] mDisplayBrightnessStrategies;
+
// We take note of the old brightness strategy so that we can know when the strategy changes.
private String mOldBrightnessStrategyName;
@@ -98,6 +100,10 @@ public class DisplayBrightnessStrategySelector {
} else {
mOffloadBrightnessStrategy = null;
}
+ mDisplayBrightnessStrategies = new DisplayBrightnessStrategy[]{mInvalidBrightnessStrategy,
+ mScreenOffBrightnessStrategy, mDozeBrightnessStrategy, mFollowerBrightnessStrategy,
+ mBoostBrightnessStrategy, mOverrideBrightnessStrategy, mTemporaryBrightnessStrategy,
+ mOffloadBrightnessStrategy};
mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean(
R.bool.config_allowAutoBrightnessWhileDozing);
mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName();
@@ -179,9 +185,10 @@ public class DisplayBrightnessStrategySelector {
" mAllowAutoBrightnessWhileDozingConfig= "
+ mAllowAutoBrightnessWhileDozingConfig);
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
- mTemporaryBrightnessStrategy.dump(ipw);
- if (mOffloadBrightnessStrategy != null) {
- mOffloadBrightnessStrategy.dump(ipw);
+ for (DisplayBrightnessStrategy displayBrightnessStrategy: mDisplayBrightnessStrategies) {
+ if (displayBrightnessStrategy != null) {
+ displayBrightnessStrategy.dump(ipw);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 3e6e09da9753..8eaecef6e562 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -267,6 +267,23 @@ public class AutomaticBrightnessStrategy {
}
/**
+ * Get the automatic screen brightness based on the last observed lux reading. Used e.g. when
+ * entering doze - we disable the light sensor, invalidate the lux, but we still need to set
+ * the initial brightness in doze mode.
+ * @param brightnessEvent Event object to populate with details about why the specific
+ * brightness was chosen.
+ */
+ public float getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ BrightnessEvent brightnessEvent) {
+ float brightness = (mAutomaticBrightnessController != null)
+ ? mAutomaticBrightnessController
+ .getAutomaticScreenBrightnessBasedOnLastObservedLux(brightnessEvent)
+ : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ adjustAutomaticBrightnessStateIfValid(brightness);
+ return brightness;
+ }
+
+ /**
* Gets the auto-brightness adjustment flag change reason
*/
public int getAutoBrightnessAdjustmentReasonsFlags() {
diff --git a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
index dd400d998eb4..9ee1d73726bd 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
@@ -23,6 +23,8 @@ import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import java.io.PrintWriter;
+
/**
* Manages the brightness of the display when the system brightness boost is requested.
*/
@@ -48,4 +50,7 @@ public class BoostBrightnessStrategy implements DisplayBrightnessStrategy {
public String getName() {
return "BoostBrightnessStrategy";
}
+
+ @Override
+ public void dump(PrintWriter writer) {}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
index 27d04fd7f743..1f28eb4fa113 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
@@ -21,6 +21,8 @@ import android.hardware.display.DisplayManagerInternal;
import com.android.server.display.DisplayBrightnessState;
+import java.io.PrintWriter;
+
/**
* Decides the DisplayBrighntessState that the display should change to based on strategy-specific
* logic within each implementation. Clamping should be done outside of DisplayBrightnessStrategy if
@@ -40,4 +42,10 @@ public interface DisplayBrightnessStrategy {
*/
@NonNull
String getName();
+
+ /**
+ * Dumps the state of the Strategy
+ * @param writer
+ */
+ void dump(PrintWriter writer);
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
index 8299586e1cac..2be74438f87a 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
@@ -22,6 +22,8 @@ import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import java.io.PrintWriter;
+
/**
* Manages the brightness of the display when the system is in the doze state.
*/
@@ -42,4 +44,6 @@ public class DozeBrightnessStrategy implements DisplayBrightnessStrategy {
return "DozeBrightnessStrategy";
}
+ @Override
+ public void dump(PrintWriter writer) {}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
index 585f576c25c3..54f9afcbdd94 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
@@ -75,6 +75,7 @@ public class FollowerBrightnessStrategy implements DisplayBrightnessStrategy {
/**
* Dumps the state of this class.
*/
+ @Override
public void dump(PrintWriter writer) {
writer.println("FollowerBrightnessStrategy:");
writer.println(" mDisplayId=" + mDisplayId);
diff --git a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
index bc241964ff86..49c3e03c8742 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
@@ -23,6 +23,8 @@ import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import java.io.PrintWriter;
+
/**
* Manages the brightness of the display when the system is in the invalid state.
*/
@@ -39,4 +41,7 @@ public class InvalidBrightnessStrategy implements DisplayBrightnessStrategy {
public String getName() {
return "InvalidBrightnessStrategy";
}
+
+ @Override
+ public void dump(PrintWriter writer) {}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
index 55f8914e26f6..4ffb16be5789 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
@@ -67,6 +67,7 @@ public class OffloadBrightnessStrategy implements DisplayBrightnessStrategy {
/**
* Dumps the state of this class.
*/
+ @Override
public void dump(PrintWriter writer) {
writer.println("OffloadBrightnessStrategy:");
writer.println(" mOffloadScreenBrightness:" + mOffloadScreenBrightness);
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
index 13327cb4dd2f..7b651d8ced8e 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
@@ -22,6 +22,8 @@ import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import java.io.PrintWriter;
+
/**
* Manages the brightness of the display when the system brightness is overridden
*/
@@ -40,4 +42,7 @@ public class OverrideBrightnessStrategy implements DisplayBrightnessStrategy {
public String getName() {
return "OverrideBrightnessStrategy";
}
+
+ @Override
+ public void dump(PrintWriter writer) {}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
index 3d411d3db658..201ef4118854 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
@@ -23,6 +23,8 @@ import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import java.io.PrintWriter;
+
/**
* Manages the brightness of the display when the system is in the ScreenOff state.
*/
@@ -41,4 +43,7 @@ public class ScreenOffBrightnessStrategy implements DisplayBrightnessStrategy {
public String getName() {
return "ScreenOffBrightnessStrategy";
}
+
+ @Override
+ public void dump(PrintWriter writer) {}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
index 35f7dd0a524d..bbd0c009debb 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
@@ -68,6 +68,7 @@ public class TemporaryBrightnessStrategy implements DisplayBrightnessStrategy {
/**
* Dumps the state of this class.
*/
+ @Override
public void dump(PrintWriter writer) {
writer.println("TemporaryBrightnessStrategy:");
writer.println(" mTemporaryScreenBrightness:" + mTemporaryScreenBrightness);
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index d2aff254890d..c6d66db3f8cd 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -67,6 +67,7 @@ import android.view.Display;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.util.DumpUtils;
@@ -112,7 +113,7 @@ public final class DreamManagerService extends SystemService {
private final Object mLock = new Object();
private final Context mContext;
- private final DreamHandler mHandler;
+ private final Handler mHandler;
private final DreamController mController;
private final PowerManager mPowerManager;
private final PowerManagerInternal mPowerManagerInternal;
@@ -211,9 +212,14 @@ public final class DreamManagerService extends SystemService {
}
public DreamManagerService(Context context) {
+ this(context, new DreamHandler(FgThread.get().getLooper()));
+ }
+
+ @VisibleForTesting
+ DreamManagerService(Context context, Handler handler) {
super(context);
mContext = context;
- mHandler = new DreamHandler(FgThread.get().getLooper());
+ mHandler = handler;
mController = new DreamController(context, mHandler, mControllerListener);
mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
@@ -244,7 +250,6 @@ public final class DreamManagerService extends SystemService {
com.android.internal.R.bool.config_keepDreamingWhenUnplugging);
mDreamsDisabledByAmbientModeSuppressionConfig = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig);
-
}
@Override
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 70993ca3e21b..1e90ab279d32 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -317,6 +317,10 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
|| ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
&& lastSystemAudioControlStatus && isSystemAudioControlFeatureEnabled())) {
+ if (hasAction(SystemAudioInitiationActionFromAvr.class)) {
+ Slog.i(TAG, "SystemAudioInitiationActionFromAvr is in progress. Restarting.");
+ removeAction(SystemAudioInitiationActionFromAvr.class);
+ }
addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
}
}
@@ -1032,6 +1036,10 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
void onSystemAudioControlFeatureSupportChanged(boolean enabled) {
setSystemAudioControlFeatureEnabled(enabled);
if (enabled) {
+ if (hasAction(SystemAudioInitiationActionFromAvr.class)) {
+ Slog.i(TAG, "SystemAudioInitiationActionFromAvr is in progress. Restarting.");
+ removeAction(SystemAudioInitiationActionFromAvr.class);
+ }
addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index dbef427e23a7..a79e7715f064 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -38,7 +38,6 @@ import android.graphics.PointF;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.Sensors;
import android.hardware.SensorPrivacyManagerInternal;
-import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.DisplayViewport;
import android.hardware.input.HostUsiVersion;
@@ -86,9 +85,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
import android.view.Display;
-import android.view.DisplayInfo;
import android.view.IInputFilter;
import android.view.IInputFilterHost;
import android.view.IInputMonitorHost;
@@ -117,7 +114,6 @@ import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
-import com.android.server.UiThread;
import com.android.server.Watchdog;
import com.android.server.input.InputManagerInternal.LidSwitchCallback;
import com.android.server.input.debug.FocusEventDebugView;
@@ -325,6 +321,9 @@ public class InputManagerService extends IInputManager.Stub
// Manages Keyboard modifier keys remapping
private final KeyRemapper mKeyRemapper;
+ // Manages loading PointerIcons
+ private final PointerIconCache mPointerIconCache;
+
// Maximum number of milliseconds to wait for input event injection.
private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
@@ -411,74 +410,6 @@ public class InputManagerService extends IInputManager.Stub
private boolean mShowKeyPresses = false;
private boolean mShowRotaryInput = false;
- @GuardedBy("mLoadedPointerIconsByDisplayAndType")
- final SparseArray<SparseArray<PointerIcon>> mLoadedPointerIconsByDisplayAndType =
- new SparseArray<>();
- @GuardedBy("mLoadedPointerIconsByDisplayAndType")
- boolean mUseLargePointerIcons = false;
- @GuardedBy("mLoadedPointerIconsByDisplayAndType")
- final SparseArray<Context> mDisplayContexts = new SparseArray<>();
- @GuardedBy("mLoadedPointerIconsByDisplayAndType")
- final SparseIntArray mDisplayDensities = new SparseIntArray();
-
- final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
- @Override
- public void onDisplayAdded(int displayId) {
- synchronized (mLoadedPointerIconsByDisplayAndType) {
- updateDisplayDensity(displayId);
- }
- }
-
- @Override
- public void onDisplayRemoved(int displayId) {
- synchronized (mLoadedPointerIconsByDisplayAndType) {
- mLoadedPointerIconsByDisplayAndType.remove(displayId);
- mDisplayContexts.remove(displayId);
- mDisplayDensities.delete(displayId);
- }
- }
-
- @Override
- public void onDisplayChanged(int displayId) {
- synchronized (mLoadedPointerIconsByDisplayAndType) {
- if (!updateDisplayDensity(displayId)) {
- return;
- }
- // The display density changed, so force all cached pointer icons to be
- // reloaded for the display.
- Slog.i(TAG,
- "Reloading pointer icons due to density change on display: " + displayId);
- var iconsByType = mLoadedPointerIconsByDisplayAndType.get(displayId);
- if (iconsByType == null) {
- return;
- }
- iconsByType.clear();
- mDisplayContexts.remove(displayId);
- }
- mNative.reloadPointerIcons();
- }
-
- // Updates the cached display density for the given displayId, and returns true if
- // the cached density changed.
- @GuardedBy("mLoadedPointerIconsByDisplayAndType")
- private boolean updateDisplayDensity(int displayId) {
- final DisplayManager displayManager = Objects.requireNonNull(
- mContext.getSystemService(DisplayManager.class));
- final Display display = displayManager.getDisplay(displayId);
- if (display == null) {
- return false;
- }
- DisplayInfo info = new DisplayInfo();
- display.getDisplayInfo(info);
- final int oldDensity = mDisplayDensities.get(displayId, 0 /* default */);
- if (oldDensity == info.logicalDensityDpi) {
- return false;
- }
- mDisplayDensities.put(displayId, info.logicalDensityDpi);
- return true;
- }
- };
-
/** Point of injection for test dependencies. */
@VisibleForTesting
static class Injector {
@@ -537,6 +468,7 @@ public class InputManagerService extends IInputManager.Stub
: new KeyboardBacklightControllerInterface() {};
mStickyModifierStateController = new StickyModifierStateController();
mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
+ mPointerIconCache = new PointerIconCache(mContext, mNative);
mUseDevInputEventForAudioJack =
mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
@@ -646,18 +578,11 @@ public class InputManagerService extends IInputManager.Stub
mWiredAccessoryCallbacks.systemReady();
}
- final DisplayManager displayManager = Objects.requireNonNull(
- mContext.getSystemService(DisplayManager.class));
- displayManager.registerDisplayListener(mDisplayListener, UiThread.getHandler());
- final Display[] displays = displayManager.getDisplays();
- for (int i = 0; i < displays.length; i++) {
- mDisplayListener.onDisplayAdded(displays[i].getDisplayId());
- }
-
mKeyboardLayoutManager.systemRunning();
mBatteryController.systemRunning();
mKeyboardBacklightController.systemRunning();
mKeyRemapper.systemRunning();
+ mPointerIconCache.systemRunning();
}
private void reloadDeviceAliases() {
@@ -2411,8 +2336,8 @@ public class InputManagerService extends IInputManager.Stub
synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ }
synchronized (mInputMonitors) { /* Test if blocked by input monitor lock. */ }
synchronized (mAdditionalDisplayInputPropertiesLock) { /* Test if blocked by props lock */ }
- synchronized (mLoadedPointerIconsByDisplayAndType) { /* Test if blocked by pointer lock */}
mBatteryController.monitor();
+ mPointerIconCache.monitor();
mNative.monitor();
}
@@ -2806,21 +2731,7 @@ public class InputManagerService extends IInputManager.Stub
// Native callback.
@SuppressWarnings("unused")
private @NonNull PointerIcon getLoadedPointerIcon(int displayId, int type) {
- synchronized (mLoadedPointerIconsByDisplayAndType) {
- SparseArray<PointerIcon> iconsByType = mLoadedPointerIconsByDisplayAndType.get(
- displayId);
- if (iconsByType == null) {
- iconsByType = new SparseArray<>();
- mLoadedPointerIconsByDisplayAndType.put(displayId, iconsByType);
- }
- PointerIcon icon = iconsByType.get(type);
- if (icon == null) {
- icon = PointerIcon.getLoadedSystemIcon(getContextForDisplay(displayId), type,
- mUseLargePointerIcons);
- iconsByType.put(type, icon);
- }
- return Objects.requireNonNull(icon);
- }
+ return mPointerIconCache.getLoadedPointerIcon(displayId, type);
}
// Native callback.
@@ -2833,33 +2744,6 @@ public class InputManagerService extends IInputManager.Stub
return sc.mNativeObject;
}
- @GuardedBy("mLoadedPointerIconsByDisplayAndType")
- @NonNull
- private Context getContextForDisplay(int displayId) {
- if (displayId == Display.INVALID_DISPLAY) {
- // Fallback to using the default context.
- return mContext;
- }
- if (displayId == mContext.getDisplay().getDisplayId()) {
- return mContext;
- }
-
- Context displayContext = mDisplayContexts.get(displayId);
- if (displayContext == null) {
- final DisplayManager displayManager = Objects.requireNonNull(
- mContext.getSystemService(DisplayManager.class));
- final Display display = displayManager.getDisplay(displayId);
- if (display == null) {
- // Fallback to using the default context.
- return mContext;
- }
-
- displayContext = mContext.createDisplayContext(display);
- mDisplayContexts.put(displayId, displayContext);
- }
- return displayContext;
- }
-
// Native callback.
@SuppressWarnings("unused")
private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier, String languageTag,
@@ -3665,15 +3549,7 @@ public class InputManagerService extends IInputManager.Stub
}
void setUseLargePointerIcons(boolean useLargeIcons) {
- synchronized (mLoadedPointerIconsByDisplayAndType) {
- if (mUseLargePointerIcons == useLargeIcons) {
- return;
- }
- mUseLargePointerIcons = useLargeIcons;
- // Clear all cached icons on all displays.
- mLoadedPointerIconsByDisplayAndType.clear();
- }
- UiThread.getHandler().post(mNative::reloadPointerIcons);
+ mPointerIconCache.setUseLargePointerIcons(useLargeIcons);
}
interface KeyboardBacklightControllerInterface {
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 4b9f2cf9d0c0..277a4d40c7e4 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -231,7 +231,10 @@ public final class KeyboardMetricsCollector {
DESKTOP_MODE(
FrameworkStatsLog
.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE,
- "DESKTOP_MODE");
+ "DESKTOP_MODE"),
+ MULTI_WINDOW_NAVIGATION(FrameworkStatsLog
+ .KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION,
+ "MULTIWINDOW_NAVIGATION");
private final int mValue;
diff --git a/services/core/java/com/android/server/input/PointerIconCache.java b/services/core/java/com/android/server/input/PointerIconCache.java
new file mode 100644
index 000000000000..233b865c69a6
--- /dev/null
+++ b/services/core/java/com/android/server/input/PointerIconCache.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.PointerIcon;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.UiThread;
+
+import java.util.Objects;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for caching loaded
+ * {@link PointerIcon}s and triggering reloading of the icons.
+ */
+final class PointerIconCache {
+ private static final String TAG = PointerIconCache.class.getSimpleName();
+
+ private final Context mContext;
+
+ // Do not hold the lock when calling into native code.
+ private final NativeInputManagerService mNative;
+
+ // We use the UI thread for loading pointer icons.
+ private final Handler mUiThreadHandler = UiThread.getHandler();
+
+ @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+ private final SparseArray<SparseArray<PointerIcon>> mLoadedPointerIconsByDisplayAndType =
+ new SparseArray<>();
+ @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+ private boolean mUseLargePointerIcons = false;
+ @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+ private final SparseArray<Context> mDisplayContexts = new SparseArray<>();
+ @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+ private final SparseIntArray mDisplayDensities = new SparseIntArray();
+
+ private final DisplayManager.DisplayListener mDisplayListener =
+ new DisplayManager.DisplayListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ synchronized (mLoadedPointerIconsByDisplayAndType) {
+ updateDisplayDensityLocked(displayId);
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ synchronized (mLoadedPointerIconsByDisplayAndType) {
+ mLoadedPointerIconsByDisplayAndType.remove(displayId);
+ mDisplayContexts.remove(displayId);
+ mDisplayDensities.delete(displayId);
+ }
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ handleDisplayChanged(displayId);
+ }
+ };
+
+ /* package */ PointerIconCache(Context context, NativeInputManagerService nativeService) {
+ mContext = context;
+ mNative = nativeService;
+ }
+
+ public void systemRunning() {
+ final DisplayManager displayManager = Objects.requireNonNull(
+ mContext.getSystemService(DisplayManager.class));
+ displayManager.registerDisplayListener(mDisplayListener, mUiThreadHandler);
+ final Display[] displays = displayManager.getDisplays();
+ for (int i = 0; i < displays.length; i++) {
+ mDisplayListener.onDisplayAdded(displays[i].getDisplayId());
+ }
+ }
+
+ public void monitor() {
+ synchronized (mLoadedPointerIconsByDisplayAndType) { /* Test if blocked by lock */}
+ }
+
+ /** Set whether the large pointer icons should be used for accessibility. */
+ public void setUseLargePointerIcons(boolean useLargeIcons) {
+ mUiThreadHandler.post(() -> handleSetUseLargePointerIcons(useLargeIcons));
+ }
+
+ /**
+ * Get a loaded system pointer icon. This will fetch the icon from the cache, or load it if
+ * it isn't already cached.
+ */
+ public @NonNull PointerIcon getLoadedPointerIcon(int displayId, int type) {
+ synchronized (mLoadedPointerIconsByDisplayAndType) {
+ SparseArray<PointerIcon> iconsByType = mLoadedPointerIconsByDisplayAndType.get(
+ displayId);
+ if (iconsByType == null) {
+ iconsByType = new SparseArray<>();
+ mLoadedPointerIconsByDisplayAndType.put(displayId, iconsByType);
+ }
+ PointerIcon icon = iconsByType.get(type);
+ if (icon == null) {
+ icon = PointerIcon.getLoadedSystemIcon(getContextForDisplayLocked(displayId), type,
+ mUseLargePointerIcons);
+ iconsByType.put(type, icon);
+ }
+ return Objects.requireNonNull(icon);
+ }
+ }
+
+ @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+ private @NonNull Context getContextForDisplayLocked(int displayId) {
+ if (displayId == Display.INVALID_DISPLAY) {
+ // Fallback to using the default context.
+ return mContext;
+ }
+ if (displayId == mContext.getDisplay().getDisplayId()) {
+ return mContext;
+ }
+
+ Context displayContext = mDisplayContexts.get(displayId);
+ if (displayContext == null) {
+ final DisplayManager displayManager = Objects.requireNonNull(
+ mContext.getSystemService(DisplayManager.class));
+ final Display display = displayManager.getDisplay(displayId);
+ if (display == null) {
+ // Fallback to using the default context.
+ return mContext;
+ }
+
+ displayContext = mContext.createDisplayContext(display);
+ mDisplayContexts.put(displayId, displayContext);
+ }
+ return displayContext;
+ }
+
+ @android.annotation.UiThread
+ private void handleDisplayChanged(int displayId) {
+ synchronized (mLoadedPointerIconsByDisplayAndType) {
+ if (!updateDisplayDensityLocked(displayId)) {
+ return;
+ }
+ // The display density changed, so force all cached pointer icons to be
+ // reloaded for the display.
+ Slog.i(TAG, "Reloading pointer icons due to density change on display: " + displayId);
+ var iconsByType = mLoadedPointerIconsByDisplayAndType.get(displayId);
+ if (iconsByType == null) {
+ return;
+ }
+ iconsByType.clear();
+ mDisplayContexts.remove(displayId);
+ }
+ mNative.reloadPointerIcons();
+ }
+
+ @android.annotation.UiThread
+ private void handleSetUseLargePointerIcons(boolean useLargeIcons) {
+ synchronized (mLoadedPointerIconsByDisplayAndType) {
+ if (mUseLargePointerIcons == useLargeIcons) {
+ return;
+ }
+ mUseLargePointerIcons = useLargeIcons;
+ // Clear all cached icons on all displays.
+ mLoadedPointerIconsByDisplayAndType.clear();
+ }
+ mNative.reloadPointerIcons();
+ }
+
+ // Updates the cached display density for the given displayId, and returns true if
+ // the cached density changed.
+ @GuardedBy("mLoadedPointerIconsByDisplayAndType")
+ private boolean updateDisplayDensityLocked(int displayId) {
+ final DisplayManager displayManager = Objects.requireNonNull(
+ mContext.getSystemService(DisplayManager.class));
+ final Display display = displayManager.getDisplay(displayId);
+ if (display == null) {
+ return false;
+ }
+ DisplayInfo info = new DisplayInfo();
+ display.getDisplayInfo(info);
+ final int oldDensity = mDisplayDensities.get(displayId, 0 /* default */);
+ if (oldDensity == info.logicalDensityDpi) {
+ return false;
+ }
+ mDisplayDensities.put(displayId, info.logicalDensityDpi);
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 9a76ebd148aa..a3ec3c52ca20 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -108,7 +108,6 @@ import android.provider.DeviceConfig;
import android.provider.Settings;
import android.security.AndroidKeyStoreMaintenance;
import android.security.Authorization;
-import android.security.KeyStore;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.security.keystore.recovery.KeyChainProtectionParams;
@@ -169,6 +168,7 @@ import java.io.PrintWriter;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
+import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@@ -292,7 +292,7 @@ public class LockSettingsService extends ILockSettings.Stub {
private final IActivityManager mActivityManager;
private final SyntheticPasswordManager mSpManager;
- private final java.security.KeyStore mJavaKeyStore;
+ private final KeyStore mKeyStore;
private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
private final UnifiedProfilePasswordCache mUnifiedProfilePasswordCache;
@@ -564,10 +564,6 @@ public class LockSettingsService extends ILockSettings.Stub {
return DeviceStateCache.getInstance();
}
- public KeyStore getKeyStore() {
- return KeyStore.getInstance();
- }
-
public RecoverableKeyStoreManager getRecoverableKeyStoreManager() {
return RecoverableKeyStoreManager.getInstance(mContext);
}
@@ -619,9 +615,9 @@ public class LockSettingsService extends ILockSettings.Stub {
return (BiometricManager) mContext.getSystemService(Context.BIOMETRIC_SERVICE);
}
- public java.security.KeyStore getJavaKeyStore() {
+ public KeyStore getKeyStore() {
try {
- java.security.KeyStore ks = java.security.KeyStore.getInstance(
+ KeyStore ks = KeyStore.getInstance(
SyntheticPasswordCrypto.androidKeystoreProviderName());
ks.load(new AndroidKeyStoreLoadStoreParameter(
SyntheticPasswordCrypto.keyNamespace()));
@@ -631,8 +627,7 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
- public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(
- java.security.KeyStore ks) {
+ public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(KeyStore ks) {
return new UnifiedProfilePasswordCache(ks);
}
@@ -654,7 +649,7 @@ public class LockSettingsService extends ILockSettings.Stub {
protected LockSettingsService(Injector injector) {
mInjector = injector;
mContext = injector.getContext();
- mJavaKeyStore = injector.getJavaKeyStore();
+ mKeyStore = injector.getKeyStore();
mRecoverableKeyStoreManager = injector.getRecoverableKeyStoreManager();
mHandler = injector.getHandler(injector.getServiceThread());
mStrongAuth = injector.getStrongAuth();
@@ -676,7 +671,7 @@ public class LockSettingsService extends ILockSettings.Stub {
mGatekeeperPasswords = new LongSparseArray<>();
mSpManager = injector.getSyntheticPasswordManager(mStorage);
- mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mJavaKeyStore);
+ mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mKeyStore);
mBiometricDeferredQueue = new BiometricDeferredQueue(mSpManager, mHandler);
mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
@@ -1482,7 +1477,7 @@ public class LockSettingsService extends ILockSettings.Stub {
byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE,
storedData.length);
byte[] decryptionResult;
- SecretKey decryptionKey = (SecretKey) mJavaKeyStore.getKey(
+ SecretKey decryptionKey = (SecretKey) mKeyStore.getKey(
PROFILE_KEY_NAME_DECRYPT + userId, null);
Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
@@ -2076,16 +2071,16 @@ public class LockSettingsService extends ILockSettings.Stub {
keyGenerator.init(new SecureRandom());
SecretKey secretKey = keyGenerator.generateKey();
try {
- mJavaKeyStore.setEntry(
+ mKeyStore.setEntry(
PROFILE_KEY_NAME_ENCRYPT + profileUserId,
- new java.security.KeyStore.SecretKeyEntry(secretKey),
+ new KeyStore.SecretKeyEntry(secretKey),
new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
- mJavaKeyStore.setEntry(
+ mKeyStore.setEntry(
PROFILE_KEY_NAME_DECRYPT + profileUserId,
- new java.security.KeyStore.SecretKeyEntry(secretKey),
+ new KeyStore.SecretKeyEntry(secretKey),
new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
@@ -2094,7 +2089,7 @@ public class LockSettingsService extends ILockSettings.Stub {
.setUserAuthenticationValidityDurationSeconds(30)
.build());
// Key imported, obtain a reference to it.
- SecretKey keyStoreEncryptionKey = (SecretKey) mJavaKeyStore.getKey(
+ SecretKey keyStoreEncryptionKey = (SecretKey) mKeyStore.getKey(
PROFILE_KEY_NAME_ENCRYPT + profileUserId, null);
Cipher cipher = Cipher.getInstance(
KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/"
@@ -2104,7 +2099,7 @@ public class LockSettingsService extends ILockSettings.Stub {
iv = cipher.getIV();
} finally {
// The original key can now be discarded.
- mJavaKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + profileUserId);
+ mKeyStore.deleteEntry(PROFILE_KEY_NAME_ENCRYPT + profileUserId);
}
} catch (UnrecoverableKeyException
| BadPaddingException | IllegalBlockSizeException | KeyStoreException
@@ -2556,11 +2551,10 @@ public class LockSettingsService extends ILockSettings.Stub {
final String encryptAlias = PROFILE_KEY_NAME_ENCRYPT + targetUserId;
final String decryptAlias = PROFILE_KEY_NAME_DECRYPT + targetUserId;
try {
- if (mJavaKeyStore.containsAlias(encryptAlias) ||
- mJavaKeyStore.containsAlias(decryptAlias)) {
+ if (mKeyStore.containsAlias(encryptAlias) || mKeyStore.containsAlias(decryptAlias)) {
Slogf.i(TAG, "Removing keystore profile key for user %d", targetUserId);
- mJavaKeyStore.deleteEntry(encryptAlias);
- mJavaKeyStore.deleteEntry(decryptAlias);
+ mKeyStore.deleteEntry(encryptAlias);
+ mKeyStore.deleteEntry(decryptAlias);
}
} catch (KeyStoreException e) {
// We have tried our best to remove the key.
@@ -3457,7 +3451,7 @@ public class LockSettingsService extends ILockSettings.Stub {
private void dumpKeystoreKeys(IndentingPrintWriter pw) {
try {
- final Enumeration<String> aliases = mJavaKeyStore.aliases();
+ final Enumeration<String> aliases = mKeyStore.aliases();
while (aliases.hasMoreElements()) {
pw.println(aliases.nextElement());
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 796d8d73fc51..31bfc6954416 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -340,8 +340,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
static final String TAG = NetworkPolicyLogger.TAG;
private static final boolean LOGD = NetworkPolicyLogger.LOGD;
private static final boolean LOGV = NetworkPolicyLogger.LOGV;
- // TODO: b/304347838 - Remove once the feature is in staging.
- private static final boolean ALWAYS_RESTRICT_BACKGROUND_NETWORK = false;
/**
* No opportunistic quota could be calculated from user data plan or data settings.
@@ -729,7 +727,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
* Map of uid -> UidStateCallbackInfo objects holding the data received from
* {@link IUidObserver#onUidStateChanged(int, int, long, int)} callbacks. In order to avoid
* creating a new object for every callback received, we hold onto the object created for each
- * uid and reuse it.
+ * uid and reuse it until the uid stays alive.
*
* Note that the lock used for accessing this object should not be used for anything else and we
* should not be acquiring new locks or doing any heavy work while this lock is held since this
@@ -802,6 +800,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
Clock.systemUTC());
}
+ @VisibleForTesting
+ UidState getUidStateForTest(int uid) {
+ synchronized (mUidRulesFirstLock) {
+ return mUidState.get(uid);
+ }
+ }
+
static class Dependencies {
final Context mContext;
final NetworkStatsManager mNetworkStatsManager;
@@ -1063,8 +1068,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
// The flag is boot-stable.
- mBackgroundNetworkRestricted = ALWAYS_RESTRICT_BACKGROUND_NETWORK
- && Flags.networkBlockedForTopSleepingAndAbove();
+ mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
if (mBackgroundNetworkRestricted) {
// Firewall rules and UidBlockedState will get updated in
// updateRulesForGlobalChangeAL below.
@@ -1257,6 +1261,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
@Override public void onUidGone(int uid, boolean disabled) {
+ synchronized (mUidStateCallbackInfos) {
+ mUidStateCallbackInfos.remove(uid);
+ }
+ // TODO: b/327058756 - Remove any pending UID_MSG_STATE_CHANGED on the handler.
mUidEventHandler.obtainMessage(UID_MSG_GONE, uid, 0).sendToTarget();
}
};
@@ -5918,7 +5926,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
void handleUidGone(int uid) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidGone");
try {
- boolean updated;
+ final boolean updated;
synchronized (mUidRulesFirstLock) {
updated = removeUidStateUL(uid);
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index eed0eecb0a22..6857869e3776 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1285,8 +1285,8 @@ public class ZenModeHelper {
*/
private static void updateZenDeviceEffects(ZenRule zenRule,
@Nullable ZenDeviceEffects newEffects, boolean isFromApp, boolean updateBitmask) {
+ // Same as with ZenPolicy, supplying null effects means keeping the previous ones.
if (newEffects == null) {
- zenRule.zenDeviceEffects = null;
return;
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 8cc242c10c16..5bd8ca63c3ff 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -17,6 +17,8 @@
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.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
@@ -43,11 +45,11 @@ import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
+import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.APP_METADATA_FILE_NAME;
import static com.android.server.pm.DexOptHelper.useArtService;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
import static com.android.server.pm.InstructionSets.getPreferredInstructionSet;
-import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_NAME;
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
import static com.android.server.pm.PackageManagerService.DEBUG_PACKAGE_SCANNING;
@@ -2225,10 +2227,16 @@ final class InstallPackageHelper {
if (appMetadataFile.exists()) {
ps.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
if (Flags.aslInApkAppMetadataSource()) {
- ps.setAppMetadataSource(PackageManager.APP_METADATA_SOURCE_INSTALLER);
+ ps.setAppMetadataSource(APP_METADATA_SOURCE_INSTALLER);
}
} else {
- ps.setAppMetadataFilePath(null);
+ if (Flags.aslInApkAppMetadataSource()
+ && parsedPackage.isAppMetadataFileInApk()) {
+ ps.setAppMetadataFilePath(appMetadataFile.getAbsolutePath());
+ ps.setAppMetadataSource(APP_METADATA_SOURCE_APK);
+ } else {
+ ps.setAppMetadataFilePath(null);
+ }
}
}
if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 3f9e989a5bba..9b0fec2c757b 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -22,8 +22,8 @@ import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_ARCHIVE_ICON_OVERLAY;
import static android.app.AppOpsManager.OP_UNARCHIVAL_CONFIRMATION;
-import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
-import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 2b20bfd7a7c2..ef8453dcee67 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -21,7 +21,7 @@ import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
import static android.app.ActivityManager.START_PERMISSION_DENIED;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap;
import static android.content.pm.ArchivedActivityInfo.drawableToBitmap;
import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5792d864a8ab..ba903789e759 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -358,7 +358,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
/** Default byte size limit for app metadata */
private static final long DEFAULT_APP_METADATA_BYTE_SIZE_LIMIT = 32000;
- private static final int APP_METADATA_FILE_ACCESS_MODE = 0640;
+ static final int APP_METADATA_FILE_ACCESS_MODE = 0640;
/**
* Throws IllegalArgumentException if the {@link IntentSender} from an immutable
@@ -964,7 +964,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) {
final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName);
- if (ps == null || ps.getPkg() == null || !ps.isSystem()) {
+ if (ps == null || ps.getPkg() == null) {
return false;
}
String emergencyInstaller = ps.getPkg().getEmergencyInstaller();
@@ -1782,8 +1782,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
private File getStagedAppMetadataFile() {
- File file = new File(stageDir, APP_METADATA_FILE_NAME);
- return file.exists() ? file : null;
+ return new File(stageDir, APP_METADATA_FILE_NAME);
}
private static boolean isAppMetadata(String name) {
@@ -1799,7 +1798,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
assertCallerIsOwnerOrRoot();
synchronized (mLock) {
assertPreparedAndNotCommittedOrDestroyedLocked("getAppMetadataFd");
- if (getStagedAppMetadataFile() == null) {
+ if (!getStagedAppMetadataFile().exists()) {
return null;
}
try {
@@ -1813,12 +1812,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void removeAppMetadata() {
File file = getStagedAppMetadataFile();
- if (file != null) {
+ if (file.exists()) {
file.delete();
}
}
- private static long getAppMetadataSizeLimit() {
+ static long getAppMetadataSizeLimit() {
final long token = Binder.clearCallingIdentity();
try {
return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE,
@@ -2131,7 +2130,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
File appMetadataFile = getStagedAppMetadataFile();
- if (appMetadataFile != null) {
+ if (appMetadataFile.exists()) {
long sizeLimit = getAppMetadataSizeLimit();
if (appMetadataFile.length() > sizeLimit) {
appMetadataFile.delete();
@@ -3419,7 +3418,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final List<ApkLite> addedFiles = getAddedApkLitesLocked();
if (addedFiles.isEmpty()
- && (removeSplitList.size() == 0 || getStagedAppMetadataFile() != null)) {
+ && (removeSplitList.size() == 0 || getStagedAppMetadataFile().exists())) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
TextUtils.formatSimple("Session: %d. No packages staged in %s", sessionId,
stageDir.getAbsolutePath()));
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3c256b1caa7d..fe8030b656b0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -18,6 +18,8 @@ package com.android.server.pm;
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.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;
@@ -596,7 +598,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
static final String RANDOM_DIR_PREFIX = "~~";
static final char RANDOM_CODEPATH_PREFIX = '-';
- static final String APP_METADATA_FILE_NAME = "app.metadata";
+ public static final String APP_METADATA_FILE_NAME = "app.metadata";
+ public static final String APP_METADATA_FILE_IN_APK_PATH = "assets/" + APP_METADATA_FILE_NAME;
static final int DEFAULT_FILE_ACCESS_MODE = 0644;
@@ -1538,6 +1541,19 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return;
}
+ // Initialize all necessary settings for archival installation.
+ pkgSetting
+ // No package.
+ .setPkg(null)
+ // Mark for later restore.
+ .setPendingRestore(true);
+ for (int userId : userIds) {
+ // Unmark "installed" for all users.
+ pkgSetting
+ .modifyUserState(userId)
+ .setInstalled(false);
+ }
+
String responsibleInstallerPackage = PackageArchiver.getResponsibleInstallerPackage(
pkgSetting);
// TODO(b/278553670) Check if responsibleInstallerPackage supports unarchival.
@@ -1548,16 +1564,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService
for (int userId : userIds) {
var archiveState = mInstallerService.mPackageArchiver.createArchiveState(
archivePackage, userId, responsibleInstallerPackage);
- if (archiveState == null) {
- continue;
- }
- pkgSetting
- .setPkg(null)
- // This package was installed as archived. Need to mark it for later restore.
- .setPendingRestore(true)
+ if (archiveState != null) {
+ pkgSetting
.modifyUserState(userId)
- .setInstalled(false)
.setArchiveState(archiveState);
+ }
}
}
@@ -5227,15 +5238,30 @@ public class PackageManagerService implements PackageSender, TestUtilityService
new PackageManager.NameNotFoundException(packageName));
}
String filePath = ps.getAppMetadataFilePath();
- if (filePath != null) {
- File file = new File(filePath);
- try {
- return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
- } catch (FileNotFoundException e) {
+ if (filePath == null) {
+ return null;
+ }
+ File file = new File(filePath);
+ if (Flags.aslInApkAppMetadataSource() && !file.exists()
+ && ps.getAppMetadataSource() == APP_METADATA_SOURCE_APK) {
+ String apkPath = ps.getPkg().getSplits().get(0).getPath();
+ if (!PackageManagerServiceUtils.extractAppMetadataFromApk(apkPath, file)) {
+ if (file.exists()) {
+ file.delete();
+ }
+ synchronized (mLock) {
+ PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
+ pkgSetting.setAppMetadataFilePath(null);
+ pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
+ }
return null;
}
}
- return null;
+ try {
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
}
@android.annotation.EnforcePermission(android.Manifest.permission.GET_APP_METADATA)
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 0a3dfc08686a..189a138a191a 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -27,6 +27,9 @@ import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH;
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
+import static com.android.server.pm.PackageInstallerSession.APP_METADATA_FILE_ACCESS_MODE;
+import static com.android.server.pm.PackageInstallerSession.getAppMetadataSizeLimit;
+import static com.android.server.pm.PackageManagerService.APP_METADATA_FILE_IN_APK_PATH;
import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
@@ -142,6 +145,8 @@ import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.zip.GZIPInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
/**
* Class containing helper methods for the PackageManagerService.
@@ -1559,6 +1564,35 @@ public class PackageManagerServiceUtils {
return initiatingPackageName == null || SHELL_PACKAGE_NAME.equals(initiatingPackageName);
}
+ /**
+ * Extract the app.metadata file from apk.
+ */
+ public static boolean extractAppMetadataFromApk(String apkPath, File appMetadataFile) {
+ boolean found = false;
+ try (ZipInputStream zipInputStream =
+ new ZipInputStream(new FileInputStream(new File(apkPath)))) {
+ ZipEntry zipEntry = null;
+ while ((zipEntry = zipInputStream.getNextEntry()) != null) {
+ if (zipEntry.getName().equals(APP_METADATA_FILE_IN_APK_PATH)) {
+ found = true;
+ try (FileOutputStream out = new FileOutputStream(appMetadataFile)) {
+ FileUtils.copy(zipInputStream, out);
+ }
+ if (appMetadataFile.length() > getAppMetadataSizeLimit()) {
+ appMetadataFile.delete();
+ return false;
+ }
+ Os.chmod(appMetadataFile.getAbsolutePath(), APP_METADATA_FILE_ACCESS_MODE);
+ break;
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, e.getMessage());
+ return false;
+ }
+ return found;
+ }
+
public static void linkFilesToOldDirs(@NonNull Installer installer,
@NonNull String packageName,
@NonNull File newPath,
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 1c70af0a56ea..b18503d7d5cb 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -596,7 +596,7 @@ public class PackageInfoUtils {
ai.requiredDisplayCategory = a.getRequiredDisplayCategory();
ai.requireContentUriPermissionFromCaller = a.getRequireContentUriPermissionFromCaller();
ai.setKnownActivityEmbeddingCerts(a.getKnownActivityEmbeddingCerts());
- assignFieldsComponentInfoParsedMainComponent(ai, a, pkgSetting, userId);
+ assignFieldsComponentInfoParsedMainComponent(ai, a, pkgSetting, state, userId);
return ai;
}
@@ -659,7 +659,7 @@ public class PackageInfoUtils {
// Backwards compatibility, coerce to null if empty
si.metaData = metaData.isEmpty() ? null : metaData;
}
- assignFieldsComponentInfoParsedMainComponent(si, s, pkgSetting, userId);
+ assignFieldsComponentInfoParsedMainComponent(si, s, pkgSetting, state, userId);
return si;
}
@@ -710,7 +710,7 @@ public class PackageInfoUtils {
pi.metaData = metaData.isEmpty() ? null : metaData;
}
pi.applicationInfo = applicationInfo;
- assignFieldsComponentInfoParsedMainComponent(pi, p, pkgSetting, userId);
+ assignFieldsComponentInfoParsedMainComponent(pi, p, pkgSetting, state, userId);
return pi;
}
@@ -903,8 +903,13 @@ public class PackageInfoUtils {
private static void assignFieldsComponentInfoParsedMainComponent(
@NonNull ComponentInfo info, @NonNull ParsedMainComponent component,
- @NonNull PackageStateInternal pkgSetting, @UserIdInt int userId) {
+ @NonNull PackageStateInternal pkgSetting, @NonNull PackageUserStateInternal state,
+ @UserIdInt int userId) {
assignFieldsComponentInfoParsedMainComponent(info, component);
+ // overwrite the enabled state with the current user state
+ info.enabled = PackageUserStateUtils.isEnabled(state, info.applicationInfo.enabled,
+ info.enabled, info.name, /* flags */ 0);
+
Pair<CharSequence, Integer> labelAndIcon =
ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
userId);
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index f15646f634c2..984994170876 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -237,7 +237,7 @@ public final class PermissionPolicyService extends SystemService {
mAppOpsCallback = new IAppOpsCallback.Stub() {
public void opChanged(int op, int uid, @Nullable String packageName,
String persistentDeviceId) {
- if (Objects.equals(persistentDeviceId,
+ if (!Objects.equals(persistentDeviceId,
VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)) {
return;
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8781bf19565e..428fca082f75 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3504,8 +3504,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
if (statusbar != null) {
- statusbar.goToFullscreenFromSplit();
- logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+ statusbar.moveFocusedTaskToFullscreen(event.getDisplayId());
+ logKeyboardSystemsEvent(event, KeyboardLogEvent.MULTI_WINDOW_NAVIGATION);
return true;
}
}
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
index b0dcf95b3f5d..881583ad8c91 100644
--- a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -282,10 +282,7 @@ class AggregatedMobileDataStatsPuller {
Slog.d(TAG,
"pullDataBytesTransferLocked() done. results count " + pulledData.size());
}
- if (!pulledData.isEmpty()) {
- return StatsManager.PULL_SUCCESS;
- }
- return StatsManager.PULL_SKIP;
+ return StatsManager.PULL_SUCCESS;
}
private static boolean isEmpty(NetworkStats stats) {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 3c6baa873eca..14e0ce1704c8 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -224,9 +224,11 @@ public interface StatusBarManagerInternal {
void showRearDisplayDialog(int currentBaseState);
/**
- * Called when requested to go to fullscreen from the active split app.
+ * Called when requested to go to fullscreen from the focused app.
+ *
+ * @param displayId of the current display.
*/
- void goToFullscreenFromSplit();
+ void moveFocusedTaskToFullscreen(int displayId);
/**
* Enters stage split from a current running app.
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 14c38bde6621..0b48a75298a4 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -810,11 +810,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
- public void goToFullscreenFromSplit() {
+ public void moveFocusedTaskToFullscreen(int displayId) {
IStatusBar bar = mBar;
if (bar != null) {
try {
- bar.goToFullscreenFromSplit();
+ bar.moveFocusedTaskToFullscreen(displayId);
} catch (RemoteException ex) { }
}
}
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 743005a2d844..b7d8cfce34ed 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -767,7 +767,7 @@ public class AnrTimer<V> implements AutoCloseable {
* Return true if the native timers are supported. Native timers are supported if the method
* nativeAnrTimerSupported() can be executed and it returns true.
*/
- private static boolean nativeTimersSupported() {
+ public static boolean nativeTimersSupported() {
try {
return nativeAnrTimerSupported();
} catch (java.lang.UnsatisfiedLinkError e) {
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
index 17f5e9585c22..7206c0377bf7 100644
--- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -164,7 +164,6 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver {
registerCarrierPrivilegesCallbacks();
}
- // TODO(b/221306368): Refactor with the new onCarrierServiceChange in the new CPCallback
private void registerCarrierPrivilegesCallbacks() {
final HandlerExecutor executor = new HandlerExecutor(mHandler);
final int modemCount = mTelephonyManager.getActiveModemCount();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 37f38252698e..601c7f450d4f 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -357,15 +357,30 @@ public class WallpaperCropper {
* Given some suggested crops, find cropHints for all orientations of the default display.
*/
SparseArray<Rect> getDefaultCrops(SparseArray<Rect> suggestedCrops, Point bitmapSize) {
- SparseArray<Rect> result = new SparseArray<>();
- // add missing cropHints for all orientation of the default display
+
SparseArray<Point> defaultDisplaySizes = mWallpaperDisplayHelper.getDefaultDisplaySizes();
boolean rtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
== View.LAYOUT_DIRECTION_RTL;
+
+ // adjust existing entries for the default display
+ SparseArray<Rect> adjustedSuggestedCrops = new SparseArray<>();
+ for (int i = 0; i < defaultDisplaySizes.size(); i++) {
+ int orientation = defaultDisplaySizes.keyAt(i);
+ Point displaySize = defaultDisplaySizes.valueAt(i);
+ Rect suggestedCrop = suggestedCrops.get(orientation);
+ if (suggestedCrop != null) {
+ adjustedSuggestedCrops.put(orientation,
+ getCrop(displaySize, bitmapSize, suggestedCrops, rtl));
+ }
+ }
+
+ // add missing cropHints for all orientation of the default display
+ SparseArray<Rect> result = adjustedSuggestedCrops.clone();
for (int i = 0; i < defaultDisplaySizes.size(); i++) {
int orientation = defaultDisplaySizes.keyAt(i);
+ if (result.contains(orientation)) continue;
Point displaySize = defaultDisplaySizes.valueAt(i);
- Rect newCrop = getCrop(displaySize, bitmapSize, suggestedCrops, rtl);
+ Rect newCrop = getCrop(displaySize, bitmapSize, adjustedSuggestedCrops, rtl);
result.put(orientation, newCrop);
}
return result;
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 5f6ffd988c84..8742ab1dd95b 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -21,8 +21,8 @@ import static android.provider.DeviceConfig.NAMESPACE_WEARABLE_SENSING;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
import android.app.BroadcastOptions;
-import android.app.ComponentOptions;
import android.app.PendingIntent;
import android.app.ambientcontext.AmbientContextEvent;
import android.app.wearable.IWearableSensingManager;
@@ -353,7 +353,7 @@ public class WearableSensingManagerService extends
dataRequest);
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setPendingIntentBackgroundActivityStartMode(
- ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
mDataRequestRateLimiter.noteEvent(
userId, RATE_LIMITER_PACKAGE_NAME, RATE_LIMITER_TAG);
final long previousCallingIdentity = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/ActivityAssistInfo.java b/services/core/java/com/android/server/wm/ActivityAssistInfo.java
index 3b91780431cb..2dc00460eeb9 100644
--- a/services/core/java/com/android/server/wm/ActivityAssistInfo.java
+++ b/services/core/java/com/android/server/wm/ActivityAssistInfo.java
@@ -30,12 +30,14 @@ public class ActivityAssistInfo {
private final IBinder mAssistToken;
private final int mTaskId;
private final ComponentName mComponentName;
+ private final int mUserId;
public ActivityAssistInfo(ActivityRecord activityRecord) {
this.mActivityToken = activityRecord.token;
this.mAssistToken = activityRecord.assistToken;
this.mTaskId = activityRecord.getTask().mTaskId;
this.mComponentName = activityRecord.mActivityComponent;
+ this.mUserId = activityRecord.mUserId;
}
/** @hide */
@@ -57,4 +59,9 @@ public class ActivityAssistInfo {
public ComponentName getComponentName() {
return mComponentName;
}
+
+ /** @hide */
+ public int getUserId() {
+ return mUserId;
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index ed5df5fab017..981c4c078dd1 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -506,7 +506,8 @@ class ActivityClientController extends IActivityClientController.Stub {
// keep backwards compatibility we remove the task from recents when finishing
// task with root activity.
mTaskSupervisor.removeTask(tr, false /*killProcess*/,
- finishWithRootActivity, "finish-activity", r.getUid(), r.info.name);
+ finishWithRootActivity, "finish-activity", r.getUid(), r.getPid(),
+ r.info.name);
res = true;
// Explicitly dismissing the activity so reset its relaunch flag.
r.mRelaunchReason = RELAUNCH_REASON_NONE;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d60fe4b7d7e1..90cff3950047 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1456,7 +1456,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mSizeConfigurations = sizeConfigurations;
}
- private void scheduleActivityMovedToDisplay(int displayId, Configuration config) {
+ private void scheduleActivityMovedToDisplay(int displayId, @NonNull Configuration config,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
if (!attachedToProcess()) {
ProtoLog.w(WM_DEBUG_SWITCH, "Can't report activity moved "
+ "to display - client not running, activityRecord=%s, displayId=%d",
@@ -1469,7 +1470,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
config);
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- MoveToDisplayItem.obtain(token, displayId, config));
+ MoveToDisplayItem.obtain(token, displayId, config, activityWindowInfo));
} catch (RemoteException e) {
// If process died, whatever.
}
@@ -9799,8 +9800,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Update last reported values.
final Configuration newMergedOverrideConfig = getMergedOverrideConfiguration();
+ final ActivityWindowInfo newActivityWindowInfo = getActivityWindowInfo();
setLastReportedConfiguration(getProcessGlobalConfiguration(), newMergedOverrideConfig);
+ setLastReportedActivityWindowInfo(newActivityWindowInfo);
if (mState == INITIALIZING) {
// No need to relaunch or schedule new config for activity that hasn't been launched
@@ -9817,7 +9820,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// There are no significant differences, so we won't relaunch but should still deliver
// the new configuration to the client process.
if (displayChanged) {
- scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
+ scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig,
+ newActivityWindowInfo);
} else {
scheduleConfigurationChanged(newMergedOverrideConfig);
}
@@ -9884,7 +9888,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// changes is always sent to all processes when they happen so it can just use whatever
// system level configuration it last got.
if (displayChanged) {
- scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig);
+ scheduleActivityMovedToDisplay(newDisplayId, newMergedOverrideConfig,
+ newActivityWindowInfo);
} else {
scheduleConfigurationChanged(newMergedOverrideConfig);
}
@@ -10059,7 +10064,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
pendingResults, pendingNewIntents, configChangeFlags,
new MergedConfiguration(getProcessGlobalConfiguration(),
getMergedOverrideConfiguration()),
- preserveWindow);
+ preserveWindow, getActivityWindowInfo());
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
lifecycleItem = ResumeActivityItem.obtain(token, isTransitionForward(),
diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
index 01d077a5bc55..4149bd9c6c56 100644
--- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
+++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
@@ -41,7 +41,7 @@ class ActivitySecurityModelFeatureFlags {
static final String DOC_LINK = "go/android-asm";
/** Used to determine which version of the ASM logic was used in logs while we iterate */
- static final int ASM_VERSION = 9;
+ static final int ASM_VERSION = 10;
private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER;
private static final String KEY_ASM_PREFIX = "ActivitySecurity__";
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 066d26222fbd..6ad056f5a902 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1548,6 +1548,8 @@ class ActivityStarter {
result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
startFlags, options, inTask, inTaskFragment, balVerdict,
intentGrants, realCallingUid);
+ } catch (Exception ex) {
+ Slog.e(TAG, "Exception on startActivityInner", ex);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
startedActivityRootTask = handleStartResult(r, options, result, newTransition,
@@ -2087,7 +2089,7 @@ class ActivityStarter {
if (!mSupervisor.getBackgroundActivityLaunchController().checkActivityAllowedToStart(
mSourceRecord, r, newTask, avoidMoveToFront(), targetTask, mLaunchFlags, mBalCode,
- mCallingUid, mRealCallingUid)) {
+ mCallingUid, mRealCallingUid, mPreferredTaskDisplayArea)) {
return START_ABORTED;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 4a5b2211800c..c0881180af94 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -822,4 +822,7 @@ public abstract class ActivityTaskManagerInternal {
*/
public abstract void unregisterCompatScaleProvider(
@CompatScaleProvider.CompatScaleModeOrderId int id);
+
+ /** Returns whether assist data is allowed. */
+ public abstract boolean isAssistDataAllowed();
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 514a3d87ecf0..218b7512b861 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6023,6 +6023,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
public int startActivityWithScreenshot(@NonNull Intent intent,
@NonNull String callingPackage, int callingUid, int callingPid,
@Nullable IBinder resultTo, @Nullable Bundle options, int userId) {
+ userId = getActivityStartController().checkTargetUser(userId,
+ false /* validateIncomingUser */, Binder.getCallingPid(),
+ Binder.getCallingUid(), "startActivityWithScreenshot");
+
return getActivityStartController()
.obtainStarter(intent, "startActivityWithScreenshot")
.setCallingUid(callingUid)
@@ -7347,6 +7351,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@CompatScaleProvider.CompatScaleModeOrderId int id) {
ActivityTaskManagerService.this.unregisterCompatScaleProvider(id);
}
+
+ @Override
+ public boolean isAssistDataAllowed() {
+ return ActivityTaskManagerService.this.isAssistDataAllowed();
+ }
}
static boolean isPip2ExperimentEnabled() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e0faddf171ca..d1d498dbac46 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -42,6 +42,7 @@ import static android.content.pm.PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
+import static android.os.Process.INVALID_PID;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -1652,11 +1653,11 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
* @return Returns true if the given task was found and removed.
*/
boolean removeTaskById(int taskId, boolean killProcess, boolean removeFromRecents,
- String reason, int callingUid) {
+ String reason, int callingUid, int callingPid) {
final Task task =
mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
if (task != null) {
- removeTask(task, killProcess, removeFromRecents, reason, callingUid, null);
+ removeTask(task, killProcess, removeFromRecents, reason, callingUid, callingPid, null);
return true;
}
Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId);
@@ -1664,11 +1665,11 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason) {
- removeTask(task, killProcess, removeFromRecents, reason, SYSTEM_UID, null);
+ removeTask(task, killProcess, removeFromRecents, reason, SYSTEM_UID, INVALID_PID, null);
}
void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason,
- int callingUid, String callerActivityClassName) {
+ int callingUid, int callingPid, String callerActivityClassName) {
if (task.mInRemoveTask) {
// Prevent recursion.
return;
@@ -1705,8 +1706,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
if (task.isPersistable) {
mService.notifyTaskPersisterLocked(null, true);
}
- mBalController
- .checkActivityAllowedToClearTask(task, callingUid, callerActivityClassName);
+ mBalController.checkActivityAllowedToClearTask(
+ task, callingUid, callingPid, callerActivityClassName);
} finally {
task.mInRemoveTask = false;
}
@@ -1874,7 +1875,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// Task was trimmed from the recent tasks list -- remove the active task record as well
// since the user won't really be able to go back to it
removeTaskById(task.mTaskId, killProcess, false /* removeFromRecents */,
- "recent-task-trimmed", SYSTEM_UID);
+ "recent-task-trimmed", SYSTEM_UID, INVALID_PID);
}
task.removedFromRecents();
}
@@ -1893,7 +1894,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// Check that we aren't reparenting to the same root task that the task is already in
if (prevRootTask != null && prevRootTask.mTaskId == rootTaskId) {
Slog.w(TAG, "Can not reparent to same root task, task=" + task
- + " already in rootTaskId=" + rootTaskId);
+ + " already in rootTaskId=" + rootTaskId + " by " + Debug.getCallers(8));
return prevRootTask;
}
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 50de0b08f3b0..d699af872c7f 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -77,11 +77,13 @@ class AppTaskImpl extends IAppTask.Stub {
synchronized (mService.mGlobalLock) {
int origCallingUid = Binder.getCallingUid();
+ int origCallingPid = Binder.getCallingPid();
final long callingIdentity = Binder.clearCallingIdentity();
try {
// We remove the task from recents to preserve backwards
if (!mService.mTaskSupervisor.removeTaskById(mTaskId, false,
- REMOVE_FROM_RECENTS, "finish-and-remove-task", origCallingUid)) {
+ REMOVE_FROM_RECENTS, "finish-and-remove-task", origCallingUid,
+ origCallingPid)) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
} finally {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index fdae53f59ad4..071f40342461 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -21,13 +21,16 @@ import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
-import static android.app.ComponentOptions.BackgroundActivityStartMode;
+import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static android.os.Process.INVALID_PID;
+import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
+import static com.android.server.wm.ActivityStarter.ASM_RESTRICTIONS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -67,6 +70,7 @@ import android.util.DebugUtils;
import android.util.Slog;
import android.widget.Toast;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
@@ -75,6 +79,7 @@ import com.android.server.UiThread;
import com.android.server.am.PendingIntentRecord;
import java.lang.annotation.Retention;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringJoiner;
import java.util.function.Consumer;
@@ -1022,7 +1027,7 @@ public class BackgroundActivityStartController {
}
/**
- * Log activity starts which violate one of the following rules of the
+ * Check activity starts which violate one of the following rules of the
* activity security model (ASM):
* See go/activity-security for rationale behind the rules.
* 1. Within a task, only an activity matching a top UID of the task can start activities
@@ -1032,7 +1037,7 @@ public class BackgroundActivityStartController {
boolean checkActivityAllowedToStart(@Nullable ActivityRecord sourceRecord,
@NonNull ActivityRecord targetRecord, boolean newTask, boolean avoidMoveTaskToFront,
@Nullable Task targetTask, int launchFlags, int balCode, int callingUid,
- int realCallingUid) {
+ int realCallingUid, TaskDisplayArea preferredTaskDisplayArea) {
// BAL Exception allowed in all cases
if (balCode == BAL_ALLOW_ALLOWLISTED_UID) {
return true;
@@ -1055,68 +1060,46 @@ public class BackgroundActivityStartController {
}
}
- if (balCode == BAL_ALLOW_GRACE_PERIOD) {
- // Allow if launching into new task, and caller matches most recently finished activity
- if (taskToFront && mTopFinishedActivity != null
- && mTopFinishedActivity.mUid == callingUid) {
- return true;
- }
-
- // Launching into existing task - allow if matches most recently finished activity
- // within the task.
- // We can reach here multiple ways:
- // 1. activity in fg fires intent (taskToFront = false, sourceRecord is available)
- // 2. activity in bg fires intent (taskToFront = false, sourceRecord is available)
- // 3. activity in bg fires intent with NEW_FLAG (taskToFront = true,
- // avoidMoveTaskToFront = true, sourceRecord is available)
- // 4. activity in bg fires PI (taskToFront = true, avoidMoveTaskToFront = true,
- // sourceRecord is not available, targetTask may be available)
- if (!taskToFront || avoidMoveTaskToFront) {
- if (targetTask != null) {
- FinishedActivityEntry finishedEntry =
- mTaskIdToFinishedActivity.get(targetTask.mTaskId);
- if (finishedEntry != null && finishedEntry.mUid == callingUid) {
- return true;
- }
- }
-
- if (sourceRecord != null) {
- FinishedActivityEntry finishedEntry =
- mTaskIdToFinishedActivity.get(sourceRecord.getTask().mTaskId);
- if (finishedEntry != null && finishedEntry.mUid == callingUid) {
- return true;
- }
- }
- }
- }
-
- BlockActivityStart bas = null;
+ BlockActivityStart bas = new BlockActivityStart();
if (sourceRecord != null) {
- boolean passesAsmChecks = true;
Task sourceTask = sourceRecord.getTask();
+ Task taskToCheck = taskToFront ? sourceTask : targetTask;
+ bas = checkTopActivityForAsm(taskToCheck, sourceRecord.getUid(),
+ sourceRecord, bas);
+
// Allow launching into a new task (or a task matching the launched activity's
// affinity) only if the current task is foreground or mutating its own task.
// The latter can happen eg. if caller uses NEW_TASK flag and the activity being
// launched matches affinity of source task.
- if (taskToFront) {
- passesAsmChecks = sourceTask != null
- && (sourceTask.isVisible() || sourceTask == targetTask);
- }
-
- if (passesAsmChecks) {
- Task taskToCheck = taskToFront ? sourceTask : targetTask;
- bas = isTopActivityMatchingUidAbsentForAsm(taskToCheck, sourceRecord.getUid(),
- sourceRecord);
+ if (taskToFront && bas.mTopActivityMatchesSource) {
+ bas.mTopActivityMatchesSource = (sourceTask != null
+ && (sourceTask.isVisible() || sourceTask == targetTask));
}
} else if (targetTask != null && (!taskToFront || avoidMoveTaskToFront)) {
// We don't have a sourceRecord, and we're launching into an existing task.
// Allow if callingUid is top of stack.
- bas = isTopActivityMatchingUidAbsentForAsm(targetTask, callingUid,
- /*sourceRecord*/null);
+ bas = checkTopActivityForAsm(targetTask, callingUid,
+ /*sourceRecord*/null, bas);
+ } else {
+ // We're launching from a non-visible activity. Has any visible app opted in?
+ TaskDisplayArea displayArea = targetTask != null && targetTask.getDisplayArea() != null
+ ? targetTask.getDisplayArea()
+ : preferredTaskDisplayArea;
+ if (displayArea != null) {
+ ArrayList<Task> visibleTasks = displayArea.getVisibleTasks();
+ for (int i = 0; i < visibleTasks.size(); i++) {
+ Task task = visibleTasks.get(i);
+ if (visibleTasks.size() == 1 && task.isActivityTypeHomeOrRecents()) {
+ bas.optedIn(task.getTopMostActivity());
+ } else {
+ bas = checkTopActivityForAsm(task, callingUid, /*sourceRecord*/null, bas);
+ }
+ }
+ }
}
- if (bas != null && !bas.mWouldBlockActivityStartIgnoringFlag) {
+ if (bas.mTopActivityMatchesSource) {
return true;
}
@@ -1140,13 +1123,16 @@ public class BackgroundActivityStartController {
? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK
: FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK);
- boolean blockActivityStartAndFeatureEnabled = ActivitySecurityModelFeatureFlags
- .shouldRestrictActivitySwitch(callingUid)
- && (bas == null || bas.mBlockActivityStartIfFlagEnabled);
+ boolean enforceBlock = bas.mTopActivityOptedIn
+ && ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(callingUid);
+
+ boolean allowedByGracePeriod = allowedByAsmGracePeriod(callingUid, sourceRecord, targetTask,
+ balCode, taskToFront, avoidMoveTaskToFront);
String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", sourceRecord,
targetRecord, targetTask, targetTopActivity, realCallingUid, balCode,
- blockActivityStartAndFeatureEnabled, taskToFront, avoidMoveTaskToFront);
+ enforceBlock, taskToFront, avoidMoveTaskToFront, allowedByGracePeriod,
+ bas.mActivityOptedIn);
FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
/* caller_uid */
@@ -1184,7 +1170,7 @@ public class BackgroundActivityStartController {
String launchedFromPackageName = targetRecord.launchedFromPackage;
if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK
- + (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ")
+ + (enforceBlock ? " blocked " : " would block ")
+ getApplicationLabel(mService.mContext.getPackageManager(),
launchedFromPackageName);
showToast(toastText);
@@ -1192,7 +1178,7 @@ public class BackgroundActivityStartController {
Slog.i(TAG, asmDebugInfo);
}
- if (blockActivityStartAndFeatureEnabled) {
+ if (enforceBlock) {
Slog.e(TAG, "[ASM] Abort Launching r: " + targetRecord
+ " as source: "
+ (sourceRecord != null ? sourceRecord : launchedFromPackageName)
@@ -1251,18 +1237,18 @@ public class BackgroundActivityStartController {
// Find the first activity which matches a safe UID and is not finishing. Clear everything
// above it
+ int[] finishCount = new int[1];
boolean shouldBlockActivityStart = ActivitySecurityModelFeatureFlags
.shouldRestrictActivitySwitch(callingUid);
- int[] finishCount = new int[0];
- if (shouldBlockActivityStart
- && blockCrossUidActivitySwitchFromBelowForActivity(targetTaskTop)) {
+ BlockActivityStart bas = checkCrossUidActivitySwitchFromBelow(
+ targetTaskTop, callingUid, new BlockActivityStart());
+ if (shouldBlockActivityStart && bas.mTopActivityOptedIn) {
ActivityRecord activity = targetTask.getActivity(isLaunchingOrLaunched);
if (activity == null) {
// mStartActivity is not in task, so clear everything
activity = targetRecord;
}
- finishCount = new int[1];
targetTask.performClearTop(activity, launchFlags, finishCount);
if (finishCount[0] > 0) {
Slog.w(TAG, "Cleared top n: " + finishCount[0] + " activities from task t: "
@@ -1279,7 +1265,8 @@ public class BackgroundActivityStartController {
Slog.i(TAG, getDebugInfoForActivitySecurity("Clear Top", sourceRecord, targetRecord,
targetTask, targetTaskTop, realCallingUid, balCode, shouldBlockActivityStart,
- /* taskToFront */ true, /* avoidMoveTaskToFront */ false));
+ /* taskToFront */ true, /* avoidMoveTaskToFront */ false,
+ /* allowedByAsmGracePeriod */ false, bas.mActivityOptedIn));
}
}
@@ -1287,7 +1274,7 @@ public class BackgroundActivityStartController {
* Returns home if the passed in callingUid is not top of the stack, rather than returning to
* previous task.
*/
- void checkActivityAllowedToClearTask(@NonNull Task task, int callingUid,
+ void checkActivityAllowedToClearTask(@NonNull Task task, int callingUid, int callingPid,
@NonNull String callerActivityClassName) {
// We may have already checked that the callingUid has additional clearTask privileges, and
// cleared the calling identify. If so, we infer we do not need further restrictions here.
@@ -1295,14 +1282,28 @@ public class BackgroundActivityStartController {
return;
}
+ String packageName = mService.mContext.getPackageManager().getNameForUid(callingUid);
+ BalState state = new BalState(callingUid, callingPid, packageName, INVALID_UID,
+ INVALID_PID, null, null, null, null, null, ActivityOptions.makeBasic());
+ @BalCode int balCode = checkBackgroundActivityStartAllowedByCaller(state).mCode;
+ if (balCode == BAL_ALLOW_ALLOWLISTED_UID
+ || balCode == BAL_ALLOW_ALLOWLISTED_COMPONENT
+ || balCode == BAL_ALLOW_PERMISSION
+ || balCode == BAL_ALLOW_SAW_PERMISSION
+ || balCode == BAL_ALLOW_VISIBLE_WINDOW
+ || balCode == BAL_ALLOW_NON_APP_VISIBLE_WINDOW) {
+ return;
+ }
+
TaskDisplayArea displayArea = task.getTaskDisplayArea();
if (displayArea == null) {
// If there is no associated display area, we can not return home.
return;
}
- BlockActivityStart bas = isTopActivityMatchingUidAbsentForAsm(task, callingUid, null);
- if (!bas.mWouldBlockActivityStartIgnoringFlag) {
+ BlockActivityStart bas = checkTopActivityForAsm(task, callingUid, null,
+ new BlockActivityStart());
+ if (bas.mTopActivityMatchesSource) {
return;
}
@@ -1339,8 +1340,7 @@ public class BackgroundActivityStartController {
);
boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
- .shouldRestrictActivitySwitch(callingUid)
- && bas.mBlockActivityStartIfFlagEnabled;
+ .shouldRestrictActivitySwitch(callingUid) && bas.mTopActivityOptedIn;
PackageManager pm = mService.mContext.getPackageManager();
String callingPackage = pm.getNameForUid(callingUid);
@@ -1381,32 +1381,30 @@ public class BackgroundActivityStartController {
* <p>
* The 'sourceRecord' can be considered top even if it is 'finishing'
* <p>
- * Returns a class where the elements are:
- * <pre>
- * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
- * consideration feature flag and targetSdk).
- * wouldBlockActivityStartIgnoringFlags: {@code true} if we should warn about the transition via
- * toasts. This happens if the transition would be blocked in case both the app was targeting V+
- * and the feature was enabled.
- * </pre>
*/
- private BlockActivityStart isTopActivityMatchingUidAbsentForAsm(@NonNull Task task,
- int uid, @Nullable ActivityRecord sourceRecord) {
+ private BlockActivityStart checkTopActivityForAsm(@NonNull Task task,
+ int uid, @Nullable ActivityRecord sourceRecord, BlockActivityStart bas) {
// If the source is visible, consider it 'top'.
if (sourceRecord != null && sourceRecord.isVisibleRequested()) {
- return BlockActivityStart.ACTIVITY_START_ALLOWED;
+ return bas.matchesSource();
}
- // Always allow actual top activity to clear task
+ // Always allow actual top activity
ActivityRecord topActivity = task.getTopMostActivity();
- if (topActivity != null && topActivity.isUid(uid)) {
- return BlockActivityStart.ACTIVITY_START_ALLOWED;
+ if (topActivity == null) {
+ Slog.wtf(TAG, "Activities for task: " + task + " not found.");
+ return bas.optedIn(topActivity);
+ }
+
+ bas = checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas);
+ if (bas.mTopActivityMatchesSource) {
+ return bas;
}
// If UID is visible in target task, allow launch
if (task.forAllActivities((Predicate<ActivityRecord>)
ar -> ar.isUid(uid) && ar.isVisibleRequested())) {
- return BlockActivityStart.ACTIVITY_START_ALLOWED;
+ return bas.matchesSource();
}
// Consider the source activity, whether or not it is finishing. Do not consider any other
@@ -1417,82 +1415,91 @@ public class BackgroundActivityStartController {
// Check top of stack (or the first task fragment for embedding).
topActivity = task.getActivity(topOfStackPredicate);
if (topActivity == null) {
- return new BlockActivityStart(true, true);
+ return bas;
}
- BlockActivityStart pair = blockCrossUidActivitySwitchFromBelow(topActivity, uid);
- if (!pair.mBlockActivityStartIfFlagEnabled) {
- return pair;
+ bas = checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas);
+ if (bas.mTopActivityMatchesSource) {
+ return bas;
}
// Even if the top activity is not a match, we may be in an embedded activity scenario with
// an adjacent task fragment. Get the second fragment.
TaskFragment taskFragment = topActivity.getTaskFragment();
if (taskFragment == null) {
- return pair;
+ return bas;
}
TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
if (adjacentTaskFragment == null) {
- return pair;
+ return bas;
}
// Check the second fragment.
topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate);
if (topActivity == null) {
- return new BlockActivityStart(true, true);
+ return bas;
}
- return blockCrossUidActivitySwitchFromBelow(topActivity, uid);
+ return checkCrossUidActivitySwitchFromBelow(topActivity, uid, bas);
}
/**
* Determines if a source is allowed to add or remove activities from the task,
* if the current ActivityRecord is above it in the stack
* <p>
- * A transition is blocked ({@code false} returned) if all of the following are met:
+ * A transition is blocked if all of the following are met:
* <pre>
* 1. The source activity and the current activity record belong to different apps
* (i.e, have different UIDs).
- * 2. Both the source activity and the current activity target U+
- * 3. The current activity has not set
+ * 2. The current activity target V+
+ * 3. The current app has set
+ * {@link R.styleable#AndroidManifestApplication_allowCrossUidActivitySwitchFromBelow}
+ * to {@code false}
+ * 4. The current activity has not set
* {@link ActivityRecord#setAllowCrossUidActivitySwitchFromBelow(boolean)} to {@code true}
* </pre>
*
- * Returns a class where the elements are:
- * <pre>
- * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
- * consideration feature flag and targetSdk).
- * wouldBlockActivityStartIgnoringFlags: {@code true} if we should warn about the transition via
- * toasts. This happens if the transition would be blocked in case both the app was targeting V+
- * and the feature was enabled.
- * </pre>
*
* @param sourceUid The source (s) activity performing the state change
*/
- private BlockActivityStart blockCrossUidActivitySwitchFromBelow(ActivityRecord ar,
- int sourceUid) {
+ private BlockActivityStart checkCrossUidActivitySwitchFromBelow(ActivityRecord ar,
+ int sourceUid, BlockActivityStart bas) {
if (ar.isUid(sourceUid)) {
- return BlockActivityStart.ACTIVITY_START_ALLOWED;
+ return bas.matchesSource();
}
- if (!blockCrossUidActivitySwitchFromBelowForActivity(ar)) {
- return BlockActivityStart.ACTIVITY_START_ALLOWED;
+ // We don't need to check package level if activity has opted out.
+ if (ar.mAllowCrossUidActivitySwitchFromBelow) {
+ bas.mTopActivityOptedIn = false;
+ return bas.matchesSource();
}
- // At this point, we would block if the feature is launched and both apps were V+
- // Since we have a feature flag, we need to check that too
- // TODO(b/258792202) Replace with CompatChanges and replace Pair with boolean once feature
- // flag is removed
- boolean restrictActivitySwitch =
- ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(ar.getUid())
- && ActivitySecurityModelFeatureFlags
- .shouldRestrictActivitySwitch(sourceUid);
- if (restrictActivitySwitch) {
- return BlockActivityStart.BLOCK;
- } else {
- return BlockActivityStart.LOG_ONLY;
+ if (!CompatChanges.isChangeEnabled(ASM_RESTRICTIONS, ar.getUid())) {
+ return bas;
}
+
+ if (ar.isUid(SYSTEM_UID)) {
+ return bas.optedIn(ar);
+ }
+
+ String packageName = ar.packageName;
+ if (packageName == null) {
+ Slog.wtf(TAG, "Package name: " + ar + " not found.");
+ return bas.optedIn(ar);
+ }
+
+ PackageManager pm = mService.mContext.getPackageManager();
+ ApplicationInfo applicationInfo;
+
+ try {
+ applicationInfo = pm.getApplicationInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.wtf(TAG, "Package name: " + packageName + " not found.");
+ return bas.optedIn(ar);
+ }
+
+ return applicationInfo.allowCrossUidActivitySwitchFromBelow ? bas : bas.optedIn(ar);
}
/**
@@ -1502,8 +1509,9 @@ public class BackgroundActivityStartController {
@Nullable ActivityRecord sourceRecord, @NonNull ActivityRecord targetRecord,
@Nullable Task targetTask, @Nullable ActivityRecord targetTopActivity,
int realCallingUid, @BalCode int balCode,
- boolean blockActivityStartAndFeatureEnabled, boolean taskToFront,
- boolean avoidMoveTaskToFront) {
+ boolean enforceBlock, boolean taskToFront,
+ boolean avoidMoveTaskToFront, boolean allowedByGracePeriod,
+ ActivityRecord activityOptedIn) {
final String prefix = "[ASM] ";
Function<ActivityRecord, String> recordToString = (ar) -> {
if (ar == null) {
@@ -1519,9 +1527,16 @@ public class BackgroundActivityStartController {
StringJoiner joiner = new StringJoiner("\n");
joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------");
- joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled);
+ joiner.add(prefix + "Block Enabled: " + enforceBlock);
+ if (!enforceBlock) {
+ joiner.add(prefix + "Feature Flag Enabled: " + android.security
+ .Flags.asmRestrictionsEnabled());
+ joiner.add(prefix + "Mendel Override: " + ActivitySecurityModelFeatureFlags
+ .asmRestrictionsEnabledForAll());
+ }
joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION);
joiner.add(prefix + "System Time: " + SystemClock.uptimeMillis());
+ joiner.add(prefix + "Activity Opted In: " + recordToString.apply(activityOptedIn));
boolean targetTaskMatchesSourceTask = targetTask != null
&& sourceRecord != null && sourceRecord.getTask() == targetTask;
@@ -1561,6 +1576,7 @@ public class BackgroundActivityStartController {
joiner.add(prefix + "TaskToFront: " + taskToFront);
joiner.add(prefix + "AvoidMoveToFront: " + avoidMoveTaskToFront);
joiner.add(prefix + "BalCode: " + balCodeToString(balCode));
+ joiner.add(prefix + "Allowed By Grace Period: " + allowedByGracePeriod);
joiner.add(prefix + "LastResumedActivity: "
+ recordToString.apply(mService.mLastResumedActivity));
@@ -1588,6 +1604,44 @@ public class BackgroundActivityStartController {
return joiner.toString();
}
+ private boolean allowedByAsmGracePeriod(int callingUid, @Nullable ActivityRecord sourceRecord,
+ @Nullable Task targetTask, @BalCode int balCode, boolean taskToFront,
+ boolean avoidMoveTaskToFront) {
+ if (balCode == BAL_ALLOW_GRACE_PERIOD) {
+ // Allow if launching into new task, and caller matches most recently finished activity
+ if (taskToFront && mTopFinishedActivity != null
+ && mTopFinishedActivity.mUid == callingUid) {
+ return true;
+ }
+
+ // Launching into existing task - allow if matches most recently finished activity
+ // within the task.
+ // We can reach here multiple ways:
+ // 1. activity in fg fires intent (taskToFront = false, sourceRecord is available)
+ // 2. activity in bg fires intent (taskToFront = false, sourceRecord is available)
+ // 3. activity in bg fires intent with NEW_FLAG (taskToFront = true,
+ // avoidMoveTaskToFront = true, sourceRecord is available)
+ // 4. activity in bg fires PI (taskToFront = true, avoidMoveTaskToFront = true,
+ // sourceRecord is not available, targetTask may be available)
+ if (!taskToFront || avoidMoveTaskToFront) {
+ if (targetTask != null) {
+ FinishedActivityEntry finishedEntry =
+ mTaskIdToFinishedActivity.get(targetTask.mTaskId);
+ if (finishedEntry != null && finishedEntry.mUid == callingUid) {
+ return true;
+ }
+ }
+
+ if (sourceRecord != null) {
+ FinishedActivityEntry finishedEntry =
+ mTaskIdToFinishedActivity.get(sourceRecord.getTask().mTaskId);
+ return finishedEntry != null && finishedEntry.mUid == callingUid;
+ }
+ }
+ }
+ return false;
+ }
+
private static boolean isSystemExemptFlagEnabled() {
return DeviceConfig.getBoolean(
NAMESPACE_WINDOW_MANAGER,
@@ -1698,55 +1752,22 @@ public class BackgroundActivityStartController {
}
}
- /**
- * Activity level allowCrossUidActivitySwitchFromBelow defaults to false.
- * Package level defaults to true.
- * We block the launch if dev has explicitly set package level to false, and activity level has
- * not opted out
- */
- private boolean blockCrossUidActivitySwitchFromBelowForActivity(@NonNull ActivityRecord ar) {
- // We don't need to check package level if activity has opted out.
- if (ar.mAllowCrossUidActivitySwitchFromBelow) {
- return false;
- }
-
- if (ActivitySecurityModelFeatureFlags.asmRestrictionsEnabledForAll()) {
- return true;
- }
-
- String packageName = ar.packageName;
- if (packageName == null) {
- return false;
- }
-
- PackageManager pm = mService.mContext.getPackageManager();
- ApplicationInfo applicationInfo;
-
- try {
- applicationInfo = pm.getApplicationInfo(packageName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.wtf(TAG, "Package name: " + packageName + " not found.");
- return false;
+ private static class BlockActivityStart {
+ private boolean mTopActivityOptedIn;
+ private boolean mTopActivityMatchesSource;
+ private ActivityRecord mActivityOptedIn;
+
+ BlockActivityStart optedIn(ActivityRecord activity) {
+ mTopActivityOptedIn = true;
+ if (mActivityOptedIn == null) {
+ mActivityOptedIn = activity;
+ }
+ return this;
}
- return !applicationInfo.allowCrossUidActivitySwitchFromBelow;
- }
-
- private static class BlockActivityStart {
- private static final BlockActivityStart ACTIVITY_START_ALLOWED =
- new BlockActivityStart(false, false);
- private static final BlockActivityStart LOG_ONLY = new BlockActivityStart(false, true);
- private static final BlockActivityStart BLOCK = new BlockActivityStart(true, true);
- // We should block if feature flag is enabled
- private final boolean mBlockActivityStartIfFlagEnabled;
- // Used for logging/toasts. Would we block if target sdk was V and feature was
- // enabled?
- private final boolean mWouldBlockActivityStartIgnoringFlag;
-
- private BlockActivityStart(boolean shouldBlockActivityStart,
- boolean wouldBlockActivityStartIgnoringFlags) {
- this.mBlockActivityStartIfFlagEnabled = shouldBlockActivityStart;
- this.mWouldBlockActivityStartIgnoringFlag = wouldBlockActivityStartIgnoringFlags;
+ BlockActivityStart matchesSource() {
+ mTopActivityMatchesSource = true;
+ return this;
}
}
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index f11d6ec0828c..925a0776bf1e 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -27,7 +27,7 @@ import android.os.SystemProperties;
import android.util.Slog;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier;
-import com.android.wm.shell.Flags;
+import com.android.window.flags.Flags;
/**
* The class that defines default launch params for tasks in desktop mode
*/
@@ -37,8 +37,6 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
TAG_WITH_CLASS_NAME ? "DesktopModeLaunchParamsModifier" : TAG_ATM;
private static final boolean DEBUG = false;
- // Desktop mode feature flags.
- private static final boolean ENABLE_DESKTOP_WINDOWING = Flags.enableDesktopWindowing();
private static final boolean DESKTOP_MODE_PROTO2_SUPPORTED =
SystemProperties.getBoolean("persist.wm.debug.desktop_mode_2", false);
public static final float DESKTOP_MODE_INITIAL_BOUNDS_SCALE =
@@ -67,6 +65,11 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
LaunchParamsController.LaunchParams currentParams,
LaunchParamsController.LaunchParams outParams) {
+ if (!isDesktopModeEnabled()) {
+ appendLog("desktop mode is not enabled, continuing");
+ return RESULT_CONTINUE;
+ }
+
if (task == null) {
appendLog("task null, skipping");
return RESULT_SKIP;
@@ -87,7 +90,7 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
// previous windowing mode to be restored even if the desktop mode state has changed.
// Let task launches inherit the windowing mode from the source task if available, which
// should have the desired windowing mode set by WM Shell. See b/286929122.
- if (isDesktopModeSupported() && source != null && source.getTask() != null) {
+ if (isDesktopModeEnabled() && source != null && source.getTask() != null) {
final Task sourceTask = source.getTask();
outParams.mWindowingMode = sourceTask.getWindowingMode();
appendLog("inherit-from-source=" + outParams.mWindowingMode);
@@ -140,14 +143,8 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier {
if (DEBUG) Slog.d(TAG, mLogBuilder.toString());
}
- /** Whether desktop mode is supported. */
- static boolean isDesktopModeSupported() {
- // Check for aconfig flag first
- if (ENABLE_DESKTOP_WINDOWING) {
- return true;
- }
- // Fall back to sysprop flag
- // TODO(b/304778354): remove sysprop once desktop aconfig flag supports dynamic overriding
- return DESKTOP_MODE_PROTO2_SUPPORTED;
+ /** Whether desktop mode is enabled. */
+ static boolean isDesktopModeEnabled() {
+ return Flags.enableDesktopWindowingMode();
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5cf9acdbc0d6..7f3df958664c 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -47,6 +47,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLO
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
@@ -1533,9 +1534,19 @@ public class DisplayPolicy {
// Check the windows that overlap with system bars to determine system bars' appearance.
if ((appWindow && attached == null && attrs.isFullscreen())
|| attrs.type == TYPE_VOICE_INTERACTION) {
- // Record the top-fullscreen-app-window which will be used to determine system UI
+
+ // If this is the exiting starting window, don't let it control the system bars.
+ // The app window behind it should be the controlling window instead. Reason: when an
+ // activity starts another activity behind a starting window, the app window of the
+ // first activity will lose the window focus. And then mTopFullscreenOpaqueWindowState
+ // will control the system bars. The logic here is to let first app window keep
+ // controlling system bars until the second app window is ready.
+ final boolean exitingStartingWindow =
+ attrs.type == TYPE_APPLICATION_STARTING && win.mAnimatingExit;
+
+ // Record the top-fullscreen-app-window which will be used to determine the system UI
// controlling window.
- if (mTopFullscreenOpaqueWindowState == null) {
+ if (mTopFullscreenOpaqueWindowState == null && !exitingStartingWindow) {
mTopFullscreenOpaqueWindowState = win;
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index b68e67e291e7..34ea4ac35224 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import static android.view.View.DRAG_FLAG_GLOBAL;
import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
+import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG;
import static com.android.input.flags.Flags.enablePointerChoreographer;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
@@ -373,8 +374,11 @@ class DragDropController {
boolean notifyUnhandledDrop(DragEvent dropEvent, String reason) {
final boolean isLocalDrag =
(mDragState.mFlags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) == 0;
+ final boolean shouldDelegateUnhandledDrag =
+ (mDragState.mFlags & DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG) != 0;
if (!com.android.window.flags.Flags.delegateUnhandledDrags()
|| mGlobalDragListener == null
+ || !shouldDelegateUnhandledDrag
|| isLocalDrag) {
// Skip if the flag is disabled, there is no unhandled-drag listener, or if this is a
// purely local drag
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 76038b945288..b266caa82056 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -216,8 +216,7 @@ class DragState {
mIsClosing = true;
// Unregister the input interceptor.
if (mInputInterceptor != null) {
- if (DEBUG_DRAG)
- Slog.d(TAG_WM, "unregistering drag input channel");
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "Unregistering drag input channel");
// Input channel should be disposed on the thread where the input is being handled.
mDragDropController.sendHandlerMessage(
@@ -227,9 +226,7 @@ class DragState {
// Send drag end broadcast if drag start has been sent.
if (mDragInProgress) {
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
- }
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "Broadcasting DRAG_ENDED");
for (WindowState ws : mNotifiedWindows) {
float x = 0;
float y = 0;
@@ -248,6 +245,7 @@ class DragState {
x, y, mThumbOffsetX, mThumbOffsetY, null, null, null, dragSurface, null,
mDragResult);
try {
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DRAG_ENDED to " + ws);
ws.mClient.dispatchDragEvent(event);
} catch (RemoteException e) {
Slog.w(TAG_WM, "Unable to drag-end window " + ws);
@@ -364,7 +362,7 @@ class DragState {
return false;
}
- if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to " + touchedWin);
final IBinder clientToken = touchedWin.mClient.asBinder();
final DragEvent event = createDropEvent(x, y, touchedWin, false /* includePrivateInfo */);
@@ -460,7 +458,7 @@ class DragState {
*/
CompletableFuture<Void> register(Display display) {
display.getRealSize(mDisplaySize);
- if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel");
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "Registering drag input channel");
if (mInputInterceptor != null) {
Slog.e(TAG_WM, "Duplicate register of drag input channel");
return completedFuture(null);
@@ -489,7 +487,7 @@ class DragState {
mSourceUserId, UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
+ Slog.d(TAG_WM, "Broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
}
final boolean containsAppExtras = containsApplicationExtras(mDataDescription);
diff --git a/services/core/java/com/android/server/wm/LaunchParamsController.java b/services/core/java/com/android/server/wm/LaunchParamsController.java
index 91bb8d007948..97b5925893ae 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsController.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsController.java
@@ -64,10 +64,7 @@ class LaunchParamsController {
void registerDefaultModifiers(ActivityTaskSupervisor supervisor) {
// {@link TaskLaunchParamsModifier} handles window layout preferences.
registerModifier(new TaskLaunchParamsModifier(supervisor));
- if (DesktopModeLaunchParamsModifier.isDesktopModeSupported()) {
- // {@link DesktopModeLaunchParamsModifier} handles default task size changes
- registerModifier(new DesktopModeLaunchParamsModifier());
- }
+ registerModifier(new DesktopModeLaunchParamsModifier());
}
/**
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index e06f2158dc14..79eb0dc620a5 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -24,3 +24,5 @@ per-file Background*Start* = set noparent
per-file Background*Start* = file:/BAL_OWNERS
per-file Background*Start* = ogunwale@google.com, louischang@google.com
+# File related to activity callers
+per-file ActivityCallerState.java = file:/core/java/android/app/COMPONENT_CALLER_OWNERS
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 4698b6b2925c..5df2edc808f6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -1079,4 +1079,10 @@ public abstract class WindowManagerInternal {
* Moves the current focus to the top activity window if the top activity is embedded.
*/
public abstract boolean moveFocusToTopEmbeddedWindowIfNeeded();
+
+ /**
+ * Returns an instance of {@link ScreenCapture.ScreenshotHardwareBuffer} containing the current
+ * screenshot.
+ */
+ public abstract ScreenCapture.ScreenshotHardwareBuffer takeAssistScreenshot();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 08d43ae6f4c9..c93cc074aa3d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4081,13 +4081,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- /**
- * Takes a snapshot of the screen. In landscape mode this grabs the whole screen.
- * In portrait mode, it grabs the upper region of the screen based on the vertical dimension
- * of the target image.
- */
- @Override
- public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) {
+ @Nullable
+ private ScreenCapture.ScreenshotHardwareBuffer takeAssistScreenshot() {
if (!checkCallingPermission(READ_FRAME_BUFFER, "requestAssistScreenshot()")) {
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
@@ -4106,24 +4101,34 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- final Bitmap bm;
+ final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer;
if (captureArgs != null) {
ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture =
ScreenCapture.createSyncCaptureListener();
ScreenCapture.captureLayers(captureArgs, syncScreenCapture);
- final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
- syncScreenCapture.getBuffer();
- bm = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+ screenshotBuffer = syncScreenCapture.getBuffer();
} else {
- bm = null;
+ screenshotBuffer = null;
}
- if (bm == null) {
+ if (screenshotBuffer == null) {
Slog.w(TAG_WM, "Failed to take screenshot");
}
+ return screenshotBuffer;
+ }
+
+ /**
+ * Takes a snapshot of the screen. In landscape mode this grabs the whole screen.
+ * In portrait mode, it grabs the upper region of the screen based on the vertical dimension
+ * of the target image.
+ */
+ @Override
+ public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) {
+ final ScreenCapture.ScreenshotHardwareBuffer shb = takeAssistScreenshot();
+ final Bitmap bm = shb != null ? shb.asBitmap() : null;
FgThread.getHandler().post(() -> {
try {
receiver.onHandleAssistScreenshot(bm);
@@ -8688,6 +8693,12 @@ public class WindowManagerService extends IWindowManager.Stub
return false;
}
}
+
+ @Override
+ public ScreenCapture.ScreenshotHardwareBuffer takeAssistScreenshot() {
+ // WMS.takeAssistScreenshot takes care of the locking.
+ return WindowManagerService.this.takeAssistScreenshot();
+ }
}
private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy {
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 9d45cfb2fb7e..027337f04b6e 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -18,7 +18,6 @@ package com.android.server.credentials;
import static android.credentials.selection.Constants.EXTRA_FINAL_RESPONSE_RECEIVER;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -161,26 +160,6 @@ public class CredentialManagerUi {
*/
public PendingIntent createPendingIntent(
RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
- return createPendingIntent(requestInfo, providerDataList, /*forAutofill=*/ false);
- }
-
- /**
- * Creates a {@link PendingIntent} to be used to invoke the credential manager selector UI,
- * by the calling app process. This intent is invoked from the Autofill flow, when the user
- * requests to bring up the 'All Options' page of the credential bottom-sheet. When the user
- * clicks on the pinned entry, the intent will bring up the 'All Options' page of the
- * bottom-sheet. The provider data list is processed by the credential autofill service for
- * each autofill id and passed in as an auth extra.
- *
- * @param requestInfo the information about the request
- */
- public PendingIntent createPendingIntentForAutofill(RequestInfo requestInfo) {
- return createPendingIntent(requestInfo, /*providerDataList=*/ null, /*forAutofill=*/ true);
- }
-
- private PendingIntent createPendingIntent(
- RequestInfo requestInfo, @Nullable ArrayList<ProviderData> providerDataList,
- boolean forAutofill) {
List<CredentialProviderInfo> allProviders =
CredentialProviderInfoFactory.getCredentialProviderServices(
mContext,
@@ -196,15 +175,9 @@ public class CredentialManagerUi {
disabledProvider.getComponentName().flattenToString())).toList();
Intent intent;
- if (forAutofill) {
- intent = IntentFactory.createCredentialSelectorIntentForAutofill(
- mContext, requestInfo, new ArrayList<>(disabledProviderDataList),
- mResultReceiver);
- } else {
- intent = IntentFactory.createCredentialSelectorIntent(
- mContext, requestInfo, providerDataList,
- new ArrayList<>(disabledProviderDataList), mResultReceiver);
- }
+ intent = IntentFactory.createCredentialSelectorIntent(
+ mContext, requestInfo, providerDataList,
+ new ArrayList<>(disabledProviderDataList), mResultReceiver);
intent.setAction(UUID.randomUUID().toString());
//TODO: Create unique pending intent using request code and cancel any pre-existing pending
// intents
@@ -213,4 +186,21 @@ public class CredentialManagerUi {
PendingIntent.FLAG_MUTABLE, /*options=*/null,
UserHandle.of(mUserId));
}
+
+ /**
+ * Creates an {@link Intent} to be used to invoke the credential manager selector UI,
+ * by the calling app process. This intent is invoked from the Autofill flow, when the user
+ * requests to bring up the 'All Options' page of the credential bottom-sheet. When the user
+ * clicks on the pinned entry, the intent will bring up the 'All Options' page of the
+ * bottom-sheet. The provider data list is processed by the credential autofill service for
+ * each autofill id and passed in as extras in the pending intent set as authentication
+ * of the pinned entry.
+ *
+ * @param requestInfo the information about the request
+ */
+ public Intent createIntentForAutofill(RequestInfo requestInfo) {
+ return IntentFactory.createCredentialSelectorIntentForAutofill(
+ mContext, requestInfo, new ArrayList<>(),
+ mResultReceiver);
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index ca72638fd0ff..3cb98eb4cd7a 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -20,6 +20,7 @@ import android.Manifest;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.credentials.Constants;
import android.credentials.CredentialProviderInfo;
import android.credentials.GetCandidateCredentialsException;
@@ -114,8 +115,7 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ
return;
}
- cancelExistingPendingIntent();
- mPendingIntent = mCredentialManagerUi.createPendingIntentForAutofill(
+ Intent intent = mCredentialManagerUi.createIntentForAutofill(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
@@ -129,7 +129,7 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ
try {
invokeClientCallbackSuccess(new GetCandidateCredentialsResponse(
- candidateProviderDataList, mPendingIntent));
+ candidateProviderDataList, intent));
} catch (RemoteException e) {
Slog.e(TAG, "Issue while responding to client with error : " + e);
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index fa63bc899cb5..cad9a09247e6 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -93,7 +93,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
android.credentials.GetCredentialRequest filteredRequest =
filterOptions(providerInfo.getCapabilities(),
getRequestSession.mClientRequest,
- providerInfo);
+ providerInfo, getRequestSession.mHybridService);
if (filteredRequest != null) {
Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
new HashMap<>();
@@ -129,7 +129,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
android.credentials.GetCredentialRequest filteredRequest =
filterOptions(providerInfo.getCapabilities(),
getRequestSession.mClientRequest,
- providerInfo);
+ providerInfo, getRequestSession.mHybridService);
if (filteredRequest != null) {
Map<String, CredentialOption> beginGetOptionToCredentialOptionMap =
new HashMap<>();
@@ -179,9 +179,19 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
private static android.credentials.GetCredentialRequest filterOptions(
List<String> providerCapabilities,
android.credentials.GetCredentialRequest clientRequest,
- CredentialProviderInfo info
- ) {
+ CredentialProviderInfo info,
+ String hybridService) {
Slog.i(TAG, "Filtering request options for: " + info.getComponentName());
+ if (android.credentials.flags.Flags.hybridFilterFixEnabled()) {
+ ComponentName hybridComponentName = ComponentName.unflattenFromString(hybridService);
+ if (hybridComponentName != null && hybridComponentName
+ .equals(info.getComponentName())) {
+ Slog.i(TAG, "Skipping filtering of options for hybrid service");
+ return clientRequest;
+ }
+ Slog.w(TAG, "Could not parse hybrid service while filtering options");
+ }
+
List<CredentialOption> filteredOptions = new ArrayList<>();
for (CredentialOption option : clientRequest.getCredentialOptions()) {
if (providerCapabilities.contains(option.getType())
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
index df7f3084aeb3..1000bfa5f6c9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java
@@ -53,7 +53,17 @@ class EnterpriseSpecificIdCalculator {
TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
mImei = telephonyService.getImei(0);
- mMeid = telephonyService.getMeid(0);
+ String meid;
+ try {
+ meid = telephonyService.getMeid(0);
+ } catch (UnsupportedOperationException doesNotSupportCdma) {
+ // Instead of catching the exception, we could check for FEATURE_TELEPHONY_CDMA.
+ // However that runs the risk of changing a device's existing ESID if on these devices
+ // telephonyService.getMeid() actually returns non-null even when the device does not
+ // declare FEATURE_TELEPHONY_CDMA.
+ meid = null;
+ }
+ mMeid = meid;
mSerialNumber = Build.getSerial();
WifiManager wifiManager = context.getSystemService(WifiManager.class);
Preconditions.checkState(wifiManager != null, "Unable to access WiFi service");
diff --git a/services/fakes/Android.bp b/services/fakes/Android.bp
new file mode 100644
index 000000000000..148054b31e89
--- /dev/null
+++ b/services/fakes/Android.bp
@@ -0,0 +1,20 @@
+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"],
+}
+
+// NOTE: These "fake" services are intended for use under the Ravenwood
+// deviceless test environment, and should *not* be included in the build
+// artifacts for physical devices, as they already supply "real" services
+filegroup {
+ name: "services.fakes-sources",
+ srcs: [
+ "java/**/*.java",
+ ],
+ path: "java",
+ visibility: ["//frameworks/base"],
+}
diff --git a/services/fakes/java/com/android/server/FakeClipboardService.java b/services/fakes/java/com/android/server/FakeClipboardService.java
new file mode 100644
index 000000000000..01016219e73d
--- /dev/null
+++ b/services/fakes/java/com/android/server/FakeClipboardService.java
@@ -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.server;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.IClipboard;
+import android.content.IOnPrimaryClipChangedListener;
+import android.os.PermissionEnforcer;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Fake implementation of {@code ClipboardManager} since the real implementation is tightly
+ * coupled with many other internal services.
+ */
+public class FakeClipboardService extends IClipboard.Stub {
+ private final RemoteCallbackList<IOnPrimaryClipChangedListener> mListeners =
+ new RemoteCallbackList<>();
+
+ private ClipData mPrimaryClip;
+ private String mPrimaryClipSource;
+
+ public FakeClipboardService(Context context) {
+ super(PermissionEnforcer.fromContext(context));
+ }
+
+ public static class Lifecycle extends SystemService {
+ private FakeClipboardService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mService = new FakeClipboardService(getContext());
+ publishBinderService(Context.CLIPBOARD_SERVICE, mService);
+ }
+ }
+
+ private static void checkArguments(int userId, int deviceId) {
+ Preconditions.checkArgument(userId == UserHandle.USER_SYSTEM,
+ "Fake only supports USER_SYSTEM user");
+ Preconditions.checkArgument(deviceId == Context.DEVICE_ID_DEFAULT,
+ "Fake only supports DEVICE_ID_DEFAULT device");
+ }
+
+ private void dispatchPrimaryClipChanged() {
+ mListeners.broadcast((listener) -> {
+ try {
+ listener.dispatchPrimaryClipChanged();
+ } catch (RemoteException ignored) {
+ }
+ });
+ }
+
+ @Override
+ public void setPrimaryClip(ClipData clip, String callingPackage, String attributionTag,
+ int userId, int deviceId) {
+ checkArguments(userId, deviceId);
+ mPrimaryClip = clip;
+ mPrimaryClipSource = callingPackage;
+ dispatchPrimaryClipChanged();
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.SET_CLIP_SOURCE)
+ public void setPrimaryClipAsPackage(ClipData clip, String callingPackage, String attributionTag,
+ int userId, int deviceId, String sourcePackage) {
+ setPrimaryClipAsPackage_enforcePermission();
+ checkArguments(userId, deviceId);
+ mPrimaryClip = clip;
+ mPrimaryClipSource = sourcePackage;
+ dispatchPrimaryClipChanged();
+ }
+
+ @Override
+ public void clearPrimaryClip(String callingPackage, String attributionTag, int userId,
+ int deviceId) {
+ checkArguments(userId, deviceId);
+ mPrimaryClip = null;
+ mPrimaryClipSource = null;
+ dispatchPrimaryClipChanged();
+ }
+
+ @Override
+ public ClipData getPrimaryClip(String pkg, String attributionTag, int userId, int deviceId) {
+ checkArguments(userId, deviceId);
+ return mPrimaryClip;
+ }
+
+ @Override
+ public ClipDescription getPrimaryClipDescription(String callingPackage, String attributionTag,
+ int userId, int deviceId) {
+ checkArguments(userId, deviceId);
+ return (mPrimaryClip != null) ? mPrimaryClip.getDescription() : null;
+ }
+
+ @Override
+ public boolean hasPrimaryClip(String callingPackage, String attributionTag, int userId,
+ int deviceId) {
+ checkArguments(userId, deviceId);
+ return mPrimaryClip != null;
+ }
+
+ @Override
+ public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
+ String callingPackage, String attributionTag, int userId, int deviceId) {
+ checkArguments(userId, deviceId);
+ mListeners.register(listener);
+ }
+
+ @Override
+ public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
+ String callingPackage, String attributionTag, int userId, int deviceId) {
+ checkArguments(userId, deviceId);
+ mListeners.unregister(listener);
+ }
+
+ @Override
+ public boolean hasClipboardText(String callingPackage, String attributionTag, int userId,
+ int deviceId) {
+ checkArguments(userId, deviceId);
+ return (mPrimaryClip != null) && (mPrimaryClip.getItemCount() > 0)
+ && (mPrimaryClip.getItemAt(0).getText() != null);
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.SET_CLIP_SOURCE)
+ public String getPrimaryClipSource(String callingPackage, String attributionTag, int userId,
+ int deviceId) {
+ getPrimaryClipSource_enforcePermission();
+ checkArguments(userId, deviceId);
+ return mPrimaryClipSource;
+ }
+
+ @Override
+ public boolean areClipboardAccessNotificationsEnabledForUser(int userId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setClipboardAccessNotificationsEnabledForUser(boolean enable, int userId) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index d4b57f191ecd..96e9ca0c19b5 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -275,6 +275,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
AndroidPackage::isUpdatableSystem,
AndroidPackage::getEmergencyInstaller,
AndroidPackage::isAllowCrossUidActivitySwitchFromBelow,
+ PackageImpl::isAppMetadataFileInApk,
)
override fun extraParams() = listOf(
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 418b78cd696b..9c9aeeaadb71 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
@@ -1016,4 +1017,34 @@ public class AutomaticBrightnessControllerTest {
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, 500));
assertEquals(500, mController.getAmbientLux(), EPSILON);
}
+
+ @Test
+ public void testBrightnessBasedOnLastObservedLux() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return 0.3f as a brightness value
+ float lux = 100.0f;
+ // Brightness as float (from 0.0f to 1.0f)
+ float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+ when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
+ .thenReturn(normalizedBrightness);
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+ when(mBrightnessThrottler.isThrottled()).thenReturn(true);
+
+ // Send a new sensor value, disable the sensor and verify
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+ mController.configure(AUTO_BRIGHTNESS_DISABLED, /* configuration= */ null,
+ /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT,
+ /* shouldResetShortTermModel= */ true);
+ assertEquals(normalizedBrightness,
+ mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ /* brightnessEvent= */ null), EPSILON);
+ }
}
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 807774f90655..fcee70fd0702 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1665,6 +1665,68 @@ public final class DisplayPowerControllerTest {
/* luxTimestamps= */ any());
}
+ @Test
+ public void testInitialDozeBrightness() {
+ when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
+ float brightness = 0.277f;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.automaticBrightnessController
+ .getAutomaticScreenBrightnessBasedOnLastObservedLux(any(BrightnessEvent.class)))
+ .thenReturn(brightness);
+ when(mHolder.hbmController.getCurrentBrightnessMax())
+ .thenReturn(PowerManager.BRIGHTNESS_MAX);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(brightness),
+ /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
+ /* ignoreAnimationLimits= */ anyBoolean());
+ }
+
+ @Test
+ public void testInitialDozeBrightness_AbcIsNull() {
+ when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, false);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ true,
+ /* isAutoBrightnessAvailable= */ false);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+ float brightness = 0.277f;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.automaticBrightnessController
+ .getAutomaticScreenBrightnessBasedOnLastObservedLux(any(BrightnessEvent.class)))
+ .thenReturn(brightness);
+ when(mHolder.hbmController.getCurrentBrightnessMax())
+ .thenReturn(PowerManager.BRIGHTNESS_MAX);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ // Automatic Brightness Controller is null so no initial doze brightness should be set and
+ // we should not crash
+ verify(mHolder.animator, never()).animateTo(eq(brightness),
+ /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
+ /* ignoreAnimationLimits= */ anyBoolean());
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
@@ -1749,6 +1811,12 @@ public final class DisplayPowerControllerTest {
private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
String uniqueId, boolean isEnabled) {
+ return createDisplayPowerController(displayId, uniqueId, isEnabled,
+ /* isAutoBrightnessAvailable= */ true);
+ }
+
+ private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
+ String uniqueId, boolean isEnabled, boolean isAutoBrightnessAvailable) {
final DisplayPowerState displayPowerState = mock(DisplayPowerState.class);
final DualRampAnimator<DisplayPowerState> animator = mock(DualRampAnimator.class);
final AutomaticBrightnessController automaticBrightnessController =
@@ -1783,6 +1851,7 @@ public final class DisplayPowerControllerTest {
final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
+ when(config.isAutoBrightnessAvailable()).thenReturn(isAutoBrightnessAvailable);
final DisplayPowerController dpc = new DisplayPowerController(
mContext, injector, mDisplayPowerCallbacksMock, mHandler,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
index c0c63c69add8..060f99b4317a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -16,9 +16,12 @@
package com.android.server.display.brightness;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+
import static org.junit.Assert.assertEquals;
import android.hardware.display.BrightnessInfo;
+import android.view.Display;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -39,6 +42,7 @@ public final class BrightnessEventTest {
mBrightnessEvent.setReason(
getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER));
mBrightnessEvent.setPhysicalDisplayId("test");
+ mBrightnessEvent.setDisplayState(Display.STATE_ON);
mBrightnessEvent.setLux(100.0f);
mBrightnessEvent.setPreThresholdLux(150.0f);
mBrightnessEvent.setTime(System.currentTimeMillis());
@@ -55,6 +59,7 @@ public final class BrightnessEventTest {
mBrightnessEvent.setAdjustmentFlags(0);
mBrightnessEvent.setAutomaticBrightnessEnabled(true);
mBrightnessEvent.setDisplayBrightnessStrategyName(DISPLAY_BRIGHTNESS_STRATEGY_NAME);
+ mBrightnessEvent.setAutoBrightnessMode(AUTO_BRIGHTNESS_MODE_IDLE);
}
@Test
@@ -69,20 +74,20 @@ public final class BrightnessEventTest {
public void testToStringWorksAsExpected() {
String actualString = mBrightnessEvent.toString(false);
String expectedString =
- "BrightnessEvent: disp=1, physDisp=test, brt=0.6, initBrt=25.0, rcmdBrt=0.6,"
- + " preBrt=NaN, lux=100.0, preLux=150.0, hbmMax=0.62, hbmMode=off, rbcStrength=-1,"
- + " thrmMax=0.65, powerFactor=0.2, wasShortTermModelActive=true, flags=,"
- + " reason=doze [ low_pwr ], autoBrightness=true, strategy="
- + DISPLAY_BRIGHTNESS_STRATEGY_NAME;
+ "BrightnessEvent: disp=1, physDisp=test, displayState=ON, brt=0.6, initBrt=25.0,"
+ + " rcmdBrt=0.6, preBrt=NaN, lux=100.0, preLux=150.0, hbmMax=0.62, hbmMode=off,"
+ + " rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, wasShortTermModelActive=true,"
+ + " flags=, reason=doze [ low_pwr ], autoBrightness=true, strategy="
+ + DISPLAY_BRIGHTNESS_STRATEGY_NAME + ", autoBrightnessMode=idle";
assertEquals(expectedString, actualString);
}
@Test
public void testFlagsToString() {
mBrightnessEvent.reset();
- mBrightnessEvent.setFlags(mBrightnessEvent.getFlags() | BrightnessEvent.FLAG_IDLE_CURVE);
+ mBrightnessEvent.setFlags(mBrightnessEvent.getFlags() | BrightnessEvent.FLAG_RBC);
String actualString = mBrightnessEvent.flagsToString();
- String expectedString = "idle_curve ";
+ String expectedString = "rbc ";
assertEquals(expectedString, actualString);
}
@@ -90,10 +95,10 @@ public final class BrightnessEventTest {
public void testFlagsToString_multipleFlags() {
mBrightnessEvent.reset();
mBrightnessEvent.setFlags(mBrightnessEvent.getFlags()
- | BrightnessEvent.FLAG_IDLE_CURVE
+ | BrightnessEvent.FLAG_RBC
| BrightnessEvent.FLAG_LOW_POWER_MODE);
String actualString = mBrightnessEvent.flagsToString();
- String expectedString = "idle_curve low_power_mode ";
+ String expectedString = "rbc low_power_mode ";
assertEquals(expectedString, actualString);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 5408e1143330..886780655de2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -359,11 +359,17 @@ public class AutomaticBrightnessStrategyTest {
AutomaticBrightnessController.class);
when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class)))
.thenReturn(automaticScreenBrightness);
+ when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ any(BrightnessEvent.class)))
+ .thenReturn(automaticScreenBrightness);
mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
automaticBrightnessController);
assertEquals(automaticScreenBrightness,
mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
new BrightnessEvent(DISPLAY_ID)), 0.0f);
+ assertEquals(automaticScreenBrightness,
+ mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ new BrightnessEvent(DISPLAY_ID)), 0.0f);
}
@Test
diff --git a/services/tests/dreamservicetests/Android.bp b/services/tests/dreamservicetests/Android.bp
index 5aa5d619854f..86b3a6cc2b35 100644
--- a/services/tests/dreamservicetests/Android.bp
+++ b/services/tests/dreamservicetests/Android.bp
@@ -18,6 +18,11 @@ android_test {
"mockito-target-minus-junit4",
"services.core",
"mockingservicestests-utils-mockito",
+ "servicestests-utils",
+ ],
+
+ defaults: [
+ "modules-utils-testable-device-config-defaults",
],
platform_apis: true,
diff --git a/services/tests/dreamservicetests/AndroidManifest.xml b/services/tests/dreamservicetests/AndroidManifest.xml
index f4b88d9c18e9..6092ef6f9427 100644
--- a/services/tests/dreamservicetests/AndroidManifest.xml
+++ b/services/tests/dreamservicetests/AndroidManifest.xml
@@ -21,6 +21,7 @@
Insert permissions here. eg:
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<application android:debuggable="true"
android:testOnly="true">
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
index 32d4e756d589..992b8534accc 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
@@ -36,13 +36,14 @@ import android.os.UserManager;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
-import com.android.server.LocalServices;
+import com.android.internal.util.test.LocalServiceKeeperRule;
import com.android.server.SystemService;
+import com.android.server.testutils.TestHandler;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -65,23 +66,24 @@ public class DreamManagerServiceMockingTest {
@Mock
private UserManager mUserManagerMock;
- private MockitoSession mMockitoSession;
+ @Rule
+ public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
- private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
- LocalServices.removeServiceForTest(clazz);
- LocalServices.addService(clazz, mock);
- }
+ private TestHandler mTestHandler;
+ private MockitoSession mMockitoSession;
@Before
public void setUp() throws Exception {
+ mTestHandler = new TestHandler(/* callback= */ null);
MockitoAnnotations.initMocks(this);
-
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
mResourcesSpy = spy(mContextSpy.getResources());
when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
- addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
- addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+ mLocalServiceKeeperRule.overrideLocalService(
+ ActivityManagerInternal.class, mActivityManagerInternalMock);
+ mLocalServiceKeeperRule.overrideLocalService(
+ PowerManagerInternal.class, mPowerManagerInternalMock);
when(mContextSpy.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
mMockitoSession = mockitoSession()
@@ -94,26 +96,20 @@ public class DreamManagerServiceMockingTest {
@After
public void tearDown() throws Exception {
mMockitoSession.finishMocking();
- LocalServices.removeServiceForTest(ActivityManagerInternal.class);
- LocalServices.removeServiceForTest(PowerManagerInternal.class);
}
private DreamManagerService createService() {
- return new DreamManagerService(mContextSpy);
+ return new DreamManagerService(mContextSpy, mTestHandler);
}
@Test
- @FlakyTest(bugId = 293443309)
public void testSettingsQueryUserChange() {
final DreamManagerService service = createService();
-
final SystemService.TargetUser from =
new SystemService.TargetUser(mock(UserInfo.class));
final SystemService.TargetUser to =
new SystemService.TargetUser(mock(UserInfo.class));
-
service.onUserSwitching(from, to);
-
verify(() -> Settings.Secure.getIntForUser(any(),
eq(Settings.Secure.SCREENSAVER_ENABLED),
anyInt(),
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
index d6e246fc7ee1..6e416857abbf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -25,6 +25,8 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -42,6 +44,7 @@ import android.content.rollback.RollbackManager;
import android.crashrecovery.flags.Flags;
import android.os.Handler;
import android.os.MessageQueue;
+import android.os.SystemProperties;
import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.runner.AndroidJUnit4;
@@ -65,6 +68,7 @@ import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
import java.time.Duration;
+import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -90,7 +94,7 @@ public class RollbackPackageHealthObserverTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
+ private HashMap<String, String> mSystemSettingsMap;
private MockitoSession mSession;
private static final String APP_A = "com.package.a";
private static final String APP_B = "com.package.b";
@@ -99,6 +103,9 @@ public class RollbackPackageHealthObserverTest {
private static final long VERSION_CODE_2 = 2L;
private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
+ private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
+ "persist.device_config.configuration.disable_high_impact_rollback";
+
private SystemConfig mSysConfig;
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@@ -111,11 +118,34 @@ public class RollbackPackageHealthObserverTest {
.initMocks(this)
.strictness(Strictness.LENIENT)
.spyStatic(PackageWatchdog.class)
+ .spyStatic(SystemProperties.class)
.startMocking();
+ mSystemSettingsMap = new HashMap<>();
// Mock PackageWatchdog
doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
.when(() -> PackageWatchdog.getInstance(mMockContext));
+
+ // Mock SystemProperties setter and various getters
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ String value = invocationOnMock.getArgument(1);
+
+ mSystemSettingsMap.put(key, value);
+ return null;
+ }
+ ).when(() -> SystemProperties.set(anyString(), anyString()));
+
+ doAnswer((Answer<Boolean>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ boolean defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Boolean.parseBoolean(storedValue);
+ }
+ ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean()));
+
+ SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(false));
}
@After
@@ -609,6 +639,32 @@ public class RollbackPackageHealthObserverTest {
observer.onBootLoop(1));
}
+ @Test
+ public void onBootLoop_impactLevelHighDisableHighImpactRollback_onePackage()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true));
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
+ null, null, false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onBootLoop(1));
+ }
+
/**
* When the rollback impact level is manual only return user impact level 0. (User impact level
* 0 is ignored by package watchdog)
@@ -924,6 +980,50 @@ public class RollbackPackageHealthObserverTest {
assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2));
}
+ /**
+ * Don't roll back if kill switch is enabled.
+ */
+ @Test
+ public void executeBootLoopMitigation_impactLevelHighKillSwitchTrue_rollbackHigh()
+ throws PackageManager.NameNotFoundException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION);
+ SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true));
+ int rollbackId1 = 1;
+ VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
+ VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoB),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ int rollbackId2 = 2;
+ VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
+ VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
+ PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
+ null, null , false, false,
+ null);
+ RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoA),
+ false, null, 111,
+ PackageManager.ROLLBACK_USER_IMPACT_HIGH);
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext, mApexManager));
+ ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(
+ List.of(rollbackInfo1, rollbackInfo2));
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
+
+ observer.executeBootLoopMitigation(1);
+ waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
+
+ verify(mRollbackManager, never()).commitRollback(
+ argument.capture(), any(), any());
+ }
+
private void waitForIdleHandler(Handler handler, Duration timeout) {
final MessageQueue queue = handler.getLooper().getQueue();
final CountDownLatch latch = new CountDownLatch(1);
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 3743483377b5..37967fa86b0f 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -85,6 +85,7 @@ android_test {
"ravenwood-junit",
"net_flags_lib",
"CtsVirtualDeviceCommonLib",
+ "com_android_server_accessibility_flags_lib",
],
libs: [
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterInputTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterInputTest.kt
index 5c8c6bb69ef8..e6c94c51d1b1 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterInputTest.kt
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityInputFilterInputTest.kt
@@ -25,6 +25,7 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.DisplayAdjustments
import android.view.DisplayInfo
import android.view.IInputFilterHost
+import android.view.InputDevice.SOURCE_JOYSTICK
import android.view.InputDevice.SOURCE_STYLUS
import android.view.InputDevice.SOURCE_TOUCHSCREEN
import android.view.InputEvent
@@ -130,6 +131,8 @@ class AccessibilityInputFilterInputTest {
private val fromTouchScreen = allOf(withDeviceId(touchDeviceId), withSource(SOURCE_TOUCHSCREEN))
private val stylusDeviceId = 2
private val fromStylus = allOf(withDeviceId(stylusDeviceId), withSource(STYLUS_SOURCE))
+ private val joystickDeviceId = 3
+ private val fromJoystick = allOf(withDeviceId(joystickDeviceId), withSource(SOURCE_JOYSTICK))
@Before
fun setUp() {
@@ -457,6 +460,23 @@ class AccessibilityInputFilterInputTest {
verifier.assertNoEvents()
}
+ /**
+ * Send some joystick events and ensure they pass through normally.
+ */
+ @Test
+ fun testJoystickEvents() {
+ enableFeatures(ALL_A11Y_FEATURES)
+
+ sendJoystickEvent()
+ verifier.assertReceivedMotion(fromJoystick)
+
+ sendJoystickEvent()
+ verifier.assertReceivedMotion(fromJoystick)
+
+ sendJoystickEvent()
+ verifier.assertReceivedMotion(fromJoystick)
+ }
+
private fun createStubDisplay(displayId: Int, displayInfo: DisplayInfo): Display {
val display = Display(DisplayManagerGlobal.getInstance(), displayId,
displayInfo, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS)
@@ -477,6 +497,11 @@ class AccessibilityInputFilterInputTest {
send(createMotionEvent(action, downTime, eventTime, STYLUS_SOURCE, stylusDeviceId))
}
+ private fun sendJoystickEvent() {
+ val time = SystemClock.uptimeMillis()
+ send(createMotionEvent(ACTION_MOVE, time, time, SOURCE_JOYSTICK, joystickDeviceId))
+ }
+
private fun send(event: InputEvent) {
// We need to make a copy of the event before sending it to the filter, because the filter
// will recycle it, but the caller of this function might want to still be able to use
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 89d146da1b75..8717a0500e57 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -47,6 +47,9 @@ import android.os.IBinder;
import android.os.LocaleList;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.SparseArray;
import android.view.Display;
import android.view.IWindow;
@@ -67,6 +70,7 @@ import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -77,9 +81,16 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+// This test verifies deprecated codepath. Probably changing this file means
+// AccessibilityWindowManagerWithAccessibilityWindowTest also needs to be updated.
+// LINT.IfChange
+
/**
- * Tests for the AccessibilityWindowManager
+ * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y enabled.
+ * TODO(b/322444245): Merge with AccessibilityWindowManagerWithAccessibilityWindowTest
+ * after completing the flag migration.
*/
+@RequiresFlagsDisabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y)
public class AccessibilityWindowManagerTest {
private static final String PACKAGE_NAME = "com.android.server.accessibility";
private static final boolean FORCE_SEND = true;
@@ -132,6 +143,9 @@ public class AccessibilityWindowManagerTest {
@Mock private IBinder mMockEmbeddedToken;
@Mock private IBinder mMockInvalidToken;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
@@ -1227,3 +1241,4 @@ public class AccessibilityWindowManagerTest {
}
}
}
+// LINT.ThenChange(/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java)
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
new file mode 100644
index 000000000000..4db27d272f8e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -0,0 +1,1247 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility;
+
+import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
+import static com.android.server.accessibility.AccessibilityWindowManagerTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowChangesMatcher.a11yWindowChanges;
+import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowIdMatcher.a11yWindowId;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.LocaleList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.IWindow;
+import android.view.WindowInfo;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowAttributes;
+import android.view.accessibility.AccessibilityWindowInfo;
+import android.view.accessibility.IAccessibilityInteractionConnection;
+
+import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for the AccessibilityWindowManager with Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y
+ * TODO(b/322444245): Merge with AccessibilityWindowManagerTest
+ * after completing the flag migration.
+ */
+@RequiresFlagsEnabled(Flags.FLAG_COMPUTE_WINDOW_CHANGES_ON_A11Y)
+public class AccessibilityWindowManagerWithAccessibilityWindowTest {
+ private static final String PACKAGE_NAME = "com.android.server.accessibility";
+ private static final boolean FORCE_SEND = true;
+ private static final boolean SEND_ON_WINDOW_CHANGES = false;
+ private static final int USER_SYSTEM_ID = UserHandle.USER_SYSTEM;
+ private static final int USER_PROFILE = 11;
+ private static final int USER_PROFILE_PARENT = 1;
+ private static final int SECONDARY_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1;
+ private static final int NUM_GLOBAL_WINDOWS = 4;
+ private static final int NUM_APP_WINDOWS = 4;
+ private static final int NUM_OF_WINDOWS = (NUM_GLOBAL_WINDOWS + NUM_APP_WINDOWS);
+ private static final int DEFAULT_FOCUSED_INDEX = 1;
+ private static final int SCREEN_WIDTH = 1080;
+ private static final int SCREEN_HEIGHT = 1920;
+ private static final int INVALID_ID = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
+ private static final int HOST_WINDOW_ID = 10;
+ private static final int EMBEDDED_WINDOW_ID = 11;
+ private static final int OTHER_WINDOW_ID = 12;
+
+ private AccessibilityWindowManager mA11yWindowManager;
+ // Window manager will support multiple focused window if config_perDisplayFocusEnabled is true,
+ // i.e., each display would have its current focused window, and one of all focused windows
+ // would be top focused window. Otherwise, window manager only supports one focused window
+ // at all displays, and that focused window would be top focused window.
+ private boolean mSupportPerDisplayFocus = false;
+ private int mTopFocusedDisplayId = Display.INVALID_DISPLAY;
+ private IBinder mTopFocusedWindowToken = null;
+
+ // List of window token, mapping from windowId -> window token.
+ private final SparseArray<IWindow> mA11yWindowTokens = new SparseArray<>();
+ // List of window info lists, mapping from displayId -> window info lists.
+ private final SparseArray<ArrayList<WindowInfo>> mWindowInfos =
+ new SparseArray<>();
+ // List of callback, mapping from displayId -> callback.
+ private final SparseArray<WindowsForAccessibilityCallback> mCallbackOfWindows =
+ new SparseArray<>();
+ // List of display ID.
+ private final ArrayList<Integer> mExpectedDisplayList = new ArrayList<>(Arrays.asList(
+ Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID));
+
+ private final MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
+
+ @Mock
+ private WindowManagerInternal mMockWindowManagerInternal;
+ @Mock
+ private AccessibilityWindowManager.AccessibilityEventSender mMockA11yEventSender;
+ @Mock
+ private AccessibilitySecurityPolicy mMockA11ySecurityPolicy;
+ @Mock
+ private AccessibilitySecurityPolicy.AccessibilityUserManager mMockA11yUserManager;
+ @Mock
+ private AccessibilityTraceManager mMockA11yTraceManager;
+
+ @Mock
+ private IBinder mMockHostToken;
+ @Mock
+ private IBinder mMockEmbeddedToken;
+ @Mock
+ private IBinder mMockInvalidToken;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Before
+ public void setUp() throws RemoteException {
+ MockitoAnnotations.initMocks(this);
+ when(mMockA11yUserManager.getCurrentUserIdLocked()).thenReturn(USER_SYSTEM_ID);
+ when(mMockA11ySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
+ USER_PROFILE)).thenReturn(USER_PROFILE_PARENT);
+ when(mMockA11ySecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
+ USER_SYSTEM_ID)).thenReturn(USER_SYSTEM_ID);
+ when(mMockA11ySecurityPolicy.resolveValidReportedPackageLocked(
+ anyString(), anyInt(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME);
+
+ doAnswer((invocation) -> {
+ onWindowsForAccessibilityChanged(invocation.getArgument(0), false);
+ return null;
+ }).when(mMockWindowManagerInternal).computeWindowsForAccessibility(anyInt());
+
+ mA11yWindowManager = new AccessibilityWindowManager(new Object(), mHandler,
+ mMockWindowManagerInternal,
+ mMockA11yEventSender,
+ mMockA11ySecurityPolicy,
+ mMockA11yUserManager,
+ mMockA11yTraceManager);
+ // Starts tracking window of default display and sets the default display
+ // as top focused display before each testing starts.
+ startTrackingPerDisplay(Display.DEFAULT_DISPLAY);
+
+ // AccessibilityEventSender is invoked during onWindowsForAccessibilityChanged.
+ // Resets it for mockito verify of further test case.
+ Mockito.reset(mMockA11yEventSender);
+
+ registerLeashedTokenAndWindowId();
+ }
+
+ @After
+ public void tearDown() {
+ mHandler.removeAllMessages();
+ }
+
+ @Test
+ public void startTrackingWindows_shouldEnableWindowManagerCallback() {
+ // AccessibilityWindowManager#startTrackingWindows already invoked in setup.
+ assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
+ final WindowsForAccessibilityCallback callbacks =
+ mCallbackOfWindows.get(Display.DEFAULT_DISPLAY);
+ verify(mMockWindowManagerInternal).setWindowsForAccessibilityCallback(
+ eq(Display.DEFAULT_DISPLAY), eq(callbacks));
+ }
+
+ @Test
+ public void stopTrackingWindows_shouldDisableWindowManagerCallback() {
+ assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
+ Mockito.reset(mMockWindowManagerInternal);
+
+ mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
+ assertFalse(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
+ verify(mMockWindowManagerInternal).setWindowsForAccessibilityCallback(
+ eq(Display.DEFAULT_DISPLAY), isNull());
+
+ }
+
+ @Test
+ public void stopTrackingWindows_shouldClearWindows() {
+ assertTrue(mA11yWindowManager.isTrackingWindowsLocked(Display.DEFAULT_DISPLAY));
+ final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
+
+ mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
+ assertNull(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY));
+ assertEquals(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
+ AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
+ assertEquals(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID),
+ activeWindowId);
+ }
+
+ @Test
+ public void stopTrackingWindows_onNonTopFocusedDisplay_shouldNotResetTopFocusWindow()
+ throws RemoteException {
+ // At setup, the default display sets be the top focused display and
+ // its current focused window sets be the top focused window.
+ // Starts tracking window of second display.
+ startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+ assertTrue(mA11yWindowManager.isTrackingWindowsLocked(SECONDARY_DISPLAY_ID));
+ // Stops tracking windows of second display.
+ mA11yWindowManager.stopTrackingWindows(SECONDARY_DISPLAY_ID);
+ assertNotEquals(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
+ AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
+ }
+
+ @Test
+ public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() {
+ final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
+ WindowInfo focusedWindowInfo =
+ mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX);
+ assertEquals(activeWindowId, mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, focusedWindowInfo.token));
+
+ focusedWindowInfo.focused = false;
+ focusedWindowInfo =
+ mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1);
+ focusedWindowInfo.focused = true;
+
+ mA11yWindowManager.onTouchInteractionStart();
+ setTopFocusedWindowAndDisplay(Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX + 1);
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
+ }
+
+ @Test
+ public void
+ onWindowsChanged_focusChangeOnNonTopFocusedDisplay_perDisplayFocusOn_notChangeWindow()
+ throws RemoteException {
+ // At setup, the default display sets be the top focused display and
+ // its current focused window sets be the top focused window.
+ // Sets supporting multiple focused window, i.e., config_perDisplayFocusEnabled is true.
+ mSupportPerDisplayFocus = true;
+ // Starts tracking window of second display.
+ startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+ // Gets the active window.
+ final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
+ // Gets the top focused window.
+ final int topFocusedWindowId =
+ mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT);
+ // Changes the current focused window at second display.
+ changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
+ DEFAULT_FOCUSED_INDEX + 1, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
+
+ onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+ // The active window should not be changed.
+ assertEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
+ // The top focused window should not be changed.
+ assertEquals(topFocusedWindowId,
+ mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT));
+ }
+
+ @Test
+ public void
+ onWindowChange_focusChangeToNonTopFocusedDisplay_perDisplayFocusOff_shouldChangeWindow()
+ throws RemoteException {
+ // At setup, the default display sets be the top focused display and
+ // its current focused window sets be the top focused window.
+ // Sets not supporting multiple focused window, i.e., config_perDisplayFocusEnabled is
+ // false.
+ mSupportPerDisplayFocus = false;
+ // Starts tracking window of second display.
+ startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+ // Gets the active window.
+ final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
+ // Gets the top focused window.
+ final int topFocusedWindowId =
+ mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT);
+ // Changes the current focused window from default display to second display.
+ changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
+ DEFAULT_FOCUSED_INDEX, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
+
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+ // The active window should be changed.
+ assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
+ // The top focused window should be changed.
+ assertNotEquals(topFocusedWindowId,
+ mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT));
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportCorrectLayer() {
+ // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup.
+ List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ for (int i = 0; i < a11yWindows.size(); i++) {
+ final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
+ final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i);
+ assertThat(mWindowInfos.get(Display.DEFAULT_DISPLAY).size() - windowInfo.layer - 1,
+ is(a11yWindow.getLayer()));
+ }
+ }
+
+ @Test
+ public void onWindowsChanged_shouldReportCorrectOrder() {
+ // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup.
+ List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ for (int i = 0; i < a11yWindows.size(); i++) {
+ final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
+ final IBinder windowToken = mA11yWindowManager
+ .getWindowTokenForUserAndWindowIdLocked(USER_SYSTEM_ID, a11yWindow.getId());
+ final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i);
+ assertThat(windowToken, is(windowInfo.token));
+ }
+ }
+
+ @Test
+ public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
+ final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
+ final int correctLayer =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer();
+ windowInfo.layer += 1;
+
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ assertNotEquals(correctLayer,
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer());
+ }
+
+ @Test
+ public void onWindowsChangedNoForceSend_layerChanged_shouldNotUpdateWindows() {
+ final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
+ final int correctLayer =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer();
+ windowInfo.layer += 1;
+
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ assertEquals(correctLayer,
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer());
+ }
+
+ @Test
+ public void onWindowsChangedNoForceSend_windowChanged_shouldUpdateWindows()
+ throws RemoteException {
+ final AccessibilityWindowInfo oldWindow =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0);
+ final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ true, USER_SYSTEM_ID);
+ final WindowInfo windowInfo = WindowInfo.obtain();
+ windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
+ windowInfo.token = token.asBinder();
+ windowInfo.layer = 0;
+ windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+ mWindowInfos.get(Display.DEFAULT_DISPLAY).set(0, windowInfo);
+
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ assertNotEquals(oldWindow,
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0));
+ }
+
+ @Test
+ public void onWindowsChangedNoForceSend_focusChanged_shouldUpdateWindows() {
+ final WindowInfo focusedWindowInfo =
+ mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX);
+ final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
+ focusedWindowInfo.focused = false;
+ windowInfo.focused = true;
+
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ assertTrue(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0)
+ .isFocused());
+ }
+
+ @Test
+ public void removeAccessibilityInteractionConnection_byWindowToken_shouldRemoved() {
+ for (int i = 0; i < NUM_OF_WINDOWS; i++) {
+ final int windowId = mA11yWindowTokens.keyAt(i);
+ final IWindow windowToken = mA11yWindowTokens.valueAt(i);
+ assertNotNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId));
+
+ mA11yWindowManager.removeAccessibilityInteractionConnection(windowToken);
+ assertNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId));
+ }
+ }
+
+ @Test
+ public void remoteAccessibilityConnection_binderDied_shouldRemoveConnection() {
+ for (int i = 0; i < NUM_OF_WINDOWS; i++) {
+ final int windowId = mA11yWindowTokens.keyAt(i);
+ final RemoteAccessibilityConnection remoteA11yConnection =
+ mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId);
+ assertNotNull(remoteA11yConnection);
+
+ remoteA11yConnection.binderDied();
+ assertNull(mA11yWindowManager.getConnectionLocked(USER_SYSTEM_ID, windowId));
+ }
+ }
+
+ @Test
+ public void getWindowTokenForUserAndWindowId_shouldNotNull() {
+ final List<AccessibilityWindowInfo> windows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ for (int i = 0; i < windows.size(); i++) {
+ final int windowId = windows.get(i).getId();
+
+ assertNotNull(mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(
+ USER_SYSTEM_ID, windowId));
+ }
+ }
+
+ @Test
+ public void findWindowId() {
+ final List<AccessibilityWindowInfo> windows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ for (int i = 0; i < windows.size(); i++) {
+ final int windowId = windows.get(i).getId();
+ final IBinder windowToken = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(
+ USER_SYSTEM_ID, windowId);
+
+ assertEquals(mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, windowToken), windowId);
+ }
+ }
+
+ @Test
+ public void resolveParentWindowId_windowIsNotEmbedded_shouldReturnGivenId()
+ throws RemoteException {
+ final int windowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY, false,
+ Mockito.mock(IBinder.class), USER_SYSTEM_ID);
+ assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId));
+ }
+
+ @Test
+ public void resolveParentWindowId_windowIsNotRegistered_shouldReturnGivenId() {
+ final int windowId = -1;
+ assertEquals(windowId, mA11yWindowManager.resolveParentWindowIdLocked(windowId));
+ }
+
+ @Test
+ public void resolveParentWindowId_windowIsAssociated_shouldReturnParentWindowId()
+ throws RemoteException {
+ final IBinder mockHostToken = Mockito.mock(IBinder.class);
+ final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class);
+ final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, mockHostToken, USER_SYSTEM_ID);
+ final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, mockEmbeddedToken, USER_SYSTEM_ID);
+
+ mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken);
+
+ final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked(
+ embeddedWindowId);
+ assertEquals(hostWindowId, resolvedWindowId);
+ }
+
+ @Test
+ public void resolveParentWindowId_windowIsDisassociated_shouldReturnGivenId()
+ throws RemoteException {
+ final IBinder mockHostToken = Mockito.mock(IBinder.class);
+ final IBinder mockEmbeddedToken = Mockito.mock(IBinder.class);
+ final int hostWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, mockHostToken, USER_SYSTEM_ID);
+ final int embeddedWindowId = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, mockEmbeddedToken, USER_SYSTEM_ID);
+
+ mA11yWindowManager.associateEmbeddedHierarchyLocked(mockHostToken, mockEmbeddedToken);
+ mA11yWindowManager.disassociateEmbeddedHierarchyLocked(mockEmbeddedToken);
+
+ final int resolvedWindowId = mA11yWindowManager.resolveParentWindowIdLocked(
+ embeddedWindowId);
+ assertNotEquals(hostWindowId, resolvedWindowId);
+ assertEquals(embeddedWindowId, resolvedWindowId);
+ }
+
+ @Test
+ public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() {
+ // Updates top 2 z-order WindowInfo are whole visible.
+ WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
+ windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
+ windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1);
+ windowInfo.regionInScreen.set(0, SCREEN_HEIGHT / 2,
+ SCREEN_WIDTH, SCREEN_HEIGHT);
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ final Region outBounds = new Region();
+ int windowId = a11yWindows.get(0).getId();
+
+ mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
+ assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
+ assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2));
+
+ windowId = a11yWindows.get(1).getId();
+
+ mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
+ assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
+ assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2));
+ }
+
+ @Test
+ public void computePartialInteractiveRegionForWindow_halfVisible_returnHalfRegion() {
+ // Updates z-order #1 WindowInfo is half visible.
+ WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
+ windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
+
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ final Region outBounds = new Region();
+ int windowId = a11yWindows.get(1).getId();
+
+ mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
+ assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
+ assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT / 2));
+ }
+
+ @Test
+ public void computePartialInteractiveRegionForWindow_notVisible_returnEmptyRegion() {
+ // Since z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ final Region outBounds = new Region();
+ int windowId = a11yWindows.get(1).getId();
+
+ mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
+ assertTrue(outBounds.getBounds().isEmpty());
+ }
+
+ @Test
+ public void computePartialInteractiveRegionForWindow_partialVisible_returnVisibleRegion() {
+ // Updates z-order #0 WindowInfo to have two interact-able areas.
+ Region region = new Region(0, 0, SCREEN_WIDTH, 200);
+ region.op(0, SCREEN_HEIGHT - 200, SCREEN_WIDTH, SCREEN_HEIGHT, Region.Op.UNION);
+ WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
+ windowInfo.regionInScreen.set(region);
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ final List<AccessibilityWindowInfo> a11yWindows =
+ mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ final Region outBounds = new Region();
+ int windowId = a11yWindows.get(1).getId();
+
+ mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
+ assertFalse(outBounds.getBounds().isEmpty());
+ assertThat(outBounds.getBounds().width(), is(SCREEN_WIDTH));
+ assertThat(outBounds.getBounds().height(), is(SCREEN_HEIGHT - 400));
+ }
+
+ @Test
+ public void updateActiveAndA11yFocusedWindow_windowStateChangedEvent_noTracking_shouldUpdate() {
+ final IBinder eventWindowToken =
+ mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1).token;
+ final int eventWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, eventWindowToken);
+ when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
+ .thenReturn(eventWindowToken);
+
+ final int noUse = 0;
+ mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
+ mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+ eventWindowId,
+ noUse,
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+ noUse);
+ assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId));
+ assertThat(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
+ is(eventWindowId));
+ }
+
+ @Test
+ public void updateActiveAndA11yFocusedWindow_hoverEvent_touchInteract_shouldSetActiveWindow() {
+ final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
+ DEFAULT_FOCUSED_INDEX + 1);
+ final int currentActiveWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
+ assertThat(currentActiveWindowId, is(not(eventWindowId)));
+
+ final int noUse = 0;
+ mA11yWindowManager.onTouchInteractionStart();
+ mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+ eventWindowId,
+ noUse,
+ AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+ noUse);
+ assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId));
+ final ArgumentCaptor<AccessibilityEvent> captor =
+ ArgumentCaptor.forClass(AccessibilityEvent.class);
+ verify(mMockA11yEventSender, times(2))
+ .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+ assertThat(captor.getAllValues().get(0),
+ allOf(displayId(Display.DEFAULT_DISPLAY),
+ a11yWindowId(currentActiveWindowId),
+ a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
+ assertThat(captor.getAllValues().get(1),
+ allOf(displayId(Display.DEFAULT_DISPLAY),
+ a11yWindowId(eventWindowId),
+ a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
+ }
+
+ @Test
+ public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_shouldUpdateA11yFocus() {
+ final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
+ DEFAULT_FOCUSED_INDEX);
+ final int currentA11yFocusedWindowId = mA11yWindowManager.getFocusedWindowId(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
+ assertThat(currentA11yFocusedWindowId, is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID));
+
+ final int noUse = 0;
+ mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+ eventWindowId,
+ AccessibilityNodeInfo.ROOT_NODE_ID,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ noUse);
+ assertThat(mA11yWindowManager.getFocusedWindowId(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
+ final ArgumentCaptor<AccessibilityEvent> captor =
+ ArgumentCaptor.forClass(AccessibilityEvent.class);
+ verify(mMockA11yEventSender, times(1))
+ .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+ assertThat(captor.getAllValues().get(0),
+ allOf(displayId(Display.DEFAULT_DISPLAY),
+ a11yWindowId(eventWindowId),
+ a11yWindowChanges(
+ AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
+ }
+
+ @Test
+ public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_multiDisplay_defaultToSecondary()
+ throws RemoteException {
+ runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
+ Display.DEFAULT_DISPLAY, SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void updateActiveAndA11yFocusedWindow_a11yFocusEvent_multiDisplay_SecondaryToDefault()
+ throws RemoteException {
+ runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
+ SECONDARY_DISPLAY_ID, Display.DEFAULT_DISPLAY);
+ }
+
+ private void runUpdateActiveAndA11yFocusedWindow_MultiDisplayTest(
+ int initialDisplayId, int eventDisplayId) throws RemoteException {
+ startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+ final int initialWindowId = getWindowIdFromWindowInfosForDisplay(
+ initialDisplayId, DEFAULT_FOCUSED_INDEX);
+ final int noUse = 0;
+ mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+ initialWindowId,
+ AccessibilityNodeInfo.ROOT_NODE_ID,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ noUse);
+ assertThat(mA11yWindowManager.getFocusedWindowId(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(initialWindowId));
+ Mockito.reset(mMockA11yEventSender);
+
+ final int eventWindowId = getWindowIdFromWindowInfosForDisplay(
+ eventDisplayId, DEFAULT_FOCUSED_INDEX);
+ mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+ eventWindowId,
+ AccessibilityNodeInfo.ROOT_NODE_ID,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ noUse);
+ assertThat(mA11yWindowManager.getFocusedWindowId(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
+ final ArgumentCaptor<AccessibilityEvent> captor =
+ ArgumentCaptor.forClass(AccessibilityEvent.class);
+ verify(mMockA11yEventSender, times(2))
+ .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+ assertThat(captor.getAllValues().get(0),
+ allOf(displayId(initialDisplayId),
+ a11yWindowId(initialWindowId),
+ a11yWindowChanges(
+ AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
+ assertThat(captor.getAllValues().get(1),
+ allOf(displayId(eventDisplayId),
+ a11yWindowId(eventWindowId),
+ a11yWindowChanges(
+ AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED)));
+ }
+
+ @Test
+ public void updateActiveAndA11yFocusedWindow_clearA11yFocusEvent_shouldClearA11yFocus() {
+ final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
+ DEFAULT_FOCUSED_INDEX);
+ final int currentA11yFocusedWindowId = mA11yWindowManager.getFocusedWindowId(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY);
+ assertThat(currentA11yFocusedWindowId, is(not(eventWindowId)));
+
+ final int noUse = 0;
+ mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+ eventWindowId,
+ AccessibilityNodeInfo.ROOT_NODE_ID,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ noUse);
+ assertThat(mA11yWindowManager.getFocusedWindowId(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(eventWindowId));
+ mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+ eventWindowId,
+ AccessibilityNodeInfo.ROOT_NODE_ID,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+ noUse);
+ assertThat(mA11yWindowManager.getFocusedWindowId(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY),
+ is(AccessibilityWindowInfo.UNDEFINED_WINDOW_ID));
+ }
+
+ @Test
+ public void onTouchInteractionEnd_shouldRollbackActiveWindow() {
+ final int eventWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
+ DEFAULT_FOCUSED_INDEX + 1);
+ final int currentActiveWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
+ assertThat(currentActiveWindowId, is(not(eventWindowId)));
+
+ final int noUse = 0;
+ mA11yWindowManager.onTouchInteractionStart();
+ mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+ eventWindowId,
+ noUse,
+ AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+ noUse);
+ assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(eventWindowId));
+ // AccessibilityEventSender is invoked after active window changed. Reset it.
+ Mockito.reset(mMockA11yEventSender);
+
+ mA11yWindowManager.onTouchInteractionEnd();
+ final ArgumentCaptor<AccessibilityEvent> captor =
+ ArgumentCaptor.forClass(AccessibilityEvent.class);
+ verify(mMockA11yEventSender, times(2))
+ .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+ assertThat(captor.getAllValues().get(0),
+ allOf(displayId(Display.DEFAULT_DISPLAY),
+ a11yWindowId(eventWindowId),
+ a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
+ assertThat(captor.getAllValues().get(1),
+ allOf(displayId(Display.DEFAULT_DISPLAY),
+ a11yWindowId(currentActiveWindowId),
+ a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ACTIVE)));
+ }
+
+ @Test
+ public void onTouchInteractionEnd_noServiceInteractiveWindow_shouldClearA11yFocus()
+ throws RemoteException {
+ final IBinder defaultFocusWinToken =
+ mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).token;
+ final int defaultFocusWindowId = mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, defaultFocusWinToken);
+ when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
+ .thenReturn(defaultFocusWinToken);
+ final int newFocusWindowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY,
+ DEFAULT_FOCUSED_INDEX + 1);
+ final IAccessibilityInteractionConnection mockNewFocusConnection =
+ mA11yWindowManager.getConnectionLocked(
+ USER_SYSTEM_ID, newFocusWindowId).getRemote();
+
+ mA11yWindowManager.stopTrackingWindows(Display.DEFAULT_DISPLAY);
+ final int noUse = 0;
+ mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+ defaultFocusWindowId,
+ noUse,
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+ noUse);
+ assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(defaultFocusWindowId));
+ assertThat(mA11yWindowManager.getFocusedWindowId(AccessibilityNodeInfo.FOCUS_INPUT),
+ is(defaultFocusWindowId));
+
+ mA11yWindowManager.onTouchInteractionStart();
+ mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+ newFocusWindowId,
+ noUse,
+ AccessibilityEvent.TYPE_VIEW_HOVER_ENTER,
+ noUse);
+ mA11yWindowManager.updateActiveAndAccessibilityFocusedWindowLocked(USER_SYSTEM_ID,
+ newFocusWindowId,
+ AccessibilityNodeInfo.ROOT_NODE_ID,
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ noUse);
+ assertThat(mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID), is(newFocusWindowId));
+ assertThat(mA11yWindowManager.getFocusedWindowId(
+ AccessibilityNodeInfo.FOCUS_ACCESSIBILITY), is(newFocusWindowId));
+
+ mA11yWindowManager.onTouchInteractionEnd();
+ mHandler.sendLastMessage();
+ verify(mockNewFocusConnection).clearAccessibilityFocus();
+ }
+
+ @Test
+ public void getPictureInPictureWindow_shouldNotNull() {
+ assertNull(mA11yWindowManager.getPictureInPictureWindowLocked());
+ mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1).inPictureInPicture = true;
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ assertNotNull(mA11yWindowManager.getPictureInPictureWindowLocked());
+ }
+
+ @Test
+ public void notifyOutsideTouch() throws RemoteException {
+ final int targetWindowId =
+ getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 1);
+ final int outsideWindowId =
+ getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
+ final IAccessibilityInteractionConnection mockRemoteConnection =
+ mA11yWindowManager.getConnectionLocked(
+ USER_SYSTEM_ID, outsideWindowId).getRemote();
+ mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0).hasFlagWatchOutsideTouch = true;
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
+ mA11yWindowManager.notifyOutsideTouch(USER_SYSTEM_ID, targetWindowId);
+ verify(mockRemoteConnection).notifyOutsideTouch();
+ }
+
+ @Test
+ public void addAccessibilityInteractionConnection_profileUser_findInParentUser()
+ throws RemoteException {
+ final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, USER_PROFILE);
+ final int windowId = mA11yWindowManager.findWindowIdLocked(
+ USER_PROFILE_PARENT, token.asBinder());
+ assertTrue(windowId >= 0);
+ }
+
+ @Test
+ public void getDisplayList() throws RemoteException {
+ // Starts tracking window of second display.
+ startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
+
+ final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked(
+ DISPLAY_TYPE_DEFAULT);
+ assertTrue(displayList.equals(mExpectedDisplayList));
+ }
+
+ @Test
+ public void setAccessibilityWindowIdToSurfaceMetadata()
+ throws RemoteException {
+ final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ true, USER_SYSTEM_ID);
+ int windowId = -1;
+ for (int i = 0; i < mA11yWindowTokens.size(); i++) {
+ if (mA11yWindowTokens.valueAt(i).equals(token)) {
+ windowId = mA11yWindowTokens.keyAt(i);
+ }
+ }
+ assertNotEquals("Returned token is not found in mA11yWindowTokens", -1, windowId);
+ verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata(
+ token.asBinder(), windowId);
+
+ mA11yWindowManager.removeAccessibilityInteractionConnection(token);
+ verify(mMockWindowManagerInternal, times(1)).setAccessibilityIdToSurfaceMetadata(
+ token.asBinder(), -1);
+ }
+
+ @Test
+ public void getHostTokenLocked_hierarchiesAreAssociated_shouldReturnHostToken() {
+ mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
+ final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
+ assertEquals(hostToken, mMockHostToken);
+ }
+
+ @Test
+ public void getHostTokenLocked_hierarchiesAreNotAssociated_shouldReturnNull() {
+ final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
+ assertNull(hostToken);
+ }
+
+ @Test
+ public void getHostTokenLocked_embeddedHierarchiesAreDisassociated_shouldReturnNull() {
+ mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
+ mA11yWindowManager.disassociateLocked(mMockEmbeddedToken);
+ final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockEmbeddedToken);
+ assertNull(hostToken);
+ }
+
+ @Test
+ public void getHostTokenLocked_hostHierarchiesAreDisassociated_shouldReturnNull() {
+ mA11yWindowManager.associateLocked(mMockEmbeddedToken, mMockHostToken);
+ mA11yWindowManager.disassociateLocked(mMockHostToken);
+ final IBinder hostToken = mA11yWindowManager.getHostTokenLocked(mMockHostToken);
+ assertNull(hostToken);
+ }
+
+ @Test
+ public void getWindowIdLocked_windowIsRegistered_shouldReturnWindowId() {
+ final int windowId = mA11yWindowManager.getWindowIdLocked(mMockHostToken);
+ assertEquals(windowId, HOST_WINDOW_ID);
+ }
+
+ @Test
+ public void getWindowIdLocked_windowIsNotRegistered_shouldReturnInvalidWindowId() {
+ final int windowId = mA11yWindowManager.getWindowIdLocked(mMockInvalidToken);
+ assertEquals(windowId, INVALID_ID);
+ }
+
+ @Test
+ public void getTokenLocked_windowIsRegistered_shouldReturnToken() {
+ final IBinder token = mA11yWindowManager.getLeashTokenLocked(HOST_WINDOW_ID);
+ assertEquals(token, mMockHostToken);
+ }
+
+ @Test
+ public void getTokenLocked_windowIsNotRegistered_shouldReturnNull() {
+ final IBinder token = mA11yWindowManager.getLeashTokenLocked(OTHER_WINDOW_ID);
+ assertNull(token);
+ }
+
+ @Test
+ public void setAccessibilityWindowAttributes_windowIsNotRegistered_titleIsChanged() {
+ final int windowId =
+ getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
+ final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+ layoutParams.accessibilityTitle = "accessibility window title";
+ final AccessibilityWindowAttributes attributes = new AccessibilityWindowAttributes(
+ layoutParams, new LocaleList());
+
+ mA11yWindowManager.setAccessibilityWindowAttributes(Display.DEFAULT_DISPLAY, windowId,
+ USER_SYSTEM_ID, attributes);
+
+ final AccessibilityWindowInfo a11yWindow = mA11yWindowManager.findA11yWindowInfoByIdLocked(
+ windowId);
+ assertEquals(toString(layoutParams.accessibilityTitle), toString(a11yWindow.getTitle()));
+ }
+
+ @Test
+ public void sendAccessibilityEventOnWindowRemoval() {
+ final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
+
+ // Removing index 0 because it's not focused, and avoids unnecessary layer change.
+ final int windowId =
+ getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
+ infos.remove(0);
+ for (WindowInfo info : infos) {
+ // Adjust layer number because it should start from 0.
+ info.layer--;
+ }
+
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+
+ final ArgumentCaptor<AccessibilityEvent> captor =
+ ArgumentCaptor.forClass(AccessibilityEvent.class);
+ verify(mMockA11yEventSender, times(1))
+ .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+ assertThat(captor.getAllValues().get(0),
+ allOf(displayId(Display.DEFAULT_DISPLAY),
+ a11yWindowId(windowId),
+ a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_REMOVED)));
+ }
+
+ @Test
+ public void sendAccessibilityEventOnWindowAddition() throws RemoteException {
+ final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
+
+ for (WindowInfo info : infos) {
+ // Adjust layer number because new window will have 0 so that layer number in
+ // A11yWindowInfo in window won't be changed.
+ info.layer++;
+ }
+
+ final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
+ false, USER_SYSTEM_ID);
+ addWindowInfo(infos, token, 0);
+ final int windowId =
+ getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, infos.size() - 1);
+
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+
+ final ArgumentCaptor<AccessibilityEvent> captor =
+ ArgumentCaptor.forClass(AccessibilityEvent.class);
+ verify(mMockA11yEventSender, times(1))
+ .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+ assertThat(captor.getAllValues().get(0),
+ allOf(displayId(Display.DEFAULT_DISPLAY),
+ a11yWindowId(windowId),
+ a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_ADDED)));
+ }
+
+ @Test
+ public void sendAccessibilityEventOnWindowChange() {
+ final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
+ infos.get(0).title = "new title";
+ final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
+
+ onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+
+ final ArgumentCaptor<AccessibilityEvent> captor =
+ ArgumentCaptor.forClass(AccessibilityEvent.class);
+ verify(mMockA11yEventSender, times(1))
+ .sendAccessibilityEventForCurrentUserLocked(captor.capture());
+ assertThat(captor.getAllValues().get(0),
+ allOf(displayId(Display.DEFAULT_DISPLAY),
+ a11yWindowId(windowId),
+ a11yWindowChanges(AccessibilityEvent.WINDOWS_CHANGE_TITLE)));
+ }
+
+ private void registerLeashedTokenAndWindowId() {
+ mA11yWindowManager.registerIdLocked(mMockHostToken, HOST_WINDOW_ID);
+ mA11yWindowManager.registerIdLocked(mMockEmbeddedToken, EMBEDDED_WINDOW_ID);
+ }
+
+ private void startTrackingPerDisplay(int displayId) throws RemoteException {
+ ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>();
+ // Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy
+ // mock window token into mA11yWindowTokens. Also, preparing WindowInfo mWindowInfos
+ // for the test.
+ int layer = 0;
+ for (int i = 0; i < NUM_GLOBAL_WINDOWS; i++) {
+ final IWindow token = addAccessibilityInteractionConnection(displayId,
+ true, USER_SYSTEM_ID);
+ addWindowInfo(windowInfosForDisplay, token, layer++);
+
+ }
+ for (int i = 0; i < NUM_APP_WINDOWS; i++) {
+ final IWindow token = addAccessibilityInteractionConnection(displayId,
+ false, USER_SYSTEM_ID);
+ addWindowInfo(windowInfosForDisplay, token, layer++);
+ }
+ // Sets up current focused window of display.
+ // Each display has its own current focused window if config_perDisplayFocusEnabled is true.
+ // Otherwise only default display needs to current focused window.
+ if (mSupportPerDisplayFocus || displayId == Display.DEFAULT_DISPLAY) {
+ windowInfosForDisplay.get(DEFAULT_FOCUSED_INDEX).focused = true;
+ }
+ // Turns on windows tracking, and update window info.
+ mA11yWindowManager.startTrackingWindows(displayId, false);
+ // Puts window lists into array.
+ mWindowInfos.put(displayId, windowInfosForDisplay);
+ // Sets the default display is the top focused display and
+ // its current focused window is the top focused window.
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ setTopFocusedWindowAndDisplay(displayId, DEFAULT_FOCUSED_INDEX);
+ }
+ // Invokes callback for sending window lists to A11y framework.
+ onWindowsForAccessibilityChanged(displayId, FORCE_SEND);
+
+ assertEquals(mA11yWindowManager.getWindowListLocked(displayId).size(),
+ windowInfosForDisplay.size());
+ }
+
+ private WindowsForAccessibilityCallback getWindowsForAccessibilityCallbacks(int displayId) {
+ ArgumentCaptor<WindowsForAccessibilityCallback> windowsForAccessibilityCallbacksCaptor =
+ ArgumentCaptor.forClass(
+ WindowsForAccessibilityCallback.class);
+ verify(mMockWindowManagerInternal)
+ .setWindowsForAccessibilityCallback(eq(displayId),
+ windowsForAccessibilityCallbacksCaptor.capture());
+ return windowsForAccessibilityCallbacksCaptor.getValue();
+ }
+
+ private IWindow addAccessibilityInteractionConnection(int displayId, boolean bGlobal,
+ int userId) throws RemoteException {
+ final IWindow mockWindowToken = Mockito.mock(IWindow.class);
+ final IAccessibilityInteractionConnection mockA11yConnection = Mockito.mock(
+ IAccessibilityInteractionConnection.class);
+ final IBinder mockConnectionBinder = Mockito.mock(IBinder.class);
+ final IBinder mockWindowBinder = Mockito.mock(IBinder.class);
+ final IBinder mockLeashToken = Mockito.mock(IBinder.class);
+ when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder);
+ when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder);
+ when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId))
+ .thenReturn(bGlobal);
+ when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowBinder))
+ .thenReturn(displayId);
+
+ int windowId = mA11yWindowManager.addAccessibilityInteractionConnection(
+ mockWindowToken, mockLeashToken, mockA11yConnection, PACKAGE_NAME, userId);
+ mA11yWindowTokens.put(windowId, mockWindowToken);
+ return mockWindowToken;
+ }
+
+ private int addAccessibilityInteractionConnection(int displayId, boolean bGlobal,
+ IBinder leashToken, int userId) throws RemoteException {
+ final IWindow mockWindowToken = Mockito.mock(IWindow.class);
+ final IAccessibilityInteractionConnection mockA11yConnection = Mockito.mock(
+ IAccessibilityInteractionConnection.class);
+ final IBinder mockConnectionBinder = Mockito.mock(IBinder.class);
+ final IBinder mockWindowBinder = Mockito.mock(IBinder.class);
+ when(mockA11yConnection.asBinder()).thenReturn(mockConnectionBinder);
+ when(mockWindowToken.asBinder()).thenReturn(mockWindowBinder);
+ when(mMockA11ySecurityPolicy.isCallerInteractingAcrossUsers(userId))
+ .thenReturn(bGlobal);
+ when(mMockWindowManagerInternal.getDisplayIdForWindow(mockWindowBinder))
+ .thenReturn(displayId);
+
+ int windowId = mA11yWindowManager.addAccessibilityInteractionConnection(
+ mockWindowToken, leashToken, mockA11yConnection, PACKAGE_NAME, userId);
+ mA11yWindowTokens.put(windowId, mockWindowToken);
+ return windowId;
+ }
+
+ private void addWindowInfo(ArrayList<WindowInfo> windowInfos, IWindow windowToken, int layer) {
+ final WindowInfo windowInfo = WindowInfo.obtain();
+ windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
+ windowInfo.token = windowToken.asBinder();
+ windowInfo.layer = layer;
+ windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+ windowInfos.add(windowInfo);
+ }
+
+ private int getWindowIdFromWindowInfosForDisplay(int displayId, int index) {
+ final IBinder windowToken = mWindowInfos.get(displayId).get(index).token;
+ return mA11yWindowManager.findWindowIdLocked(
+ USER_SYSTEM_ID, windowToken);
+ }
+
+ private void setTopFocusedWindowAndDisplay(int displayId, int index) {
+ // Sets the top focus window.
+ mTopFocusedWindowToken = mWindowInfos.get(displayId).get(index).token;
+ // Sets the top focused display.
+ mTopFocusedDisplayId = displayId;
+ }
+
+ private void onWindowsForAccessibilityChanged(int displayId, boolean forceSend) {
+ WindowsForAccessibilityCallback callbacks = mCallbackOfWindows.get(displayId);
+ if (callbacks == null) {
+ callbacks = getWindowsForAccessibilityCallbacks(displayId);
+ mCallbackOfWindows.put(displayId, callbacks);
+ }
+ callbacks.onWindowsForAccessibilityChanged(forceSend, mTopFocusedDisplayId,
+ mTopFocusedWindowToken, mWindowInfos.get(displayId));
+ }
+
+ private void changeFocusedWindowOnDisplayPerDisplayFocusConfig(
+ int changeFocusedDisplayId, int newFocusedWindowIndex, int oldTopFocusedDisplayId,
+ int oldFocusedWindowIndex) {
+ if (mSupportPerDisplayFocus) {
+ // Gets the old focused window of display which wants to change focused window.
+ WindowInfo focusedWindowInfo =
+ mWindowInfos.get(changeFocusedDisplayId).get(oldFocusedWindowIndex);
+ // Resets the focus of old focused window.
+ focusedWindowInfo.focused = false;
+ // Gets the new window of display which wants to change focused window.
+ focusedWindowInfo =
+ mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex);
+ // Sets the focus of new focused window.
+ focusedWindowInfo.focused = true;
+ } else {
+ // Gets the window of display which wants to change focused window.
+ WindowInfo focusedWindowInfo =
+ mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex);
+ // Sets the focus of new focused window.
+ focusedWindowInfo.focused = true;
+ // Gets the old focused window of old top focused display.
+ focusedWindowInfo =
+ mWindowInfos.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex);
+ // Resets the focus of old focused window.
+ focusedWindowInfo.focused = false;
+ // Changes the top focused display and window.
+ setTopFocusedWindowAndDisplay(changeFocusedDisplayId, newFocusedWindowIndex);
+ }
+ }
+
+ @Nullable
+ private static String toString(@Nullable CharSequence cs) {
+ return cs == null ? null : cs.toString();
+ }
+
+ static class DisplayIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ private final int mDisplayId;
+
+ DisplayIdMatcher(int displayId) {
+ super();
+ mDisplayId = displayId;
+ }
+
+ static DisplayIdMatcher displayId(int displayId) {
+ return new DisplayIdMatcher(displayId);
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityEvent event) {
+ return event.getDisplayId() == mDisplayId;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to displayId " + mDisplayId);
+ }
+ }
+
+ static class WindowIdMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ private int mWindowId;
+
+ WindowIdMatcher(int windowId) {
+ super();
+ mWindowId = windowId;
+ }
+
+ static WindowIdMatcher a11yWindowId(int windowId) {
+ return new WindowIdMatcher(windowId);
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityEvent event) {
+ return event.getWindowId() == mWindowId;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to windowId " + mWindowId);
+ }
+ }
+
+ static class WindowChangesMatcher extends TypeSafeMatcher<AccessibilityEvent> {
+ private int mWindowChanges;
+
+ WindowChangesMatcher(int windowChanges) {
+ super();
+ mWindowChanges = windowChanges;
+ }
+
+ static WindowChangesMatcher a11yWindowChanges(int windowChanges) {
+ return new WindowChangesMatcher(windowChanges);
+ }
+
+ @Override
+ protected boolean matchesSafely(AccessibilityEvent event) {
+ return event.getWindowChanges() == mWindowChanges;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Matching to window changes " + mWindowChanges);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
index 01159b1f901c..bcb4877f64c7 100644
--- a/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
@@ -32,7 +32,6 @@ import android.testing.AndroidTestingRunner;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.server.companion.PackageUtils;
import com.android.server.pm.pkg.AndroidPackage;
import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index a6e05ddc792c..55208972895d 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -364,6 +364,30 @@ public class HdmiCecLocalDeviceAudioSystemTest {
}
@Test
+ public void systemAudioControlOnPowerOn_singleActionStarted() throws Exception {
+ mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
+ mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
+ Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
+ mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn(
+ Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true);
+ assertThat(
+ mHdmiCecLocalDeviceAudioSystem.getActions(
+ SystemAudioInitiationActionFromAvr.class))
+ .hasSize(1);
+ }
+
+ @Test
+ public void onSystemAudioControlFeatureSupportChanged_singleActionStarted() throws Exception {
+ mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class);
+ mHdmiCecLocalDeviceAudioSystem.onSystemAudioControlFeatureSupportChanged(true);
+ mHdmiCecLocalDeviceAudioSystem.onSystemAudioControlFeatureSupportChanged(true);
+ assertThat(
+ mHdmiCecLocalDeviceAudioSystem.getActions(
+ SystemAudioInitiationActionFromAvr.class))
+ .hasSize(1);
+ }
+
+ @Test
public void handleActiveSource_updateActiveSource() throws Exception {
HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
ActiveSource expectedActiveSource = new ActiveSource(ADDR_TV, 0x0000);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index e59b5ea027ed..2ba3969bb9e5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -48,7 +48,6 @@ import android.os.UserManager;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.provider.Settings;
-import android.security.KeyStore;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -102,7 +101,6 @@ public abstract class BaseLockSettingsServiceTests {
IActivityManager mActivityManager;
DevicePolicyManager mDevicePolicyManager;
DevicePolicyManagerInternal mDevicePolicyManagerInternal;
- KeyStore mKeyStore;
MockSyntheticPasswordManager mSpManager;
IAuthSecret mAuthSecretService;
WindowManagerInternal mMockWindowManager;
@@ -165,7 +163,6 @@ public abstract class BaseLockSettingsServiceTests {
new LockSettingsServiceTestable.MockInjector(
mContext,
mStorage,
- mKeyStore,
mActivityManager,
setUpStorageManagerMock(),
mSpManager,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 296d2cba83dd..f9077c4ae602 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -30,7 +30,6 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.storage.IStorageManager;
-import android.security.KeyStore;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.service.gatekeeper.IGateKeeperService;
@@ -41,6 +40,7 @@ import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreMa
import com.android.server.pm.UserManagerInternal;
import java.io.FileNotFoundException;
+import java.security.KeyStore;
public class LockSettingsServiceTestable extends LockSettingsService {
private Intent mSavedFrpNotificationIntent = null;
@@ -50,7 +50,6 @@ public class LockSettingsServiceTestable extends LockSettingsService {
public static class MockInjector extends LockSettingsService.Injector {
private LockSettingsStorage mLockSettingsStorage;
- private KeyStore mKeyStore;
private IActivityManager mActivityManager;
private IStorageManager mStorageManager;
private SyntheticPasswordManager mSpManager;
@@ -62,14 +61,13 @@ public class LockSettingsServiceTestable extends LockSettingsService {
public boolean mIsHeadlessSystemUserMode = false;
public boolean mIsMainUserPermanentAdmin = false;
- public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
- IActivityManager activityManager,
- IStorageManager storageManager, SyntheticPasswordManager spManager,
- FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
+ public MockInjector(Context context, LockSettingsStorage storage,
+ IActivityManager activityManager, IStorageManager storageManager,
+ SyntheticPasswordManager spManager, FakeGsiService gsiService,
+ RecoverableKeyStoreManager recoverableKeyStoreManager,
UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
super(context);
mLockSettingsStorage = storage;
- mKeyStore = keyStore;
mActivityManager = activityManager;
mStorageManager = storageManager;
mSpManager = spManager;
@@ -110,11 +108,6 @@ public class LockSettingsServiceTestable extends LockSettingsService {
}
@Override
- public KeyStore getKeyStore() {
- return mKeyStore;
- }
-
- @Override
public IStorageManager getStorageManager() {
return mStorageManager;
}
@@ -145,8 +138,7 @@ public class LockSettingsServiceTestable extends LockSettingsService {
}
@Override
- public UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(
- java.security.KeyStore ks) {
+ public UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(KeyStore ks) {
return mock(UnifiedProfilePasswordCache.class);
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index a529382bfb26..15cd5115a49e 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -149,6 +149,7 @@ import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicy;
+import android.net.NetworkPolicyManager;
import android.net.NetworkStateSnapshot;
import android.net.NetworkTemplate;
import android.net.TelephonyNetworkSpecifier;
@@ -205,7 +206,6 @@ import libcore.io.Streams;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
@@ -1620,6 +1620,12 @@ public class NetworkPolicyManagerServiceTest {
verify(mActivityManagerInternal).notifyNetworkPolicyRulesUpdated(UID_A, procStateSeq);
}
+ private void callAndWaitOnUidGone(int uid) throws Exception {
+ // The disabled argument is used only for ephemeral apps and does not matter here.
+ mUidObserver.onUidGone(uid, false /* disabled */);
+ waitForUidEventHandlerIdle();
+ }
+
private void callAndWaitOnUidStateChanged(int uid, int procState, long procStateSeq)
throws Exception {
callAndWaitOnUidStateChanged(uid, procState, procStateSeq,
@@ -2144,14 +2150,12 @@ public class NetworkPolicyManagerServiceTest {
assertFalse(mService.isUidNetworkingBlocked(UID_E, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainEnabled() throws Exception {
verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnProcStateChange() throws Exception {
@@ -2181,7 +2185,6 @@ public class NetworkPolicyManagerServiceTest {
assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnAllowlistChange() throws Exception {
@@ -2220,7 +2223,6 @@ public class NetworkPolicyManagerServiceTest {
assertFalse(mService.isUidNetworkingBlocked(UID_B, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testBackgroundChainOnTempAllowlistChange() throws Exception {
@@ -2250,7 +2252,15 @@ public class NetworkPolicyManagerServiceTest {
assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
}
- @Ignore("Temporarily disabled until the feature is enabled")
+ private boolean isUidState(int uid, int procState, int procStateSeq, int capability) {
+ final NetworkPolicyManager.UidState uidState = mService.getUidStateForTest(uid);
+ if (uidState == null) {
+ return false;
+ }
+ return uidState.uid == uid && uidState.procStateSeq == procStateSeq
+ && uidState.procState == procState && uidState.capability == capability;
+ }
+
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersProcStateChanges() throws Exception {
@@ -2313,7 +2323,6 @@ public class NetworkPolicyManagerServiceTest {
waitForUidEventHandlerIdle();
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersStaleChanges() throws Exception {
@@ -2334,7 +2343,6 @@ public class NetworkPolicyManagerServiceTest {
waitForUidEventHandlerIdle();
}
- @Ignore("Temporarily disabled until the feature is enabled")
@Test
@RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
public void testUidObserverFiltersCapabilityChanges() throws Exception {
@@ -2371,6 +2379,31 @@ public class NetworkPolicyManagerServiceTest {
}
@Test
+ public void testUidStateChangeAfterUidGone() throws Exception {
+ final int testProcStateSeq = 51;
+ final int testProcState = PROCESS_STATE_IMPORTANT_FOREGROUND;
+
+ try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) {
+ // First callback for uid.
+ callOnUidStatechanged(UID_B, testProcState, testProcStateSeq, PROCESS_CAPABILITY_NONE);
+ assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED));
+ }
+ waitForUidEventHandlerIdle();
+ assertTrue(isUidState(UID_B, testProcState, testProcStateSeq, PROCESS_CAPABILITY_NONE));
+
+ callAndWaitOnUidGone(UID_B);
+ try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) {
+ // Even though the procState is the same, the uid had exited - so this should be
+ // processed as a fresh callback.
+ callOnUidStatechanged(UID_B, testProcState, testProcStateSeq + 1,
+ PROCESS_CAPABILITY_NONE);
+ assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED));
+ }
+ waitForUidEventHandlerIdle();
+ assertTrue(isUidState(UID_B, testProcState, testProcStateSeq + 1, PROCESS_CAPABILITY_NONE));
+ }
+
+ @Test
public void testLowPowerStandbyAllowlist() throws Exception {
// Chain background is also enabled but these procstates are important enough to be exempt.
callAndWaitOnUidStateChanged(UID_A, PROCESS_STATE_TOP, 0);
diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
index c8bef45af839..076d5caf5954 100644
--- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
@@ -312,10 +312,12 @@ public class AnrTimerTest {
}
/**
- * Verify the dump output.
+ * Verify the dump output. This only applies when native timers are supported.
*/
@Test
public void testDumpOutput() throws Exception {
+ if (!AnrTimer.nativeTimersSupported()) return;
+
String r1 = getDumpOutput();
assertThat(r1).doesNotContain("timer:");
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 8c50ef406ec6..8c2fd1013056 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -4420,13 +4420,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@EnableFlags(Flags.FLAG_MODES_API)
public void updateAutomaticZenRule_nullDeviceEffectsUpdate() {
// Adds a starting rule with empty zen policies and device effects
+ ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build();
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
- .setDeviceEffects(new ZenDeviceEffects.Builder().build())
+ .setDeviceEffects(zde)
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
// Sets Device Effects to null
@@ -4437,10 +4437,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// user modified, it can be updated.
mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
Process.SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- // When AZR's ZenDeviceEffects is null, the updated rule's device effects will be null.
- assertThat(rule.getDeviceEffects()).isNull();
+ // When AZR's ZenDeviceEffects is null, the updated rule's device effects are kept.
+ assertThat(rule.getDeviceEffects()).isEqualTo(zde);
}
@Test
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 417fbd06be66..0d5bf95d959d 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -67,6 +67,7 @@ public class VibratorControlServiceTest {
@Mock
private PackageManagerInternal mPackageManagerInternalMock;
+ private TestLooper mTestLooper;
private FakeVibratorController mFakeVibratorController;
private VibratorControlService mVibratorControlService;
private VibrationSettings mVibrationSettings;
@@ -74,6 +75,7 @@ public class VibratorControlServiceTest {
@Before
public void setUp() throws Exception {
+ mTestLooper = new TestLooper();
when(mPackageManagerInternalMock.getSystemUiServiceComponent())
.thenReturn(new ComponentName("", ""));
LocalServices.removeServiceForTest(PackageManagerInternal.class);
@@ -83,7 +85,7 @@ public class VibratorControlServiceTest {
mVibrationSettings = new VibrationSettings(
ApplicationProvider.getApplicationContext(), new Handler(testLooper.getLooper()));
- mFakeVibratorController = new FakeVibratorController();
+ mFakeVibratorController = new FakeVibratorController(mTestLooper.getLooper());
mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(),
mMockVibrationScaler, mVibrationSettings, mLock);
}
@@ -108,13 +110,13 @@ public class VibratorControlServiceTest {
@Test
public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest()
throws RemoteException {
- FakeVibratorController fakeController1 = new FakeVibratorController();
- FakeVibratorController fakeController2 = new FakeVibratorController();
- mVibratorControlService.registerVibratorController(fakeController1);
- mVibratorControlService.unregisterVibratorController(fakeController2);
+ FakeVibratorController controller1 = new FakeVibratorController(mTestLooper.getLooper());
+ FakeVibratorController controller2 = new FakeVibratorController(mTestLooper.getLooper());
+ mVibratorControlService.registerVibratorController(controller1);
+ mVibratorControlService.unregisterVibratorController(controller2);
verifyZeroInteractions(mMockVibrationScaler);
- assertThat(fakeController1.isLinkedToDeath).isTrue();
+ assertThat(controller1.isLinkedToDeath).isTrue();
}
@Test
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java
index 79abe21a301d..db823d649cb6 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java
@@ -18,23 +18,26 @@ package com.android.server.vibrator;
import static com.google.common.truth.Truth.assertThat;
-import android.os.RemoteException;
+import android.os.test.TestLooper;
import org.junit.Before;
import org.junit.Test;
public class VibratorControllerHolderTest {
- private final FakeVibratorController mFakeVibratorController = new FakeVibratorController();
+ private TestLooper mTestLooper;
+ private FakeVibratorController mFakeVibratorController;
private VibratorControllerHolder mVibratorControllerHolder;
@Before
public void setUp() throws Exception {
+ mTestLooper = new TestLooper();
+ mFakeVibratorController = new FakeVibratorController(mTestLooper.getLooper());
mVibratorControllerHolder = new VibratorControllerHolder();
}
@Test
- public void testSetVibratorController_linksVibratorControllerToDeath() throws RemoteException {
+ public void testSetVibratorController_linksVibratorControllerToDeath() {
mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
assertThat(mVibratorControllerHolder.getVibratorController())
.isEqualTo(mFakeVibratorController);
@@ -42,8 +45,7 @@ public class VibratorControllerHolderTest {
}
@Test
- public void testSetVibratorController_setControllerToNull_unlinksVibratorControllerToDeath()
- throws RemoteException {
+ public void testSetVibratorController_setControllerToNull_unlinksVibratorControllerToDeath() {
mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
mVibratorControllerHolder.setVibratorController(null);
assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
@@ -51,8 +53,7 @@ public class VibratorControllerHolderTest {
}
@Test
- public void testBinderDied_withValidController_unlinksVibratorControllerToDeath()
- throws RemoteException {
+ public void testBinderDied_withValidController_unlinksVibratorControllerToDeath() {
mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
mVibratorControllerHolder.binderDied(mFakeVibratorController);
assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
@@ -60,10 +61,10 @@ public class VibratorControllerHolderTest {
}
@Test
- public void testBinderDied_withInvalidController_ignoresRequest()
- throws RemoteException {
+ public void testBinderDied_withInvalidController_ignoresRequest() {
mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
- FakeVibratorController imposterVibratorController = new FakeVibratorController();
+ FakeVibratorController imposterVibratorController =
+ new FakeVibratorController(mTestLooper.getLooper());
mVibratorControllerHolder.binderDied(imposterVibratorController);
assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
assertThat(mVibratorControllerHolder.getVibratorController())
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index ed89ccf07453..d2ad61f2ba9f 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -97,7 +97,6 @@ import android.view.InputDevice;
import android.view.flags.Flags;
import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.FrameworkStatsLog;
@@ -117,6 +116,7 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
@@ -128,9 +128,7 @@ import java.util.function.Predicate;
public class VibratorManagerServiceTest {
private static final int TEST_TIMEOUT_MILLIS = 1_000;
- // Time to allow for a cancellation to complete (notably including system ramp down), but not so
- // long that tests easily get really slow or flaky. If a vibration is close to this, it should
- // be cancelled in the body of the individual test.
+ // Time to allow for a cancellation to complete and the vibrators to become idle.
private static final int CLEANUP_TIMEOUT_MILLIS = 100;
private static final int UID = Process.ROOT_UID;
private static final int VIRTUAL_DEVICE_ID = 1;
@@ -186,8 +184,8 @@ public class VibratorManagerServiceTest {
private AudioManager mAudioManagerMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
-
- private SparseArray<VibrationEffect> mHapticFeedbackVibrationMap = new SparseArray<>();
+ private final SparseArray<VibrationEffect> mHapticFeedbackVibrationMap = new SparseArray<>();
+ private final List<HalVibration> mPendingVibrations = new ArrayList<>();
private VibratorManagerService mService;
private Context mContextSpy;
@@ -207,7 +205,7 @@ public class VibratorManagerServiceTest {
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
mVibrationConfig = new VibrationConfig(mContextSpy.getResources());
- mFakeVibratorController = new FakeVibratorController();
+ mFakeVibratorController = new FakeVibratorController(mTestLooper.getLooper());
ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
@@ -253,12 +251,15 @@ public class VibratorManagerServiceTest {
@After
public void tearDown() throws Exception {
if (mService != null) {
- // Wait until all vibrators have stopped vibrating, with a bit of flexibility for tests
- // that just do a click or have cancelled at the end (waiting for ramp-down).
- //
- // Note: if a test is flaky here, check whether a VibrationEffect duration is close to
- // CLEANUP_TIMEOUT_MILLIS - in which case it's probably best to just cancel that effect
- // explicitly at the end of the test case (rather than letting it run and race flakily).
+ if (!mPendingVibrations.stream().allMatch(HalVibration::hasEnded)) {
+ // Cancel any pending vibration from tests.
+ cancelVibrate(mService);
+ for (HalVibration vibration : mPendingVibrations) {
+ vibration.waitForEnd();
+ }
+ }
+ // Wait until all vibrators have stopped vibrating, waiting for ramp-down.
+ // Note: if a test is flaky here something is wrong with the vibration finalization.
assertTrue(waitUntil(s -> {
for (int vibratorId : mService.getVibratorIds()) {
if (s.isVibrating(vibratorId)) {
@@ -266,7 +267,7 @@ public class VibratorManagerServiceTest {
}
}
return true;
- }, mService, CLEANUP_TIMEOUT_MILLIS));
+ }, mService, mVibrationConfig.getRampDownDurationMs() + CLEANUP_TIMEOUT_MILLIS));
}
LocalServices.removeServiceForTest(PackageManagerInternal.class);
@@ -323,21 +324,26 @@ public class VibratorManagerServiceTest {
(VibratorManagerService.ExternalVibratorService) service;
} else if (service instanceof VibratorControlService) {
mVibratorControlService = (VibratorControlService) service;
+ mFakeVibratorController.setVibratorControlService(
+ mVibratorControlService);
}
}
+ @Override
HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
Resources resources, VibratorInfo vibratorInfo) {
return new HapticFeedbackVibrationProvider(
resources, vibratorInfo, mHapticFeedbackVibrationMap);
}
+ @Override
VibratorControllerHolder createVibratorControllerHolder() {
VibratorControllerHolder holder = new VibratorControllerHolder();
holder.setVibratorController(mFakeVibratorController);
return holder;
}
+ @Override
boolean isServiceDeclared(String name) {
return true;
}
@@ -486,8 +492,9 @@ public class VibratorManagerServiceTest {
InOrder inOrderVerifier = inOrder(listenerMock);
// First notification done when listener is registered.
inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+ // Vibrator on notification done before vibration ended, no need to wait.
inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
- // The last notification is after the vibration has completed.
+ // Vibrator off notification done after vibration completed, wait for notification.
inOrderVerifier.verify(listenerMock, timeout(TEST_TIMEOUT_MILLIS)).onVibrating(eq(false));
inOrderVerifier.verifyNoMoreInteractions();
@@ -517,6 +524,7 @@ public class VibratorManagerServiceTest {
InOrder inOrderVerifier = inOrder(listenerMock);
// First notification done when listener is registered.
inOrderVerifier.verify(listenerMock).onVibrating(eq(false));
+ // Vibrator on notification done before vibration ended, no need to wait.
inOrderVerifier.verify(listenerMock).onVibrating(eq(true));
inOrderVerifier.verify(listenerMock, atLeastOnce()).asBinder(); // unregister
inOrderVerifier.verifyNoMoreInteractions();
@@ -541,7 +549,6 @@ public class VibratorManagerServiceTest {
verify(listeners[0]).onVibrating(eq(true));
verify(listeners[1]).onVibrating(eq(true));
verify(listeners[2], never()).onVibrating(eq(true));
- cancelVibrate(service); // Clean up long-ish effect.
}
@Test
@@ -889,21 +896,24 @@ public class VibratorManagerServiceTest {
eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString());
}
- @FlakyTest
@Test
public void vibrate_withOngoingRepeatingVibration_ignoresEffect() throws Exception {
mockVibrators(1);
- mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createSystemReadyService();
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
- new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
- vibrate(service, repeatingEffect, new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_UNKNOWN).build());
+ new long[]{10, 10_000}, new int[]{128, 255}, 1);
+ vibrate(service, repeatingEffect,
+ new VibrationAttributes.Builder()
+ .setUsage(VibrationAttributes.USAGE_UNKNOWN)
+ .build());
- // VibrationThread will start this vibration async, wait until it has started.
- assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
- service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
HAPTIC_FEEDBACK_ATTRS);
@@ -914,9 +924,8 @@ public class VibratorManagerServiceTest {
// The second vibration shouldn't have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
// No segment played is the prebaked CLICK from the second vibration.
- assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+ assertFalse(fakeVibrator.getAllEffectSegments().stream()
.anyMatch(PrebakedSegment.class::isInstance));
- cancelVibrate(service); // Clean up repeating effect.
}
@Test
@@ -930,11 +939,13 @@ public class VibratorManagerServiceTest {
VibratorManagerService service = createSystemReadyService();
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
- new long[]{10, 10_000}, new int[]{255, 0}, 1);
+ new long[]{10, 10_000}, new int[]{128, 255}, 1);
vibrate(service, repeatingEffect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, wait until the off waveform step.
- assertTrue(waitUntil(s -> fakeVibrator.getOffCount() > 0, service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
// Cancel vibration right before requesting a new one.
// This should trigger slow IVibrator.off before setting the vibration status to cancelled.
@@ -942,6 +953,8 @@ public class VibratorManagerServiceTest {
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
ALARM_ATTRS);
+ // The second vibration should have recorded that the vibrators were turned on.
+ verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
// Check that second vibration was played.
assertTrue(fakeVibrator.getAllEffectSegments().stream()
.anyMatch(PrebakedSegment.class::isInstance));
@@ -951,45 +964,47 @@ public class VibratorManagerServiceTest {
public void vibrate_withNewSameImportanceVibrationAndBothRepeating_cancelsOngoingEffect()
throws Exception {
mockVibrators(1);
- mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createSystemReadyService();
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
- new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+ new long[]{10, 10_000}, new int[]{128, 255}, 1);
vibrate(service, repeatingEffect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, wait until it has started.
- assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
- service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
VibrationEffect repeatingEffect2 = VibrationEffect.createWaveform(
- new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+ new long[]{10, 10_000}, new int[]{255, 128}, 1);
vibrate(service, repeatingEffect2, ALARM_ATTRS);
- // VibrationThread will start this vibration async, so wait before checking it started.
- assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() == 2,
- service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 4, service,
+ TEST_TIMEOUT_MILLIS));
// The second vibration should have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
-
- cancelVibrate(service); // Clean up repeating effect.
}
- @FlakyTest
@Test
public void vibrate_withNewSameImportanceVibrationButOngoingIsRepeating_ignoreNewVibration()
throws Exception {
mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createSystemReadyService();
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
- new long[]{10, 10_000}, new int[]{255, 0}, 1);
+ new long[]{10, 10_000}, new int[]{128, 255}, 1);
vibrate(service, repeatingEffect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, wait until it has started.
- assertTrue(waitUntil(s -> !fakeVibrator.getAllEffectSegments().isEmpty(), service,
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2, service,
TEST_TIMEOUT_MILLIS));
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
@@ -998,53 +1013,55 @@ public class VibratorManagerServiceTest {
// The second vibration shouldn't have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
// The second vibration shouldn't have played any prebaked segment.
- assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+ assertFalse(fakeVibrator.getAllEffectSegments().stream()
.anyMatch(PrebakedSegment.class::isInstance));
- cancelVibrate(service); // Clean up long effect.
}
@Test
public void vibrate_withNewUnknownUsageVibrationAndRepeating_cancelsOngoingEffect()
throws Exception {
mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createSystemReadyService();
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
- new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+ new long[]{10, 10_000}, new int[]{128, 255}, 1);
vibrate(service, repeatingEffect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, wait until it has started.
- assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
- service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
VibrationEffect repeatingEffect2 = VibrationEffect.createWaveform(
- new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+ new long[]{10, 10_000}, new int[]{255, 128}, 1);
vibrate(service, repeatingEffect2, UNKNOWN_ATTRS);
- // VibrationThread will start this vibration async, so wait before checking it started.
- assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() == 2,
- service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 4, service,
+ TEST_TIMEOUT_MILLIS));
// The second vibration should have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
-
- cancelVibrate(service); // Clean up repeating effect.
}
- @FlakyTest
@Test
public void vibrate_withNewUnknownUsageVibrationAndNotRepeating_ignoreNewVibration()
throws Exception {
mockVibrators(1);
FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createSystemReadyService();
VibrationEffect alarmEffect = VibrationEffect.createWaveform(
- new long[]{10, 10_000}, new int[]{255, 0}, -1);
+ new long[]{10, 10_000}, new int[]{128, 255}, -1);
vibrate(service, alarmEffect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, wait until it has started.
- assertTrue(waitUntil(s -> !fakeVibrator.getAllEffectSegments().isEmpty(), service,
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2, service,
TEST_TIMEOUT_MILLIS));
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
@@ -1053,23 +1070,24 @@ public class VibratorManagerServiceTest {
// The second vibration shouldn't have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
// The second vibration shouldn't have played any prebaked segment.
- assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+ assertFalse(fakeVibrator.getAllEffectSegments().stream()
.anyMatch(PrebakedSegment.class::isInstance));
- cancelVibrate(service); // Clean up long effect.
}
@Test
public void vibrate_withOngoingHigherImportanceVibration_ignoresEffect() throws Exception {
mockVibrators(1);
- mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createSystemReadyService();
VibrationEffect effect = VibrationEffect.createWaveform(
- new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
+ new long[]{10, 10_000}, new int[]{128, 255}, -1);
vibrate(service, effect, ALARM_ATTRS);
- // VibrationThread will start this vibration async, so wait before checking it started.
- assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2,
service, TEST_TIMEOUT_MILLIS));
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
@@ -1078,26 +1096,27 @@ public class VibratorManagerServiceTest {
// The second vibration shouldn't have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
// The second vibration shouldn't have played any prebaked segment.
- assertFalse(mVibratorProviders.get(1).getAllEffectSegments().stream()
+ assertFalse(fakeVibrator.getAllEffectSegments().stream()
.anyMatch(PrebakedSegment.class::isInstance));
- cancelVibrate(service); // Clean up long effect.
}
@Test
public void vibrate_withOngoingLowerImportanceVibration_cancelsOngoingEffect()
throws Exception {
mockVibrators(1);
- mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibratorManagerService service = createSystemReadyService();
VibrationEffect effect = VibrationEffect.createWaveform(
- new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
+ new long[]{10, 10_000}, new int[]{128, 255}, -1);
vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
- // VibrationThread will start this vibration async, so wait before checking it started.
- assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
- service, TEST_TIMEOUT_MILLIS));
+ // VibrationThread will start this vibration async.
+ // Wait until second step started to ensure the noteVibratorOn was triggered.
+ assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2, service,
+ TEST_TIMEOUT_MILLIS));
vibrateAndWaitUntilFinished(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
RINGTONE_ATTRS);
@@ -1105,10 +1124,8 @@ public class VibratorManagerServiceTest {
// The second vibration should have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
// One segment played is the prebaked CLICK from the second vibration.
- assertEquals(1,
- mVibratorProviders.get(1).getAllEffectSegments().stream()
- .filter(PrebakedSegment.class::isInstance)
- .count());
+ assertEquals(1, fakeVibrator.getAllEffectSegments().stream()
+ .filter(PrebakedSegment.class::isInstance).count());
}
@Test
@@ -1139,10 +1156,8 @@ public class VibratorManagerServiceTest {
// The new vibration should have recorded that the vibrators were turned on.
verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
// One segment played is the prebaked CLICK from the new vibration.
- assertEquals(1,
- mVibratorProviders.get(1).getAllEffectSegments().stream()
- .filter(PrebakedSegment.class::isInstance)
- .count());
+ assertEquals(1, mVibratorProviders.get(1).getAllEffectSegments().stream()
+ .filter(PrebakedSegment.class::isInstance).count());
}
@Test
@@ -1153,8 +1168,9 @@ public class VibratorManagerServiceTest {
.build();
mockVibrators(1);
- mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
- mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
VibratorManagerService service = createSystemReadyService();
VibrationEffect effect = VibrationEffect.createWaveform(
@@ -1173,7 +1189,7 @@ public class VibratorManagerServiceTest {
verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
// One step segment (with several amplitudes) and one click should have played. Notably
// there is no primitive segment.
- List<VibrationEffectSegment> played = mVibratorProviders.get(1).getAllEffectSegments();
+ List<VibrationEffectSegment> played = fakeVibrator.getAllEffectSegments();
assertEquals(2, played.size());
assertEquals(1, played.stream().filter(StepSegment.class::isInstance).count());
assertEquals(1, played.stream().filter(PrebakedSegment.class::isInstance).count());
@@ -1182,8 +1198,9 @@ public class VibratorManagerServiceTest {
@Test
public void vibrate_withInputDevices_vibratesInputDevices() throws Exception {
mockVibrators(1);
- mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
- mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1});
when(mIInputManagerMock.getVibratorIds(eq(1))).thenReturn(new int[]{1});
when(mIInputManagerMock.getInputDevice(eq(1))).thenReturn(createInputDeviceWithVibrator(1));
@@ -1198,7 +1215,7 @@ public class VibratorManagerServiceTest {
vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
- assertTrue(mVibratorProviders.get(1).getAllEffectSegments().isEmpty());
+ assertTrue(fakeVibrator.getAllEffectSegments().isEmpty());
}
@Test
@@ -1497,8 +1514,6 @@ public class VibratorManagerServiceTest {
// Alarm vibration will be scaled with SCALE_NONE.
assertEquals(1f,
((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(2)).getScale(), 1e-5);
-
- cancelVibrate(service); // Clean up long-ish effect.
}
@Test
@@ -1533,8 +1548,6 @@ public class VibratorManagerServiceTest {
assertEquals(1, fakeVibrator.getAllEffectSegments().size());
assertEquals(defaultAmplitude / 255f, fakeVibrator.getAmplitudes().get(0), 1e-5);
-
- cancelVibrate(service); // Clean up long-ish effect.
}
@Test
@@ -1570,7 +1583,6 @@ public class VibratorManagerServiceTest {
// Vibration is not stopped nearly after updating service.
assertFalse(waitUntil(s -> !s.isVibrating(1), service, 50));
- cancelVibrate(service); // Clean up long effect.
}
@Test
@@ -1596,8 +1608,6 @@ public class VibratorManagerServiceTest {
HAPTIC_FEEDBACK_ATTRS);
// Haptic feedback played normally when it's from the default device.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
-
- cancelVibrate(service); // Clean up long-ish effect.
}
@Test
@@ -1854,9 +1864,7 @@ public class VibratorManagerServiceTest {
assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is not cancelled.
- assertFalse(waitUntil(s -> !s.isVibrating(1), service, CLEANUP_TIMEOUT_MILLIS));
assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
- cancelVibrate(service); // Clean up long effect.
}
@Test
@@ -1910,9 +1918,7 @@ public class VibratorManagerServiceTest {
assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is not cancelled.
- assertFalse(waitUntil(s -> !s.isVibrating(1), service, CLEANUP_TIMEOUT_MILLIS));
assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
- cancelVibrate(service); // Clean up long effect.
}
@Test
@@ -2536,29 +2542,29 @@ public class VibratorManagerServiceTest {
private HalVibration vibrateAndWaitUntilFinished(VibratorManagerService service,
CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
- HalVibration vib =
- service.vibrateWithPermissionCheck(UID, Context.DEVICE_ID_DEFAULT, PACKAGE_NAME,
- effect, attrs, "some reason", service);
+ HalVibration vib = vibrate(service, effect, attrs);
if (vib != null) {
vib.waitForEnd();
}
-
return vib;
}
- private void vibrate(VibratorManagerService service, VibrationEffect effect,
+ private HalVibration vibrate(VibratorManagerService service, VibrationEffect effect,
VibrationAttributes attrs) {
- vibrate(service, CombinedVibration.createParallel(effect), attrs);
+ return vibrate(service, CombinedVibration.createParallel(effect), attrs);
}
- private void vibrate(VibratorManagerService service, CombinedVibration effect,
+ private HalVibration vibrate(VibratorManagerService service, CombinedVibration effect,
VibrationAttributes attrs) {
- vibrateWithDevice(service, Context.DEVICE_ID_DEFAULT, effect, attrs);
+ return vibrateWithDevice(service, Context.DEVICE_ID_DEFAULT, effect, attrs);
}
- private void vibrateWithDevice(VibratorManagerService service, int deviceId,
+ private HalVibration vibrateWithDevice(VibratorManagerService service, int deviceId,
CombinedVibration effect, VibrationAttributes attrs) {
- service.vibrate(UID, deviceId, PACKAGE_NAME, effect, attrs, "some reason", service);
+ HalVibration vib = service.vibrateWithPermissionCheck(UID, deviceId, PACKAGE_NAME, effect,
+ attrs, "some reason", service);
+ mPendingVibrations.add(vib);
+ return vib;
}
private boolean waitUntil(Predicate<VibratorManagerService> predicate,
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
index 391220669f2f..2a010f0a82a9 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
@@ -18,7 +18,10 @@ package com.android.server.vibrator;
import android.annotation.NonNull;
import android.frameworks.vibrator.IVibratorController;
+import android.frameworks.vibrator.VibrationParam;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.VibrationAttributes;
@@ -28,17 +31,42 @@ import android.os.VibrationAttributes;
*/
public final class FakeVibratorController extends IVibratorController.Stub {
+ private final Handler mHandler;
+ private VibratorControlService mVibratorControlService;
+ private VibrationParam[] mRequestResult = new VibrationParam[0];
+
public boolean isLinkedToDeath = false;
public boolean didRequestVibrationParams = false;
public int requestVibrationType = VibrationAttributes.USAGE_UNKNOWN;
public long requestTimeoutInMillis = 0;
+ public FakeVibratorController(Looper looper) {
+ mHandler = new Handler(looper);
+ }
+
+ public void setVibratorControlService(VibratorControlService service) {
+ mVibratorControlService = service;
+ }
+
+ public void setRequestResult(VibrationParam... params) {
+ mRequestResult = params;
+ }
+
@Override
- public void requestVibrationParams(int vibrationType, long timeoutInMillis, IBinder iBinder)
+ public void requestVibrationParams(int vibrationType, long timeoutInMillis, IBinder token)
throws RemoteException {
didRequestVibrationParams = true;
requestVibrationType = vibrationType;
requestTimeoutInMillis = timeoutInMillis;
+ mHandler.post(() -> {
+ if (mVibratorControlService != null) {
+ try {
+ mVibratorControlService.onRequestVibrationParamsComplete(token, mRequestResult);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index e904eae00802..0a29dfbd7db7 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -145,7 +145,7 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase {
KeyboardLogEvent.SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE, 0},
{"Meta + Ctrl + DPAD_UP -> Split screen navigation",
new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
- KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION, KeyEvent.KEYCODE_DPAD_UP,
+ KeyboardLogEvent.MULTI_WINDOW_NAVIGATION, KeyEvent.KEYCODE_DPAD_UP,
META_ON | CTRL_ON},
{"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
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 6132ee3c89c4..173a1b693d87 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -74,7 +74,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -86,9 +85,9 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import android.app.ActivityOptions;
+import android.app.ActivityOptions.BackgroundActivityStartMode;
import android.app.AppOpsManager;
import android.app.BackgroundStartPrivileges;
-import android.app.ComponentOptions.BackgroundActivityStartMode;
import android.app.IApplicationThread;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index ef36bff91a67..4060d40865d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static com.android.server.wm.DesktopModeLaunchParamsModifier.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
@@ -31,11 +32,14 @@ import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import com.android.server.wm.LaunchParamsController.LaunchParamsModifier.Result;
+import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -70,11 +74,19 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
}
@Test
+ @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ public void testReturnsContinueIfDesktopWindowingIsDisabled() {
+ assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(null).calculate());
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testReturnsSkipIfTaskIsNull() {
assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate());
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testReturnsSkipIfNotBoundsPhase() {
final Task task = new TaskBuilder(mSupervisor).build();
assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(task).setPhase(
@@ -82,6 +94,7 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testReturnsSkipIfTaskNotUsingActivityTypeStandardOrUndefined() {
final Task task = new TaskBuilder(mSupervisor).setActivityType(
ACTIVITY_TYPE_ASSISTANT).build();
@@ -89,6 +102,7 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testReturnsDoneIfTaskUsingActivityTypeStandard() {
final Task task = new TaskBuilder(mSupervisor).setActivityType(
ACTIVITY_TYPE_STANDARD).build();
@@ -96,6 +110,7 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testReturnsDoneIfTaskUsingActivityTypeUndefined() {
final Task task = new TaskBuilder(mSupervisor).setActivityType(
ACTIVITY_TYPE_UNDEFINED).build();
@@ -103,6 +118,7 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testReturnsSkipIfCurrentParamsHasBounds() {
final Task task = new TaskBuilder(mSupervisor).setActivityType(
ACTIVITY_TYPE_STANDARD).build();
@@ -111,6 +127,7 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testUsesDefaultBounds() {
final Task task = new TaskBuilder(mSupervisor).setActivityType(
ACTIVITY_TYPE_STANDARD).build();
@@ -125,6 +142,7 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testUsesDisplayAreaAndWindowingModeFromSource() {
final Task task = new TaskBuilder(mSupervisor).setActivityType(
ACTIVITY_TYPE_STANDARD).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 4c32a586ae78..a0461a6ea9a4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -42,6 +42,7 @@ import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -541,7 +542,7 @@ public class DragDropControllerTests extends WindowTestsBase {
}
@Test
- public void testUnhandledDragListenerNotCalledForNormalDrags() throws RemoteException {
+ public void testUnhandledDragNotCalledForNormalDrags() throws RemoteException {
assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
final IGlobalDragListener listener = mock(IGlobalDragListener.class);
@@ -552,15 +553,16 @@ public class DragDropControllerTests extends WindowTestsBase {
}
@Test
- public void testUnhandledDragListenerReceivesUnhandledDropOverWindow() {
+ public void testUnhandledDragReceivesUnhandledDropOverWindow() {
assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
final IGlobalDragListener listener = mock(IGlobalDragListener.class);
doReturn(mock(Binder.class)).when(listener).asBinder();
mTarget.setGlobalDragListener(listener);
final int invalidXY = 100_000;
- startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
- // Notify the unhandled drag listener
+ startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
+ ClipData.newPlainText("label", "Test"), () -> {
+ // Trigger an unhandled drop and verify the global drag listener was called
mTarget.reportDropWindow(mWindow.mInputChannelToken, invalidXY, invalidXY);
mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
mTarget.reportDropResult(mWindow.mClient, false);
@@ -575,15 +577,16 @@ public class DragDropControllerTests extends WindowTestsBase {
}
@Test
- public void testUnhandledDragListenerReceivesUnhandledDropOverNoValidWindow() {
+ public void testUnhandledDragReceivesUnhandledDropOverNoValidWindow() {
assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
final IGlobalDragListener listener = mock(IGlobalDragListener.class);
doReturn(mock(Binder.class)).when(listener).asBinder();
mTarget.setGlobalDragListener(listener);
final int invalidXY = 100_000;
- startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
- // Notify the unhandled drag listener
+ startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
+ ClipData.newPlainText("label", "Test"), () -> {
+ // Trigger an unhandled drop and verify the global drag listener was called
mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
mTarget.onUnhandledDropCallback(true);
@@ -597,15 +600,38 @@ public class DragDropControllerTests extends WindowTestsBase {
}
@Test
- public void testUnhandledDragListenerCallbackTimeout() {
+ public void testUnhandledDragDoesNotReceiveUnhandledDropWithoutDragFlag() {
assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
final IGlobalDragListener listener = mock(IGlobalDragListener.class);
doReturn(mock(Binder.class)).when(listener).asBinder();
mTarget.setGlobalDragListener(listener);
final int invalidXY = 100_000;
- startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
- // Notify the unhandled drag listener
+ startDrag(View.DRAG_FLAG_GLOBAL,
+ ClipData.newPlainText("label", "Test"), () -> {
+ // Trigger an unhandled drop and verify the global drag listener was not called
+ mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
+ mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+ mToken = null;
+ try {
+ verify(listener, never()).onUnhandledDrop(any(), any());
+ } catch (RemoteException e) {
+ fail("Failed to verify unhandled drop: " + e);
+ }
+ });
+ }
+
+ @Test
+ public void testUnhandledDragCallbackTimeout() {
+ assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+ final IGlobalDragListener listener = mock(IGlobalDragListener.class);
+ doReturn(mock(Binder.class)).when(listener).asBinder();
+ mTarget.setGlobalDragListener(listener);
+ final int invalidXY = 100_000;
+ startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG,
+ ClipData.newPlainText("label", "Test"), () -> {
+ // Trigger an unhandled drop and verify the global drag listener was called
mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index 530a39e8b53e..0f2c62d3d09b 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -53,11 +53,14 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.usb.UsbServiceDumpProto;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
@@ -147,6 +150,7 @@ public class UsbService extends IUsbManager.Stub {
private final UsbSettingsManager mSettingsManager;
private final UsbPermissionManager mPermissionManager;
+ static final int PACKAGE_MONITOR_OPERATION_ID = 1;
/**
* The user id of the current user. There might be several profiles (with separate user ids)
* per user.
@@ -156,6 +160,10 @@ public class UsbService extends IUsbManager.Stub {
private final Object mLock = new Object();
+ // Key: USB port id
+ // Value: A set of UIDs of requesters who request disabling usb data
+ private final ArrayMap<String, ArraySet<Integer>> mUsbDisableRequesters = new ArrayMap<>();
+
/**
* @return the {@link UsbUserSettingsManager} for the given userId
*/
@@ -261,6 +269,10 @@ public class UsbService extends IUsbManager.Stub {
if (mDeviceManager != null) {
mDeviceManager.bootCompleted();
}
+ if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) {
+ new PackageUninstallMonitor()
+ .register(mContext, UserHandle.ALL, BackgroundThread.getHandler());
+ }
}
/** Called when a user is unlocked. */
@@ -873,6 +885,11 @@ public class UsbService extends IUsbManager.Stub {
Objects.requireNonNull(callback, "enableUsbData: 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, enable, Binder.getCallingUid())) return false;
+ }
+
final long ident = Binder.clearCallingIdentity();
boolean wait;
try {
@@ -892,6 +909,31 @@ public class UsbService extends IUsbManager.Stub {
return wait;
}
+ /**
+ * If enable = true, exclude UID from update list.
+ * If enable = false, include UID in update list.
+ * Return false if enable = true and the list is empty (no updates).
+ * Return true otherwise (let downstream decide on updates).
+ */
+ private boolean shouldUpdateUsbSignaling(String portId, boolean enable, int uid) {
+ synchronized (mUsbDisableRequesters) {
+ if (!mUsbDisableRequesters.containsKey(portId)) {
+ mUsbDisableRequesters.put(portId, new ArraySet<>());
+ }
+
+ ArraySet<Integer> uidsOfDisableRequesters = mUsbDisableRequesters.get(portId);
+
+ if (enable) {
+ uidsOfDisableRequesters.remove(uid);
+ // re-enable USB port (return true) if there are no other disable requesters
+ return uidsOfDisableRequesters.isEmpty();
+ } else {
+ uidsOfDisableRequesters.add(uid);
+ }
+ }
+ return true;
+ }
+
@Override
public void enableUsbDataWhileDocked(String portId, int operationId,
IUsbOperationInternal callback) {
@@ -1344,4 +1386,26 @@ public class UsbService extends IUsbManager.Stub {
private static String removeLastChar(String value) {
return value.substring(0, value.length() - 1);
}
+
+ /**
+ * Upon app removal, clear associated UIDs from the mUsbDisableRequesters list
+ * and re-enable USB data signaling if no remaining apps require USB disabling.
+ */
+ private class PackageUninstallMonitor extends PackageMonitor {
+ @Override
+ public void onUidRemoved(int uid) {
+ synchronized (mUsbDisableRequesters) {
+ for (String portId : mUsbDisableRequesters.keySet()) {
+ ArraySet<Integer> disabledUid = mUsbDisableRequesters.get(portId);
+ if (disabledUid != null) {
+ disabledUid.remove(uid);
+ if (disabledUid.isEmpty()) {
+ enableUsbData(portId, true, PACKAGE_MONITOR_OPERATION_ID,
+ new IUsbOperationInternal.Default());
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index c217780d90d6..aef7158fd613 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -16,15 +16,21 @@
package com.android.server.voiceinteraction;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
import android.app.AppGlobals;
+import android.app.admin.DevicePolicyManagerInternal;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.content.ComponentName;
@@ -41,6 +47,7 @@ import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.graphics.Bitmap;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.KeyphraseMetadata;
import android.hardware.soundtrigger.ModelParams;
@@ -84,6 +91,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import android.window.ScreenCapture;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -110,7 +118,9 @@ import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.android.server.policy.AppOpsPolicy;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.server.wm.ActivityAssistInfo;
import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -127,6 +137,19 @@ public class VoiceInteractionManagerService extends SystemService {
static final String TAG = "VoiceInteractionManager";
static final boolean DEBUG = false;
+ /** Static constants used by Contextual Search helper. */
+ private static final String CS_KEY_FLAG_SECURE_FOUND =
+ "com.android.contextualsearch.flag_secure_found";
+ private static final String CS_KEY_FLAG_SCREENSHOT =
+ "com.android.contextualsearch.screenshot";
+ private static final String CS_KEY_FLAG_IS_MANAGED_PROFILE_VISIBLE =
+ "com.android.contextualsearch.is_managed_profile_visible";
+ private static final String CS_KEY_FLAG_VISIBLE_PACKAGE_NAMES =
+ "com.android.contextualsearch.visible_package_names";
+ private static final String CS_INTENT_FILTER =
+ "com.android.contextualsearch.LAUNCH";
+
+
final Context mContext;
final ContentResolver mResolver;
// Can be overridden for testing purposes
@@ -135,6 +158,8 @@ public class VoiceInteractionManagerService extends SystemService {
final ActivityManagerInternal mAmInternal;
final ActivityTaskManagerInternal mAtmInternal;
final UserManagerInternal mUserManagerInternal;
+ final WindowManagerInternal mWmInternal;
+ final DevicePolicyManagerInternal mDpmInternal;
final ArrayMap<Integer, VoiceInteractionManagerServiceStub.SoundTriggerSession>
mLoadedKeyphraseIds = new ArrayMap<>();
ShortcutServiceInternal mShortcutServiceInternal;
@@ -156,7 +181,10 @@ public class VoiceInteractionManagerService extends SystemService {
LocalServices.getService(ActivityManagerInternal.class));
mAtmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityTaskManagerInternal.class));
-
+ mWmInternal = Objects.requireNonNull(
+ LocalServices.getService(WindowManagerInternal.class));
+ mDpmInternal = Objects.requireNonNull(
+ LocalServices.getService(DevicePolicyManagerInternal.class));
LegacyPermissionManagerInternal permissionManagerInternal = LocalServices.getService(
LegacyPermissionManagerInternal.class);
permissionManagerInternal.setVoiceInteractionPackagesProvider(
@@ -1019,6 +1047,56 @@ public class VoiceInteractionManagerService extends SystemService {
public boolean showSessionFromSession(@NonNull IBinder token, @Nullable Bundle sessionArgs,
int flags, @Nullable String attributionTag) {
synchronized (this) {
+ final String csKey = mContext.getResources()
+ .getString(R.string.config_defaultContextualSearchKey);
+ final String csEnabledKey = mContext.getResources()
+ .getString(R.string.config_defaultContextualSearchEnabled);
+
+ // If the request is for Contextual Search, process it differently
+ if (sessionArgs != null && sessionArgs.containsKey(csKey)) {
+ if (sessionArgs.getBoolean(csEnabledKey, true)) {
+ // If Contextual Search is enabled, try to follow that path.
+ Intent launchIntent = getContextualSearchIntent(sessionArgs);
+ if (launchIntent != null) {
+ // Hand over to contextual search helper.
+ Slog.d(TAG, "Handed over to contextual search helper.");
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return startContextualSearch(launchIntent);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+ }
+
+ // Since we are here, Contextual Search helper couldn't handle the request.
+ final String visEnabledKey = mContext.getResources()
+ .getString(R.string.config_defaultContextualSearchLegacyEnabled);
+ if (sessionArgs.getBoolean(visEnabledKey, true)) {
+ // If visEnabledKey is set to true (or absent), we try following VIS path.
+ String csPkgName = mContext.getResources()
+ .getString(R.string.config_defaultContextualSearchPackageName);
+ if (!csPkgName.equals(getCurInteractor(
+ Binder.getCallingUserHandle().getIdentifier()).getPackageName())) {
+ // Check if the interactor can handle Contextual Search.
+ // If not, return failure.
+ Slog.w(TAG, "Contextual Search not supported yet. Returning failure.");
+ return false;
+ }
+ } else {
+ // If visEnabledKey is set to false AND the request was for Contextual
+ // Search, return false.
+ return false;
+ }
+ // Given that we haven't returned yet, we can say that
+ // - Contextual Search Helper couldn't handle the request
+ // - VIS path for Contextual Search is enabled
+ // - The current interactor supports Contextual Search.
+ // Hence, we will proceed with the VIS path.
+ Slog.d(TAG, "Contextual search not supported yet. Proceeding with VIS.");
+
+ }
+
if (mImpl == null) {
Slog.w(TAG, "showSessionFromSession without running voice interaction service");
return false;
@@ -2644,6 +2722,70 @@ public class VoiceInteractionManagerService extends SystemService {
}
}
};
+
+ private Intent getContextualSearchIntent(Bundle args) {
+ String csPkgName = mContext.getResources()
+ .getString(R.string.config_defaultContextualSearchPackageName);
+ if (csPkgName.isEmpty()) {
+ // Return null if csPackageName is not specified.
+ return null;
+ }
+ Intent launchIntent = new Intent(CS_INTENT_FILTER);
+ launchIntent.setPackage(csPkgName);
+ ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivity(
+ launchIntent, PackageManager.MATCH_FACTORY_ONLY);
+ if (resolveInfo == null) {
+ return null;
+ }
+ launchIntent.setComponent(resolveInfo.getComponentInfo().getComponentName());
+ launchIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_NO_ANIMATION
+ | FLAG_ACTIVITY_NO_USER_ACTION);
+ launchIntent.putExtras(args);
+ boolean isAssistDataAllowed = mAtmInternal.isAssistDataAllowed();
+ final List<ActivityAssistInfo> records = mAtmInternal.getTopVisibleActivities();
+ ArrayList<String> visiblePackageNames = new ArrayList<>();
+ boolean isManagedProfileVisible = false;
+ for (ActivityAssistInfo record: records) {
+ // Add the package name to the list only if assist data is allowed.
+ if (isAssistDataAllowed) {
+ visiblePackageNames.add(record.getComponentName().getPackageName());
+ }
+ if (mDpmInternal.isUserOrganizationManaged(record.getUserId())) {
+ isManagedProfileVisible = true;
+ }
+ }
+ final ScreenCapture.ScreenshotHardwareBuffer shb = mWmInternal.takeAssistScreenshot();
+ final Bitmap bm = shb != null ? shb.asBitmap() : null;
+ // Now that everything is fetched, putting it in the launchIntent.
+ if (bm != null) {
+ launchIntent.putExtra(CS_KEY_FLAG_SECURE_FOUND, shb.containsSecureLayers());
+ // Only put the screenshot if assist data is allowed
+ if (isAssistDataAllowed) {
+ launchIntent.putExtra(CS_KEY_FLAG_SCREENSHOT, bm.asShared());
+ }
+ }
+ launchIntent.putExtra(CS_KEY_FLAG_IS_MANAGED_PROFILE_VISIBLE, isManagedProfileVisible);
+ // Only put the list of visible package names if assist data is allowed
+ if (isAssistDataAllowed) {
+ launchIntent.putExtra(CS_KEY_FLAG_VISIBLE_PACKAGE_NAMES, visiblePackageNames);
+ }
+
+ return launchIntent;
+ }
+
+ @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS)
+ private boolean startContextualSearch(Intent launchIntent) {
+ // Contextual search starts with a frozen screen - so we launch without
+ // any system animations or starting window.
+ final ActivityOptions opts = ActivityOptions.makeCustomTaskAnimation(mContext,
+ /* enterResId= */ 0, /* exitResId= */ 0, null, null, null);
+ opts.setDisableStartingWindow(true);
+ int resultCode = mAtmInternal.startActivityWithScreenshot(launchIntent,
+ mContext.getPackageName(), Binder.getCallingUid(), Binder.getCallingPid(), null,
+ opts.toBundle(), Binder.getCallingUserHandle().getIdentifier());
+ return resultCode == ActivityManager.START_SUCCESS;
+ }
+
}
/**
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 82ed340e8a0c..58488d1900fe 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -573,7 +573,7 @@ public class SubscriptionInfo implements Parcelable {
* Returns the number of this subscription.
*
* Starting with API level 30, returns the number of this subscription if the calling app meets
- * one of the following requirements:
+ * at least one of the following requirements:
* <ul>
* <li>If the calling app's target SDK is API level 29 or lower and the app has been granted
* the READ_PHONE_STATE permission.
@@ -584,8 +584,8 @@ public class SubscriptionInfo implements Parcelable {
* <li>If the calling app is the default SMS role holder.
* </ul>
*
- * @return the number of this subscription, or an empty string if one of these requirements is
- * not met
+ * @return the number of this subscription, or an empty string if none of the requirements
+ * are met.
* @deprecated use {@link SubscriptionManager#getPhoneNumber(int)} instead, which takes a
* {@link #getSubscriptionId() subscription ID}.
*/
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 9789082e1460..3f02ae9e13df 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -549,13 +549,19 @@ public class MmTelFeature extends ImsFeature {
public static final int CAPABILITY_TYPE_SMS = 1 << 3;
/**
- * This MmTelFeature supports Call Composer (section 2.4 of RC.20)
+ * This MmTelFeature supports Call Composer (section 2.4 of RC.20). This is the superset
+ * Call Composer, meaning that all subset types of Call Composers must be enabled when this
+ * capability is enabled
*/
public static final int CAPABILITY_TYPE_CALL_COMPOSER = 1 << 4;
/**
- * This MmTelFeature supports Business-only Call Composer
+ * This MmTelFeature supports Business-only Call Composer. This is a subset of
+ * {@code CAPABILITY_TYPE_CALL_COMPOSER} that only supports business related
+ * information for calling (e.g. information to signal if the call is a business call) in
+ * the SIP header. When enabling {@code CAPABILITY_TYPE_CALL_COMPOSER}, the
+ * {@code CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY} capability must also be enabled.
*/
@FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
public static final int CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY = 1 << 5;
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 217659ee4345..700856c50bae 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -726,7 +726,7 @@ public class GraphicsActivity extends Activity {
.map(Object::toString)
.collect(Collectors.joining(", ")));
int initialNumEvents = mModeChangedEvents.size();
- surface.setFrameRate(30.f, compatibility);
+ surface.setFrameRate(70.f, compatibility);
verifyFrameRates(expectedFrameRates, surface);
verifyModeSwitchesDontChangeResolution(initialNumEvents, mModeChangedEvents.size());
});
@@ -824,7 +824,7 @@ public class GraphicsActivity extends Activity {
Display display = getDisplay();
List<Float> expectedFrameRates = getRefreshRates(display.getMode(), display)
.stream()
- .filter(rate -> rate >= 30.f)
+ .filter(rate -> rate >= 70.f)
.collect(Collectors.toList());
assumeTrue("**** testSurfaceControlFrameRateCompatibility SKIPPED because no refresh rate "
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
index eca258c5a74d..b5d5b5fb6d92 100644
--- a/tools/hoststubgen/TEST_MAPPING
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -1,6 +1,7 @@
{
"presubmit": [
- { "name": "tiny-framework-dump-test" },
+ // TODO(b/326897452): Reenable after JDK 21 switch.
+ // { "name": "tiny-framework-dump-test" },
{ "name": "hoststubgentest" },
{ "name": "hoststubgen-invoke-test" }
],
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
index cee29dcd1d59..1dec6ab092cb 100755
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
@@ -46,6 +46,7 @@ def check_one_file(filename):
class TestWithGoldenOutput(unittest.TestCase):
# Test to check the generated jar files to the golden output.
+ @unittest.skip("Disabled until JDK 21 is merged and the golden files updated")
def test_compare_to_golden(self):
files = os.listdir(GOLDEN_DIR)
files.sort()