summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp15
-rwxr-xr-xapi/gen_combined_removed_dex.sh2
-rw-r--r--core/api/current.txt1
-rw-r--r--core/api/system-current.txt6
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/ActivityThread.java14
-rw-r--r--core/java/android/app/ApplicationPackageManager.java16
-rw-r--r--core/java/android/app/Instrumentation.java77
-rw-r--r--core/java/android/app/KeyguardManager.java173
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java28
-rw-r--r--core/java/android/app/admin/EnforcingAdmin.aidl19
-rw-r--r--core/java/android/app/admin/EnforcingAdmin.java28
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl2
-rw-r--r--core/java/android/app/admin/Provisioning_OWNERS4
-rw-r--r--core/java/android/content/Context.java15
-rw-r--r--core/java/android/content/Intent.java1
-rw-r--r--core/java/android/content/om/OWNERS3
-rw-r--r--core/java/android/content/pm/ArchivedActivityParcel.aidl25
-rw-r--r--core/java/android/content/pm/ArchivedPackageParcel.aidl4
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl7
-rw-r--r--core/java/android/content/pm/PackageInfo.java16
-rw-r--r--core/java/android/content/pm/PackageItemInfo.java17
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java7
-rw-r--r--core/java/android/content/pm/parsing/ApkLite.java96
-rw-r--r--core/java/android/content/pm/parsing/ApkLiteParseUtils.java31
-rw-r--r--core/java/android/content/pm/parsing/PackageLite.java76
-rw-r--r--core/java/android/content/res/OWNERS3
-rw-r--r--core/java/android/credentials/CredentialManager.java65
-rw-r--r--core/java/android/credentials/GetCandidateCredentialsException.java95
-rw-r--r--core/java/android/credentials/GetCandidateCredentialsRequest.aidl19
-rw-r--r--core/java/android/credentials/GetCandidateCredentialsRequest.java147
-rw-r--r--core/java/android/credentials/GetCandidateCredentialsResponse.aidl19
-rw-r--r--core/java/android/credentials/GetCandidateCredentialsResponse.java55
-rw-r--r--core/java/android/credentials/ICredentialManager.aidl4
-rw-r--r--core/java/android/credentials/IGetCandidateCredentialsCallback.aidl30
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl3
-rw-r--r--core/java/android/hardware/input/InputManager.java28
-rw-r--r--core/java/android/hardware/input/InputManagerGlobal.java16
-rw-r--r--core/java/android/hardware/input/KeyboardLayout.java28
-rw-r--r--core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java504
-rw-r--r--core/java/android/hardware/input/PhysicalKeyLayout.java434
-rw-r--r--core/java/android/os/Debug.java18
-rw-r--r--core/java/android/permission/flags.aconfig8
-rw-r--r--core/java/android/security/TEST_MAPPING2
-rw-r--r--core/java/android/service/autofill/AutofillServiceInfo.java16
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java8
-rw-r--r--core/java/android/view/InputWindowHandle.java12
-rw-r--r--core/java/android/view/KeyCharacterMap.aidl19
-rw-r--r--core/java/android/view/KeyCharacterMap.java37
-rw-r--r--core/java/android/view/ViewGroup.java2
-rw-r--r--core/java/android/view/WindowLayout.java8
-rw-r--r--core/java/android/view/inputmethod/TEST_MAPPING3
-rw-r--r--core/java/android/widget/AdapterViewFlipper.java41
-rw-r--r--core/java/android/widget/AnalogClock.java33
-rw-r--r--core/java/android/widget/TextClock.java130
-rw-r--r--core/java/android/widget/TextView.java3
-rw-r--r--core/java/android/widget/ViewFlipper.java40
-rw-r--r--core/java/android/widget/inline/TEST_MAPPING3
-rw-r--r--core/java/android/window/SnapshotDrawerUtils.java7
-rw-r--r--core/java/android/window/TransitionRequestInfo.java70
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java29
-rw-r--r--core/java/com/android/internal/app/ChooserListAdapter.java9
-rw-r--r--core/jni/android_os_Debug.cpp138
-rw-r--r--core/jni/android_view_KeyCharacterMap.cpp34
-rw-r--r--core/proto/android/content/package_item_info.proto1
-rw-r--r--core/res/AndroidManifest.xml4
-rw-r--r--core/tests/coretests/Android.bp2
-rw-r--r--core/tests/coretests/src/android/content/BroadcastReceiverTests.java12
-rw-r--r--core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java10
-rw-r--r--core/tests/coretests/src/android/service/quicksettings/OWNERS1
-rw-r--r--core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java11
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt132
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java1
-rw-r--r--data/etc/services.core.protolog.json66
-rw-r--r--keystore/java/android/security/AndroidKeyStoreMaintenance.java8
-rw-r--r--keystore/java/android/security/Authorization.java3
-rw-r--r--keystore/java/android/security/KeyStore.java3
-rw-r--r--keystore/java/android/security/KeyStore2.java19
-rw-r--r--keystore/java/android/security/KeyStoreOperation.java5
-rw-r--r--keystore/java/android/security/KeyStoreSecurityLevel.java7
-rw-r--r--keystore/java/android/security/LegacyVpnProfileStore.java5
-rw-r--r--keystore/java/android/security/SystemKeyStore.java9
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java9
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java2
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java3
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java2
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java2
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java2
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java11
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java7
-rw-r--r--libs/WindowManager/Shell/res/values-television/config.xml6
-rw-r--r--libs/WindowManager/Shell/res/values-watch/config.xml35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java108
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java22
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt50
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt35
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java14
-rw-r--r--libs/androidfw/OWNERS1
-rw-r--r--packages/CredentialManager/AndroidManifest.xml47
-rw-r--r--packages/CredentialManager/res/xml/autofill_service_configuration.xml10
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt38
-rw-r--r--packages/PackageInstaller/AndroidManifest.xml7
-rw-r--r--packages/PackageInstaller/res/values/themes.xml5
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsTextFieldPasswordPageProvider.kt73
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt3
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt92
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt61
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPasswordTest.kt85
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java107
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/OWNERS2
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig7
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml4
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml1
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml1
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java37
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt7
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt3
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml129
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml109
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml1
-rw-r--r--packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml49
-rw-r--r--packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml47
-rw-r--r--packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml8
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java31
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java23
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java63
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java16
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java98
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt163
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt127
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt185
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt298
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureLayoutInflaterFactory.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PlaceHolderDrawable.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt140
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt240
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt359
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt15
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt64
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt21
-rw-r--r--services/autofill/features.aconfig9
-rw-r--r--services/core/java/com/android/server/SmartStorageMaintIdler.java29
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java82
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java1
-rw-r--r--services/core/java/com/android/server/am/AnrHelper.java3
-rw-r--r--services/core/java/com/android/server/am/AnrTimer.java834
-rw-r--r--services/core/java/com/android/server/am/AppProfiler.java11
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java20
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java96
-rw-r--r--services/core/java/com/android/server/display/BrightnessRangeController.java31
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java33
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java26
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java43
-rw-r--r--services/core/java/com/android/server/display/RampAnimator.java35
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java4
-rw-r--r--services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java132
-rw-r--r--services/core/java/com/android/server/display/config/HdrBrightnessData.java98
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java85
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/input/GestureMonitorSpyWindow.java4
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java7
-rw-r--r--services/core/java/com/android/server/input/KeyboardLayoutManager.java18
-rw-r--r--services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java4
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java25
-rw-r--r--services/core/java/com/android/server/media/LegacyBluetoothRouteController.java6
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java1
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java7
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java3
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java10
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java12
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java196
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java13
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java126
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java92
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java26
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java10
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java9
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java9
-rw-r--r--services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java22
-rw-r--r--services/core/java/com/android/server/security/TEST_MAPPING2
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java17
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java50
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java20
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartInterceptor.java29
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java28
-rw-r--r--services/core/java/com/android/server/wm/ContentRecorder.java119
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java5
-rw-r--r--services/core/java/com/android/server/wm/DragState.java13
-rw-r--r--services/core/java/com/android/server/wm/InputConsumerImpl.java4
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java4
-rw-r--r--services/core/java/com/android/server/wm/InputWindowHandleWrapper.java5
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java22
-rw-r--r--services/core/java/com/android/server/wm/Task.java15
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java19
-rw-r--r--services/core/java/com/android/server/wm/Transition.java17
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java18
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java12
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java1
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java23
-rw-r--r--services/core/xsd/display-device-config/display-device-config.xsd32
-rw-r--r--services/core/xsd/display-device-config/schema/current.txt16
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java29
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java3
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java89
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java3
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java38
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java132
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java77
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java62
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java117
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java41
-rw-r--r--services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java378
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java21
-rw-r--r--services/tests/wmtests/AndroidManifest.xml7
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java90
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java24
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java50
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java149
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java79
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java9
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java6
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java9
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java16
-rw-r--r--tests/CtsSurfaceControlTestsStaging/TEST_MAPPING2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt13
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt3
-rw-r--r--tests/Input/Android.bp1
-rw-r--r--tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt69
-rw-r--r--tests/InputScreenshotTest/Android.bp60
-rw-r--r--tests/InputScreenshotTest/AndroidManifest.xml31
-rw-r--r--tests/InputScreenshotTest/AndroidTest.xml36
-rw-r--r--tests/InputScreenshotTest/OWNERS2
-rw-r--r--tests/InputScreenshotTest/TEST_MAPPING7
-rw-r--r--tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.pngbin0 -> 77207 bytes
-rw-r--r--tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.pngbin0 -> 70708 bytes
-rw-r--r--tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.pngbin0 -> 73441 bytes
-rw-r--r--tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.pngbin0 -> 71619 bytes
-rw-r--r--tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.pngbin0 -> 45707 bytes
-rw-r--r--tests/InputScreenshotTest/src/android/input/screenshot/Bitmap.kt66
-rw-r--r--tests/InputScreenshotTest/src/android/input/screenshot/DefaultDeviceEmulationSpec.kt75
-rw-r--r--tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt44
-rw-r--r--tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt87
-rw-r--r--tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt70
-rw-r--r--tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt58
-rw-r--r--tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt70
-rw-r--r--tests/InputScreenshotTest/src/android/input/screenshot/LayoutPreview.kt39
-rw-r--r--tools/aapt2/OWNERS4
372 files changed, 10289 insertions, 2286 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index a5178cfaeec8..2623702f1dc9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -35,6 +35,7 @@ java_defaults {
":android.widget.flags-aconfig-java{.generated_srcjars}",
":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
":sdk_sandbox_flags_lib{.generated_srcjars}",
+ ":android.permission.flags-aconfig-java{.generated_srcjars}",
],
// Add aconfig-annotations-lib as a dependency for the optimization
libs: ["aconfig-annotations-lib"],
@@ -130,7 +131,6 @@ java_aconfig_library {
name: "android.security.flags-aconfig-java-host",
aconfig_declarations: "android.security.flags-aconfig",
host_supported: true,
- test: true,
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
@@ -250,3 +250,16 @@ java_aconfig_library {
aconfig_declarations: "com.android.media.flags.bettertogether-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// Permissions
+aconfig_declarations {
+ name: "android.permission.flags-aconfig",
+ package: "android.permission.flags",
+ srcs: ["core/java/android/permission/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.permission.flags-aconfig-java",
+ aconfig_declarations: "android.permission.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/api/gen_combined_removed_dex.sh b/api/gen_combined_removed_dex.sh
index 9225fe8dfe85..71f366a6aae2 100755
--- a/api/gen_combined_removed_dex.sh
+++ b/api/gen_combined_removed_dex.sh
@@ -6,6 +6,6 @@ shift 2
# Convert each removed.txt to the "dex format" equivalent, and print all output.
for f in "$@"; do
- "$metalava_path" --no-banner "$f" --dex-api "${tmp_dir}/tmp"
+ "$metalava_path" "$f" --dex-api "${tmp_dir}/tmp"
cat "${tmp_dir}/tmp"
done
diff --git a/core/api/current.txt b/core/api/current.txt
index f488c82a2a60..a5784a048274 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -32285,6 +32285,7 @@ package android.os {
method public static long getNativeHeapFreeSize();
method public static long getNativeHeapSize();
method public static long getPss();
+ method @FlaggedApi(Flags.FLAG_REMOVE_APP_PROFILER_PSS_COLLECTION) public static long getRss();
method public static String getRuntimeStat(String);
method public static java.util.Map<java.lang.String,java.lang.String> getRuntimeStats();
method @Deprecated public static int getThreadAllocCount();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1a0bd85efcdf..5c48b21ad35e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3484,6 +3484,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 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";
@@ -3799,10 +3800,6 @@ package android.content.pm {
field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800
}
- public class PackageInfo implements android.os.Parcelable {
- field public boolean isArchived;
- }
-
public class PackageInstaller {
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
@@ -3880,6 +3877,7 @@ package android.content.pm {
method public static void forceSafeLabels();
method @Deprecated @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager);
method @NonNull public CharSequence loadSafeLabel(@NonNull android.content.pm.PackageManager, @FloatRange(from=0) float, int);
+ field @FlaggedApi(Flags.FLAG_ARCHIVING) public boolean isArchived;
}
public abstract class PackageManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a8a6eb9f34ee..a4cc44646341 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -50,6 +50,7 @@ package android {
field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
field public static final String SET_GAME_SERVICE = "android.permission.SET_GAME_SERVICE";
field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
+ field public static final String START_ACTIVITIES_FROM_SDK_SANDBOX = "android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD";
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 00e546ad25b0..01912012f04a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3720,7 +3720,19 @@ public final class ActivityThread extends ClientTransactionHandler
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
- if (r.packageInfo == null) {
+
+ if (getInstrumentation() != null
+ && getInstrumentation().getContext() != null
+ && getInstrumentation().getContext().getApplicationInfo() != null
+ && getInstrumentation().isSdkSandboxAllowedToStartActivities()) {
+ // Activities launched from CTS-in-sandbox tests use a customized ApplicationInfo. See
+ // also {@link SdkSandboxManagerLocal#getSdkSandboxApplicationInfoForInstrumentation}.
+ r.packageInfo =
+ getPackageInfo(
+ getInstrumentation().getContext().getApplicationInfo(),
+ mCompatibilityInfo,
+ Context.CONTEXT_INCLUDE_CODE);
+ } else if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, mCompatibilityInfo,
Context.CONTEXT_INCLUDE_CODE);
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 02558602acd3..04d04b9adbdd 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -3355,7 +3355,11 @@ public class ApplicationPackageManager extends PackageManager {
}
Drawable dr = null;
if (itemInfo.packageName != null) {
- dr = getDrawable(itemInfo.packageName, itemInfo.icon, appInfo);
+ if (itemInfo.isArchived) {
+ dr = getArchivedAppIcon(itemInfo.packageName);
+ } else {
+ dr = getDrawable(itemInfo.packageName, itemInfo.icon, appInfo);
+ }
}
if (dr == null && itemInfo != appInfo && appInfo != null) {
dr = loadUnbadgedItemIcon(appInfo, appInfo);
@@ -3964,4 +3968,14 @@ public class ApplicationPackageManager extends PackageManager {
throw e.rethrowFromSystemServer();
}
}
+
+ @Nullable
+ private Drawable getArchivedAppIcon(String packageName) {
+ try {
+ return new BitmapDrawable(null,
+ mPM.getArchivedAppIcon(packageName, new UserHandle(getUserId())));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index e31486f18dbf..10747bb0e57e 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -26,6 +26,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerGlobal;
@@ -474,6 +475,56 @@ public class Instrumentation {
sr.waitForComplete();
}
+ boolean isSdkSandboxAllowedToStartActivities() {
+ return Process.isSdkSandbox()
+ && mThread != null
+ && mThread.mBoundApplication != null
+ && mThread.mBoundApplication.isSdkInSandbox
+ && getContext() != null
+ && (getContext()
+ .checkSelfPermission(
+ android.Manifest.permission
+ .START_ACTIVITIES_FROM_SDK_SANDBOX)
+ == PackageManager.PERMISSION_GRANTED);
+ }
+
+ /**
+ * Activity name resolution for CTS-in-SdkSandbox tests requires some adjustments. Intents
+ * generated using {@link Context#getPackageName()} use the SDK sandbox package name in the
+ * component field instead of the test package name. An SDK-in-sandbox test attempting to launch
+ * an activity in the test package will encounter name resolution errors when resolving the
+ * activity name in the SDK sandbox package.
+ *
+ * <p>This function replaces the package name of the input intent component to allow activities
+ * belonging to a CTS-in-sandbox test to resolve correctly.
+ *
+ * @param intent the intent to modify to allow CTS-in-sandbox activity resolution.
+ */
+ private void adjustIntentForCtsInSdkSandboxInstrumentation(@NonNull Intent intent) {
+ if (mComponent != null
+ && intent.getComponent() != null
+ && getContext()
+ .getPackageManager()
+ .getSdkSandboxPackageName()
+ .equals(intent.getComponent().getPackageName())) {
+ // Resolve the intent target for the test package, not for the sandbox package.
+ intent.setComponent(
+ new ComponentName(
+ mComponent.getPackageName(), intent.getComponent().getClassName()));
+ }
+ // We match the intent identifier against the running instrumentations for the sandbox.
+ intent.setIdentifier(mComponent.getPackageName());
+ }
+
+ private ActivityInfo resolveActivityInfoForCtsInSandbox(@NonNull Intent intent) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ ActivityInfo ai = intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0);
+ if (ai != null) {
+ ai.processName = mThread.getProcessName();
+ }
+ return ai;
+ }
+
/**
* Start a new activity and wait for it to begin running before returning.
* In addition to being synchronous, this method as some semantic
@@ -531,8 +582,10 @@ public class Instrumentation {
synchronized (mSync) {
intent = new Intent(intent);
- ActivityInfo ai = intent.resolveActivityInfo(
- getTargetContext().getPackageManager(), 0);
+ ActivityInfo ai =
+ isSdkSandboxAllowedToStartActivities()
+ ? resolveActivityInfoForCtsInSandbox(intent)
+ : intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0);
if (ai == null) {
throw new RuntimeException("Unable to resolve activity for: " + intent);
}
@@ -1842,6 +1895,9 @@ public class Instrumentation {
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -1914,6 +1970,11 @@ public class Instrumentation {
IBinder token, Activity target, Intent[] intents, Bundle options,
int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ for (Intent intent : intents) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -1989,6 +2050,9 @@ public class Instrumentation {
Context who, IBinder contextThread, IBinder token, String target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -2060,6 +2124,9 @@ public class Instrumentation {
Context who, IBinder contextThread, IBinder token, String resultWho,
Intent intent, int requestCode, Bundle options, UserHandle user) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -2110,6 +2177,9 @@ public class Instrumentation {
Intent intent, int requestCode, Bundle options,
boolean ignoreTargetSecurity, int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -2161,6 +2231,9 @@ public class Instrumentation {
Context who, IBinder contextThread, IAppTask appTask,
Intent intent, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index e447dc511ced..2d554031ab48 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -49,7 +49,6 @@ import android.util.ArrayMap;
import android.util.Log;
import android.view.IOnKeyguardExitResult;
import android.view.IWindowManager;
-import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
import com.android.internal.annotations.VisibleForTesting;
@@ -71,9 +70,7 @@ import java.util.Objects;
import java.util.concurrent.Executor;
/**
- * Class that can be used to lock and unlock the keyguard. The
- * actual class to control the keyguard locking is
- * {@link android.app.KeyguardManager.KeyguardLock}.
+ * Class to manage and query the state of the lock screen (also known as Keyguard).
*/
@SystemService(Context.KEYGUARD_SERVICE)
public class KeyguardManager {
@@ -259,7 +256,9 @@ public class KeyguardManager {
* {@link android.app.Activity#RESULT_OK} if the user successfully completes the challenge.
*
* @return the intent for launching the activity or null if no password is required.
- * @deprecated see BiometricPrompt.Builder#setDeviceCredentialAllowed(boolean)
+ *
+ * @deprecated see {@link
+ * android.hardware.biometrics.BiometricPrompt.Builder#setAllowedAuthenticators(int)}
*/
@Deprecated
@RequiresFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)
@@ -477,13 +476,12 @@ public class KeyguardManager {
/**
* Handle returned by {@link KeyguardManager#newKeyguardLock} that allows
- * you to disable / reenable the keyguard.
+ * you to temporarily disable / reenable the keyguard (lock screen).
*
- * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
- * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
- * instead; this allows you to seamlessly hide the keyguard as your application
- * moves in and out of the foreground and does not require that any special
- * permissions be requested.
+ * @deprecated Use {@link android.R.attr#showWhenLocked} or {@link
+ * android.app.Activity#setShowWhenLocked(boolean)} instead. This allows you to seamlessly
+ * occlude and unocclude the keyguard as your application moves in and out of the foreground
+ * and does not require that any special permissions be requested.
*/
@Deprecated
public class KeyguardLock {
@@ -498,12 +496,12 @@ public class KeyguardManager {
* Disable the keyguard from showing. If the keyguard is currently
* showing, hide it. The keyguard will be prevented from showing again
* until {@link #reenableKeyguard()} is called.
- *
+ * <p>
+ * This only works if the keyguard is not secure.
+ * <p>
* A good place to call this is from {@link android.app.Activity#onResume()}
*
- * Note: This call has no effect while any {@link android.app.admin.DevicePolicyManager}
- * is enabled that requires a password.
- *
+ * @see KeyguardManager#isKeyguardSecure()
* @see #reenableKeyguard()
*/
@RequiresPermission(Manifest.permission.DISABLE_KEYGUARD)
@@ -520,9 +518,6 @@ public class KeyguardManager {
*
* A good place to call this is from {@link android.app.Activity#onPause()}
*
- * Note: This call has no effect while any {@link android.app.admin.DevicePolicyManager}
- * is enabled that requires a password.
- *
* @see #disableKeyguard()
*/
@RequiresPermission(Manifest.permission.DISABLE_KEYGUARD)
@@ -621,20 +616,18 @@ public class KeyguardManager {
}
/**
- * Enables you to lock or unlock the keyguard. Get an instance of this class by
- * calling {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
- * This class is wrapped by {@link android.app.KeyguardManager KeyguardManager}.
+ * Enables you to temporarily disable / reenable the keyguard (lock screen).
+ *
* @param tag A tag that informally identifies who you are (for debugging who
* is disabling the keyguard).
*
* @return A {@link KeyguardLock} handle to use to disable and reenable the
* keyguard.
*
- * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
- * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
- * instead; this allows you to seamlessly hide the keyguard as your application
- * moves in and out of the foreground and does not require that any special
- * permissions be requested.
+ * @deprecated Use {@link android.R.attr#showWhenLocked} or {@link
+ * android.app.Activity#setShowWhenLocked(boolean)} instead. This allows you to seamlessly
+ * occlude and unocclude the keyguard as your application moves in and out of the foreground
+ * and does not require that any special permissions be requested.
*/
@Deprecated
public KeyguardLock newKeyguardLock(String tag) {
@@ -642,9 +635,36 @@ public class KeyguardManager {
}
/**
- * Return whether the keyguard is currently locked.
+ * Returns whether the lock screen (also known as Keyguard) is showing.
+ * <p>
+ * Specifically, this returns {@code true} in the following cases:
+ * <ul>
+ * <li>The lock screen is showing in the foreground.</li>
+ * <li>The lock screen is showing, but it is occluded by an activity that is showing on top of
+ * it. A common example is the phone app receiving a call or making an emergency call.</li>
+ * <li>The lock screen was showing but is temporarily disabled as a result of <a
+ * href="https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode">lock task
+ * mode</a> or an app using the deprecated {@link KeyguardLock} API.</li>
+ * </ul>
+ * <p>
+ * "Showing" refers to a logical state of the UI, regardless of whether the screen happens to be
+ * on. When the power button is pressed on an unlocked device, the lock screen starts "showing"
+ * immediately when the screen turns off.
+ * <p>
+ * This method does not distinguish a lock screen that is requiring authentication (e.g. with
+ * PIN, pattern, password, or biometric) from a lock screen that is trivially dismissible (e.g.
+ * with swipe). It also does not distinguish a lock screen requesting a SIM card PIN from a
+ * normal device lock screen. Finally, it always returns the global lock screen state and does
+ * not consider the {@link Context}'s user specifically.
+ * <p>
+ * Note that {@code isKeyguardLocked()} is confusingly named and probably should be called
+ * {@code isKeyguardShowing()}. On many devices, the lock screen displays an <i>unlocked</i>
+ * padlock icon when it is trivially dismissible. As mentioned above, {@code isKeyguardLocked()}
+ * actually returns {@code true} in this case, not {@code false} as might be expected. {@link
+ * #isDeviceLocked()} is an alternative API that has slightly different semantics.
*
- * @return {@code true} if the keyguard is locked.
+ * @return {@code true} if the lock screen is showing
+ * @see #isDeviceLocked()
*/
public boolean isKeyguardLocked() {
try {
@@ -655,12 +675,23 @@ public class KeyguardManager {
}
/**
- * Return whether the keyguard is secured by a PIN, pattern or password or a SIM card
- * is currently locked.
- *
- * <p>See also {@link #isDeviceSecure()} which ignores SIM locked states.
+ * Returns whether the user has a secure lock screen or there is a locked SIM card.
+ * <p>
+ * Specifically, this returns {@code true} if at least one of the following is true:
+ * <ul>
+ * <li>The {@link Context}'s user has a secure lock screen. A full user has a secure lock
+ * screen if its lock screen is set to PIN, pattern, or password, as opposed to swipe or none.
+ * A profile that uses a unified challenge is considered to have a secure lock screen if and
+ * only if its parent user has a secure lock screen.</li>
+ * <li>At least one SIM card is currently locked and requires a PIN.</li>
+ * </ul>
+ * <p>
+ * This method does not consider whether the lock screen is currently showing or not.
+ * <p>
+ * See also {@link #isDeviceSecure()} which excludes locked SIM cards.
*
- * @return {@code true} if a PIN, pattern or password is set or a SIM card is locked.
+ * @return {@code true} if the user has a secure lock screen or there is a locked SIM card
+ * @see #isDeviceSecure()
*/
public boolean isKeyguardSecure() {
try {
@@ -671,11 +702,11 @@ public class KeyguardManager {
}
/**
- * If keyguard screen is showing or in restricted key input mode (i.e. in
- * keyguard password emergency screen). When in such mode, certain keys,
- * such as the Home key and the right soft keys, don't work.
+ * Returns whether the lock screen is showing.
+ * <p>
+ * This is exactly the same as {@link #isKeyguardLocked()}.
*
- * @return {@code true} if in keyguard restricted input mode.
+ * @return the value of {@link #isKeyguardLocked()}
* @deprecated Use {@link #isKeyguardLocked()} instead.
*/
public boolean inKeyguardRestrictedInputMode() {
@@ -683,11 +714,26 @@ public class KeyguardManager {
}
/**
- * Returns whether the device is currently locked and requires a PIN, pattern or
- * password to unlock.
+ * Returns whether the device is currently locked for the user.
+ * <p>
+ * This returns the device locked state for the {@link Context}'s user. If this user is the
+ * current user, then the device is considered "locked" when the lock screen is showing (i.e.
+ * {@link #isKeyguardLocked()} returns {@code true}) and is not trivially dismissible (e.g. with
+ * swipe), and the user has a PIN, pattern, or password.
+ * <p>
+ * Note: the above definition implies that a user with no PIN, pattern, or password is never
+ * considered locked, even if the lock screen is showing and requesting a SIM card PIN. The
+ * device PIN and SIM PIN are separate. Also, the user is not considered locked if face
+ * authentication has just completed or a trust agent is keeping the device unlocked, since in
+ * these cases the lock screen is dismissible with swipe.
+ * <p>
+ * For a user that is not the current user but can be switched to (usually this means "another
+ * full user"), and that has a PIN, pattern, or password, the device is always considered
+ * locked. For a profile with a unified challenge, the device is considered locked if and only
+ * if the device is locked for the parent user.
*
- * @return {@code true} if unlocking the device currently requires a PIN, pattern or
- * password.
+ * @return {@code true} if the device is currently locked for the user
+ * @see #isKeyguardLocked()
*/
public boolean isDeviceLocked() {
return isDeviceLocked(mContext.getUserId());
@@ -708,12 +754,19 @@ public class KeyguardManager {
}
/**
- * Returns whether the device is secured with a PIN, pattern or
- * password.
- *
- * <p>See also {@link #isKeyguardSecure} which treats SIM locked states as secure.
+ * Returns whether the user has a secure lock screen.
+ * <p>
+ * This returns {@code true} if the {@link Context}'s user has a secure lock screen. A full user
+ * has a secure lock screen if its lock screen is set to PIN, pattern, or password, as opposed
+ * to swipe or none. A profile that uses a unified challenge is considered to have a secure lock
+ * screen if and only if its parent user has a secure lock screen.
+ * <p>
+ * This method does not consider whether the lock screen is currently showing or not.
+ * <p>
+ * See also {@link #isKeyguardSecure()} which includes locked SIM cards.
*
- * @return {@code true} if a PIN, pattern or password was set.
+ * @return {@code true} if the user has a secure lock screen
+ * @see #isKeyguardSecure()
*/
public boolean isDeviceSecure() {
return isDeviceSecure(mContext.getUserId());
@@ -734,8 +787,7 @@ public class KeyguardManager {
}
/**
- * If the device is currently locked (see {@link #isKeyguardLocked()}, requests the Keyguard to
- * be dismissed.
+ * Requests that the Keyguard (lock screen) be dismissed if it is currently showing.
* <p>
* If the Keyguard is not secure or the device is currently in a trusted state, calling this
* method will immediately dismiss the Keyguard without any user interaction.
@@ -746,8 +798,9 @@ public class KeyguardManager {
* If the value set for the {@link Activity} attr {@link android.R.attr#turnScreenOn} is true,
* the screen will turn on when the keyguard is dismissed.
*
- * @param activity The activity requesting the dismissal. The activity must be either visible
- * by using {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} or must be in a state in
+ * @param activity The activity requesting the dismissal. The activity must either be visible
+ * by using {@link android.R.attr#showWhenLocked} or {@link
+ * android.app.Activity#setShowWhenLocked(boolean)}, or must be in a state in
* which it would be visible if Keyguard would not be hiding it. If that's not
* the case, the request will fail immediately and
* {@link KeyguardDismissCallback#onDismissError} will be invoked.
@@ -762,8 +815,7 @@ public class KeyguardManager {
}
/**
- * If the device is currently locked (see {@link #isKeyguardLocked()}, requests the Keyguard to
- * be dismissed.
+ * Requests that the Keyguard (lock screen) be dismissed if it is currently showing.
* <p>
* If the Keyguard is not secure or the device is currently in a trusted state, calling this
* method will immediately dismiss the Keyguard without any user interaction.
@@ -774,8 +826,9 @@ public class KeyguardManager {
* If the value set for the {@link Activity} attr {@link android.R.attr#turnScreenOn} is true,
* the screen will turn on when the keyguard is dismissed.
*
- * @param activity The activity requesting the dismissal. The activity must be either visible
- * by using {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} or must be in a state in
+ * @param activity The activity requesting the dismissal. The activity must either be visible
+ * by using {@link android.R.attr#showWhenLocked} or {@link
+ * android.app.Activity#setShowWhenLocked(boolean)}, or must be in a state in
* which it would be visible if Keyguard would not be hiding it. If that's not
* the case, the request will fail immediately and
* {@link KeyguardDismissCallback#onDismissError} will be invoked.
@@ -829,12 +882,12 @@ public class KeyguardManager {
* @param callback Lets you know whether the operation was successful and
* it is safe to launch anything that would normally be considered safe
* once the user has gotten past the keyguard.
-
- * @deprecated Use {@link LayoutParams#FLAG_DISMISS_KEYGUARD}
- * and/or {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED}
- * instead; this allows you to seamlessly hide the keyguard as your application
- * moves in and out of the foreground and does not require that any special
- * permissions be requested.
+ *
+ * @deprecated Use {@link android.R.attr#showWhenLocked} or {@link
+ * android.app.Activity#setShowWhenLocked(boolean)} to seamlessly occlude and unocclude the
+ * keyguard as your application moves in and out of the foreground, without requiring any
+ * special permissions. Use {@link #requestDismissKeyguard(android.app.Activity,
+ * KeyguardDismissCallback)} to request dismissal of the keyguard.
*/
@Deprecated
@RequiresPermission(Manifest.permission.DISABLE_KEYGUARD)
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 715edc5161b7..213e5cb4ad64 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -11806,6 +11806,34 @@ public class DevicePolicyManager {
}
/**
+ * Returns the list of {@link EnforcingAdmin}s who have set this restriction.
+ *
+ * <p>Note that for {@link #POLICY_SUSPEND_PACKAGES} it returns the PO or DO to keep the
+ * behavior the same as before the bug fix for b/192245204.
+ *
+ * <p>This API is only callable by the system UID
+ *
+ * @param userId The user for whom to retrieve the information.
+ * @param restriction The restriction enforced by admins. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ *
+ * @hide
+ */
+ public @NonNull Set<EnforcingAdmin> getEnforcingAdminsForRestriction(int userId,
+ @NonNull String restriction) {
+ if (mService != null) {
+ try {
+ return new HashSet<>(mService.getEnforcingAdminsForRestriction(
+ userId, restriction));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Hide or unhide packages. When a package is hidden it is unavailable for use, but the data and
* actual package file remain. This function can be called by a device owner, profile owner, or
* by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
diff --git a/core/java/android/app/admin/EnforcingAdmin.aidl b/core/java/android/app/admin/EnforcingAdmin.aidl
new file mode 100644
index 000000000000..bfbfdbeaf9aa
--- /dev/null
+++ b/core/java/android/app/admin/EnforcingAdmin.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.app.admin;
+
+parcelable EnforcingAdmin; \ No newline at end of file
diff --git a/core/java/android/app/admin/EnforcingAdmin.java b/core/java/android/app/admin/EnforcingAdmin.java
index 771794dbe0fb..7c718f6651a2 100644
--- a/core/java/android/app/admin/EnforcingAdmin.java
+++ b/core/java/android/app/admin/EnforcingAdmin.java
@@ -19,6 +19,7 @@ package android.app.admin;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
@@ -38,6 +39,11 @@ public final class EnforcingAdmin implements Parcelable {
private final UserHandle mUserHandle;
/**
+ * @hide
+ */
+ private final ComponentName mComponentName;
+
+ /**
* Creates an enforcing admin with the given params.
*/
public EnforcingAdmin(
@@ -46,6 +52,21 @@ public final class EnforcingAdmin implements Parcelable {
mPackageName = Objects.requireNonNull(packageName);
mAuthority = Objects.requireNonNull(authority);
mUserHandle = Objects.requireNonNull(userHandle);
+ mComponentName = null;
+ }
+
+ /**
+ * Creates an enforcing admin with the given params.
+ *
+ * @hide
+ */
+ public EnforcingAdmin(
+ @NonNull String packageName, @NonNull Authority authority,
+ @NonNull UserHandle userHandle, @Nullable ComponentName componentName) {
+ mPackageName = Objects.requireNonNull(packageName);
+ mAuthority = Objects.requireNonNull(authority);
+ mUserHandle = Objects.requireNonNull(userHandle);
+ mComponentName = componentName;
}
private EnforcingAdmin(Parcel source) {
@@ -53,6 +74,7 @@ public final class EnforcingAdmin implements Parcelable {
mUserHandle = new UserHandle(source.readInt());
mAuthority = Objects.requireNonNull(
source.readParcelable(Authority.class.getClassLoader()));
+ mComponentName = source.readParcelable(ComponentName.class.getClassLoader());
}
/**
@@ -86,7 +108,8 @@ public final class EnforcingAdmin implements Parcelable {
EnforcingAdmin other = (EnforcingAdmin) o;
return Objects.equals(mPackageName, other.mPackageName)
&& Objects.equals(mAuthority, other.mAuthority)
- && Objects.equals(mUserHandle, other.mUserHandle);
+ && Objects.equals(mUserHandle, other.mUserHandle)
+ && Objects.equals(mComponentName, other.mComponentName);
}
@Override
@@ -97,7 +120,7 @@ public final class EnforcingAdmin implements Parcelable {
@Override
public String toString() {
return "EnforcingAdmin { mPackageName= " + mPackageName + ", mAuthority= " + mAuthority
- + ", mUserHandle= " + mUserHandle + " }";
+ + ", mUserHandle= " + mUserHandle + ", mComponentName= " + mComponentName + " }";
}
@Override
@@ -110,6 +133,7 @@ public final class EnforcingAdmin implements Parcelable {
dest.writeString(mPackageName);
dest.writeInt(mUserHandle.getIdentifier());
dest.writeParcelable(mAuthority, flags);
+ dest.writeParcelable(mComponentName, flags);
}
@NonNull
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 95ec89e5f444..c49b820b9e37 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -54,6 +54,7 @@ import android.security.keystore.ParcelableKeyGenParameterSpec;
import android.telephony.data.ApnSetting;
import com.android.internal.infra.AndroidFuture;
import android.app.admin.DevicePolicyState;
+import android.app.admin.EnforcingAdmin;
import java.util.List;
@@ -274,6 +275,7 @@ interface IDevicePolicyManager {
Intent createAdminSupportIntent(in String restriction);
Bundle getEnforcingAdminAndUserDetails(int userId,String restriction);
+ List<EnforcingAdmin> getEnforcingAdminsForRestriction(int userId,String restriction);
boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden, boolean parent);
boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean parent);
diff --git a/core/java/android/app/admin/Provisioning_OWNERS b/core/java/android/app/admin/Provisioning_OWNERS
index fa0a1f09bfc8..8f71fc0c4c05 100644
--- a/core/java/android/app/admin/Provisioning_OWNERS
+++ b/core/java/android/app/admin/Provisioning_OWNERS
@@ -1,4 +1,4 @@
# Assign bugs to android-enterprise-triage@google.com
-mdb.ae-provisioning-reviews@google.com
+ae-provisioning-reviews@google.com
+petuska@google.com #{LAST_RESORT_SUGGESTION}
file:EnterprisePlatform_OWNERS
-petuska@google.com #{LAST_RESORT_SUGGESTION} \ No newline at end of file
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d6dee9389c8e..b6a98a5b8f83 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4089,6 +4089,7 @@ public abstract class Context {
VIBRATOR_MANAGER_SERVICE,
VIBRATOR_SERVICE,
//@hide: STATUS_BAR_SERVICE,
+ THREAD_NETWORK_SERVICE,
CONNECTIVITY_SERVICE,
PAC_PROXY_SERVICE,
VCN_MANAGEMENT_SERVICE,
@@ -4764,6 +4765,20 @@ public abstract class Context {
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.net.thread.ThreadNetworkManager}.
+ *
+ * <p>On devices without {@link PackageManager#FEATURE_THREAD_NETWORK} system feature
+ * the {@link #getSystemService(String)} will return {@code null}.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.thread.ThreadNetworkManager
+ * @hide
+ */
+ @SystemApi
+ public static final String THREAD_NETWORK_SERVICE = "thread_network";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.net.IpSecManager} for encrypting Sockets or Networks with
* IPSec.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e9bbed340800..7579d99f4927 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12516,6 +12516,7 @@ public class Intent implements Parcelable, Cloneable {
return (mFlags & FLAG_ACTIVITY_NEW_DOCUMENT) == FLAG_ACTIVITY_NEW_DOCUMENT;
}
+ // TODO(b/299109198): Refactor into the {@link SdkSandboxManagerLocal}
/** @hide */
public boolean isSandboxActivity(@NonNull Context context) {
if (mAction != null && mAction.equals(ACTION_START_SANDBOXED_ACTIVITY)) {
diff --git a/core/java/android/content/om/OWNERS b/core/java/android/content/om/OWNERS
index 3669817e9844..72aed2d3fdaf 100644
--- a/core/java/android/content/om/OWNERS
+++ b/core/java/android/content/om/OWNERS
@@ -1,6 +1,5 @@
# Bug component: 568631
-toddke@android.com
-toddke@google.com
patb@google.com
zyy@google.com
+jakmcbane@google.com \ No newline at end of file
diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl
new file mode 100644
index 000000000000..7ab7ed1cc5df
--- /dev/null
+++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.content.pm;
+
+/** @hide */
+parcelable ArchivedActivityParcel {
+ String title;
+ // PNG compressed bitmaps.
+ byte[] iconBitmap;
+ byte[] monochromeIconBitmap;
+}
diff --git a/core/java/android/content/pm/ArchivedPackageParcel.aidl b/core/java/android/content/pm/ArchivedPackageParcel.aidl
index 573e69094e35..d3cd79efc3b5 100644
--- a/core/java/android/content/pm/ArchivedPackageParcel.aidl
+++ b/core/java/android/content/pm/ArchivedPackageParcel.aidl
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.content.pm.ArchivedActivityParcel;
import android.content.pm.SigningDetails;
/**
@@ -29,9 +30,8 @@ parcelable ArchivedPackageParcel {
int versionCode;
int versionCodeMajor;
int targetSdkVersion;
- String backupAllowed;
String defaultToDeviceProtectedStorage;
String requestLegacyExternalStorage;
String userDataFragile;
- String clearUserDataOnFailedRestoreAllowed;
+ ArchivedActivityParcel[] archivedActivities;
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 556c794b6139..aca88d6af033 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -20,6 +20,7 @@ package android.content.pm;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ArchivedPackageParcel;
@@ -59,7 +60,7 @@ import android.os.Bundle;
import android.os.IRemoteCallback;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
-import android.content.IntentSender;
+import android.os.UserHandle;
import java.util.Map;
@@ -832,5 +833,7 @@ interface IPackageManager {
void unregisterPackageMonitorCallback(IRemoteCallback callback);
- ArchivedPackageParcel getArchivedPackage(in String apkPath);
+ ArchivedPackageParcel getArchivedPackage(in String packageName, int userId);
+
+ Bitmap getArchivedAppIcon(String packageName, in UserHandle user);
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 1fe19231e4b4..63c11b779641 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -18,9 +18,7 @@ package android.content.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.IntentSender;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -490,18 +488,6 @@ public class PackageInfo implements Parcelable {
*/
public boolean isActiveApex;
- /**
- * Whether the package is currently in an archived state.
- *
- * <p>Packages can be archived through
- * {@link PackageInstaller#requestArchive(String, IntentSender)} and do not have any APKs stored
- * on the device, but do keep the data directory.
- * @hide
- */
- // TODO(b/278553670) Unhide and update @links before launch.
- @SystemApi
- public boolean isArchived;
-
public PackageInfo() {
}
@@ -589,7 +575,6 @@ public class PackageInfo implements Parcelable {
}
dest.writeBoolean(isApex);
dest.writeBoolean(isActiveApex);
- dest.writeBoolean(isArchived);
dest.restoreAllowSquashing(prevAllowSquashing);
}
@@ -655,6 +640,5 @@ public class PackageInfo implements Parcelable {
}
isApex = source.readBoolean();
isActiveApex = source.readBoolean();
- isArchived = source.readBoolean();
}
}
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index bb978e05dd16..c7091ad99199 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -21,6 +21,7 @@ import static android.text.TextUtils.SAFE_STRING_FLAG_SINGLE_LINE;
import static android.text.TextUtils.SAFE_STRING_FLAG_TRIM;
import static android.text.TextUtils.makeSafeForPresentation;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -173,6 +174,18 @@ public class PackageItemInfo {
*/
public int showUserIcon;
+ /**
+ * Whether the package is currently in an archived state.
+ *
+ * <p>Packages can be archived through {@link PackageArchiver} and do not have any APKs stored
+ * on the device, but do keep the data directory.
+ * @hide
+ */
+ // TODO(b/278553670) Unhide and update @links before launch.
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public boolean isArchived;
+
public PackageItemInfo() {
showUserIcon = UserHandle.USER_NULL;
}
@@ -189,6 +202,7 @@ public class PackageItemInfo {
logo = orig.logo;
metaData = orig.metaData;
showUserIcon = orig.showUserIcon;
+ isArchived = orig.isArchived;
}
/**
@@ -442,6 +456,7 @@ public class PackageItemInfo {
dest.writeBundle(metaData);
dest.writeInt(banner);
dest.writeInt(showUserIcon);
+ dest.writeBoolean(isArchived);
}
/**
@@ -459,6 +474,7 @@ public class PackageItemInfo {
}
proto.write(PackageItemInfoProto.ICON, icon);
proto.write(PackageItemInfoProto.BANNER, banner);
+ proto.write(PackageItemInfoProto.IS_ARCHIVED, isArchived);
proto.end(token);
}
@@ -473,6 +489,7 @@ public class PackageItemInfo {
metaData = source.readBundle();
banner = source.readInt();
showUserIcon = source.readInt();
+ isArchived = source.readBoolean();
}
/**
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 149de7efc861..0333942b7f3e 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -834,4 +834,11 @@ public abstract class RegisteredServicesCache<V> {
public abstract V parseServiceAttributes(Resources res,
String packageName, AttributeSet attrs);
+
+ @VisibleForTesting
+ public void unregisterReceivers() {
+ mContext.unregisterReceiver(mPackageReceiver);
+ mContext.unregisterReceiver(mExternalReceiver);
+ mContext.unregisterReceiver(mUserRemovedReceiver);
+ }
}
diff --git a/core/java/android/content/pm/parsing/ApkLite.java b/core/java/android/content/pm/parsing/ApkLite.java
index 159b789fc4a0..f3194be81b0d 100644
--- a/core/java/android/content/pm/parsing/ApkLite.java
+++ b/core/java/android/content/pm/parsing/ApkLite.java
@@ -23,11 +23,9 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.SigningDetails;
import android.content.pm.VerifierInfo;
-import android.os.Build;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DataClass;
-import com.android.internal.util.XmlUtils;
import java.util.List;
import java.util.Set;
@@ -142,32 +140,9 @@ public class ApkLite {
private final boolean mIsSdkLibrary;
/**
- * Set to <code>false</code> if the application does not wish to permit any OS-driven
- * backups of its data; <code>true</code> otherwise.
+ * Archival install info.
*/
- private final boolean mBackupAllowed;
-
- /**
- * When set, the default data storage directory for this app is pointed at
- * the device-protected location.
- */
- private final boolean mDefaultToDeviceProtectedStorage;
-
- /**
- * If {@code true} this app requests full external storage access.
- */
- private final boolean mRequestLegacyExternalStorage;
-
- /**
- * Indicates whether this application has declared its user data as fragile, causing the
- * system to prompt the user on whether to keep the user data on uninstall.
- */
- private final boolean mUserDataFragile;
-
- /**
- * Indicates whether this application's data will be cleared on a failed restore.
- */
- private final boolean mClearUserDataOnFailedRestoreAllowed;
+ private final @Nullable ArchivedPackageParcel mArchivedPackage;
public ApkLite(String path, String packageName, String splitName, boolean isFeatureSplit,
String configForSplit, String usesSplitName, boolean isSplitRequired, int versionCode,
@@ -179,10 +154,7 @@ public class ApkLite {
String requiredSystemPropertyName, String requiredSystemPropertyValue,
int minSdkVersion, int targetSdkVersion, int rollbackDataPolicy,
Set<String> requiredSplitTypes, Set<String> splitTypes,
- boolean hasDeviceAdminReceiver, boolean isSdkLibrary, boolean clearUserDataAllowed,
- boolean backupAllowed, boolean defaultToDeviceProtectedStorage,
- boolean requestLegacyExternalStorage, boolean userDataFragile,
- boolean clearUserDataOnFailedRestoreAllowed) {
+ boolean hasDeviceAdminReceiver, boolean isSdkLibrary) {
mPath = path;
mPackageName = packageName;
mSplitName = splitName;
@@ -216,11 +188,7 @@ public class ApkLite {
mRollbackDataPolicy = rollbackDataPolicy;
mHasDeviceAdminReceiver = hasDeviceAdminReceiver;
mIsSdkLibrary = isSdkLibrary;
- mBackupAllowed = backupAllowed;
- mDefaultToDeviceProtectedStorage = defaultToDeviceProtectedStorage;
- mRequestLegacyExternalStorage = requestLegacyExternalStorage;
- mUserDataFragile = userDataFragile;
- mClearUserDataOnFailedRestoreAllowed = clearUserDataOnFailedRestoreAllowed;
+ mArchivedPackage = null;
}
public ApkLite(String path, ArchivedPackageParcel archivedPackage) {
@@ -257,16 +225,7 @@ public class ApkLite {
mRollbackDataPolicy = 0;
mHasDeviceAdminReceiver = false;
mIsSdkLibrary = false;
- // @see ParsingPackageUtils#parseBaseAppBasicFlags
- mBackupAllowed = XmlUtils.convertValueToBoolean(archivedPackage.backupAllowed, true);
- mDefaultToDeviceProtectedStorage = XmlUtils.convertValueToBoolean(
- archivedPackage.defaultToDeviceProtectedStorage, false);
- mRequestLegacyExternalStorage = XmlUtils.convertValueToBoolean(
- archivedPackage.requestLegacyExternalStorage,
- mTargetSdkVersion < Build.VERSION_CODES.Q);
- mUserDataFragile = XmlUtils.convertValueToBoolean(archivedPackage.userDataFragile, false);
- mClearUserDataOnFailedRestoreAllowed = XmlUtils.convertValueToBoolean(
- archivedPackage.clearUserDataOnFailedRestoreAllowed, true);
+ mArchivedPackage = archivedPackage;
}
/**
@@ -576,53 +535,18 @@ public class ApkLite {
}
/**
- * Set to <code>false</code> if the application does not wish to permit any OS-driven
- * backups of its data; <code>true</code> otherwise.
- */
- @DataClass.Generated.Member
- public boolean isBackupAllowed() {
- return mBackupAllowed;
- }
-
- /**
- * When set, the default data storage directory for this app is pointed at
- * the device-protected location.
- */
- @DataClass.Generated.Member
- public boolean isDefaultToDeviceProtectedStorage() {
- return mDefaultToDeviceProtectedStorage;
- }
-
- /**
- * If {@code true} this app requests full external storage access.
- */
- @DataClass.Generated.Member
- public boolean isRequestLegacyExternalStorage() {
- return mRequestLegacyExternalStorage;
- }
-
- /**
- * Indicates whether this application has declared its user data as fragile, causing the
- * system to prompt the user on whether to keep the user data on uninstall.
- */
- @DataClass.Generated.Member
- public boolean isUserDataFragile() {
- return mUserDataFragile;
- }
-
- /**
- * Indicates whether this application's data will be cleared on a failed restore.
+ * Archival install info.
*/
@DataClass.Generated.Member
- public boolean isClearUserDataOnFailedRestoreAllowed() {
- return mClearUserDataOnFailedRestoreAllowed;
+ public @Nullable ArchivedPackageParcel getArchivedPackage() {
+ return mArchivedPackage;
}
@DataClass.Generated(
- time = 1693513509013L,
+ time = 1694792109463L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/ApkLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final boolean mBackupAllowed\nprivate final boolean mDefaultToDeviceProtectedStorage\nprivate final boolean mRequestLegacyExternalStorage\nprivate final boolean mUserDataFragile\nprivate final boolean mClearUserDataOnFailedRestoreAllowed\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.Nullable java.lang.String mSplitName\nprivate final @android.annotation.Nullable java.lang.String mUsesSplitName\nprivate final @android.annotation.Nullable java.lang.String mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mRevisionCode\nprivate final int mInstallLocation\nprivate final int mMinSdkVersion\nprivate final int mTargetSdkVersion\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final boolean mFeatureSplit\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mProfileableByShell\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mUseEmbeddedDex\nprivate final @android.annotation.Nullable java.lang.String mTargetPackageName\nprivate final boolean mOverlayIsStatic\nprivate final int mOverlayPriority\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyName\nprivate final @android.annotation.Nullable java.lang.String mRequiredSystemPropertyValue\nprivate final int mRollbackDataPolicy\nprivate final boolean mHasDeviceAdminReceiver\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass ApkLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 066ff6896ac8..5f86742fc562 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -40,7 +40,6 @@ import android.util.Pair;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.XmlUtils;
import libcore.io.IoUtils;
@@ -447,13 +446,6 @@ public class ApkLiteParseUtils {
int overlayPriority = 0;
int rollbackDataPolicy = 0;
- boolean clearUserDataAllowed = true;
- boolean backupAllowed = true;
- boolean defaultToDeviceProtectedStorage = false;
- String requestLegacyExternalStorage = null;
- boolean userDataFragile = false;
- boolean clearUserDataOnFailedRestoreAllowed = true;
-
String requiredSystemPropertyName = null;
String requiredSystemPropertyValue = null;
@@ -493,22 +485,6 @@ public class ApkLiteParseUtils {
useEmbeddedDex = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
"useEmbeddedDex", false);
- clearUserDataAllowed = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
- "allowClearUserDataOnFailedRestore", true);
- backupAllowed = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
- "allowBackup", true);
- defaultToDeviceProtectedStorage = parser.getAttributeBooleanValue(
- ANDROID_RES_NAMESPACE,
- "defaultToDeviceProtectedStorage", false);
- userDataFragile = parser.getAttributeBooleanValue(ANDROID_RES_NAMESPACE,
- "hasFragileUserData", false);
- clearUserDataOnFailedRestoreAllowed = parser.getAttributeBooleanValue(
- ANDROID_RES_NAMESPACE,
- "allowClearUserDataOnFailedRestore", true);
-
- requestLegacyExternalStorage = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
- "requestLegacyExternalStorage");
-
rollbackDataPolicy = parser.getAttributeIntValue(ANDROID_RES_NAMESPACE,
"rollbackDataPolicy", 0);
String permission = parser.getAttributeValue(ANDROID_RES_NAMESPACE,
@@ -629,9 +605,6 @@ public class ApkLiteParseUtils {
return input.skip(message);
}
- boolean isRequestLegacyExternalStorage = XmlUtils.convertValueToBoolean(
- requestLegacyExternalStorage, targetSdkVersion < Build.VERSION_CODES.Q);
-
return input.success(
new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
configForSplit, usesSplitName, isSplitRequired, versionCode,
@@ -641,9 +614,7 @@ public class ApkLiteParseUtils {
overlayIsStatic, overlayPriority, requiredSystemPropertyName,
requiredSystemPropertyValue, minSdkVersion, targetSdkVersion,
rollbackDataPolicy, requiredSplitTypes.first, requiredSplitTypes.second,
- hasDeviceAdminReceiver, isSdkLibrary, clearUserDataAllowed, backupAllowed,
- defaultToDeviceProtectedStorage, isRequestLegacyExternalStorage,
- userDataFragile, clearUserDataOnFailedRestoreAllowed));
+ hasDeviceAdminReceiver, isSdkLibrary));
}
private static boolean isDeviceAdminReceiver(
diff --git a/core/java/android/content/pm/parsing/PackageLite.java b/core/java/android/content/pm/parsing/PackageLite.java
index ccef9def609e..116dd1fc9a42 100644
--- a/core/java/android/content/pm/parsing/PackageLite.java
+++ b/core/java/android/content/pm/parsing/PackageLite.java
@@ -18,6 +18,7 @@ package android.content.pm.parsing;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.PackageInfo;
import android.content.pm.SigningDetails;
import android.content.pm.VerifierInfo;
@@ -112,29 +113,11 @@ public class PackageLite {
* Indicates if this package is a sdk.
*/
private final boolean mIsSdkLibrary;
+
/**
- * Set to <code>false</code> if the application does not wish to permit any OS-driven
- * backups of its data; <code>true</code> otherwise.
- */
- private final boolean mBackupAllowed;
- /**
- * When set, the default data storage directory for this app is pointed at
- * the device-protected location.
- */
- private final boolean mDefaultToDeviceProtectedStorage;
- /**
- * If {@code true} this app requests full external storage access.
- */
- private final boolean mRequestLegacyExternalStorage;
- /**
- * Indicates whether this application has declared its user data as fragile, causing the
- * system to prompt the user on whether to keep the user data on uninstall.
- */
- private final boolean mUserDataFragile;
- /**
- * Indicates whether this application's data will be cleared on a failed restore.
+ * Archival install info.
*/
- private final boolean mClearUserDataOnFailedRestoreAllowed;
+ private final @Nullable ArchivedPackageParcel mArchivedPackage;
public PackageLite(String path, String baseApkPath, ApkLite baseApk,
String[] splitNames, boolean[] isFeatureSplits, String[] usesSplitNames,
@@ -171,11 +154,7 @@ public class PackageLite {
mSplitApkPaths = splitApkPaths;
mSplitRevisionCodes = splitRevisionCodes;
mTargetSdk = targetSdk;
- mBackupAllowed = baseApk.isBackupAllowed();
- mDefaultToDeviceProtectedStorage = baseApk.isDefaultToDeviceProtectedStorage();
- mRequestLegacyExternalStorage = baseApk.isRequestLegacyExternalStorage();
- mUserDataFragile = baseApk.isUserDataFragile();
- mClearUserDataOnFailedRestoreAllowed = baseApk.isClearUserDataOnFailedRestoreAllowed();
+ mArchivedPackage = baseApk.getArchivedPackage();
}
/**
@@ -455,53 +434,18 @@ public class PackageLite {
}
/**
- * Set to <code>false</code> if the application does not wish to permit any OS-driven
- * backups of its data; <code>true</code> otherwise.
- */
- @DataClass.Generated.Member
- public boolean isBackupAllowed() {
- return mBackupAllowed;
- }
-
- /**
- * When set, the default data storage directory for this app is pointed at
- * the device-protected location.
- */
- @DataClass.Generated.Member
- public boolean isDefaultToDeviceProtectedStorage() {
- return mDefaultToDeviceProtectedStorage;
- }
-
- /**
- * If {@code true} this app requests full external storage access.
- */
- @DataClass.Generated.Member
- public boolean isRequestLegacyExternalStorage() {
- return mRequestLegacyExternalStorage;
- }
-
- /**
- * Indicates whether this application has declared its user data as fragile, causing the
- * system to prompt the user on whether to keep the user data on uninstall.
- */
- @DataClass.Generated.Member
- public boolean isUserDataFragile() {
- return mUserDataFragile;
- }
-
- /**
- * Indicates whether this application's data will be cleared on a failed restore.
+ * Archival install info.
*/
@DataClass.Generated.Member
- public boolean isClearUserDataOnFailedRestoreAllowed() {
- return mClearUserDataOnFailedRestoreAllowed;
+ public @Nullable ArchivedPackageParcel getArchivedPackage() {
+ return mArchivedPackage;
}
@DataClass.Generated(
- time = 1693513525097L,
+ time = 1694792176268L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/content/pm/parsing/PackageLite.java",
- inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final boolean mBackupAllowed\nprivate final boolean mDefaultToDeviceProtectedStorage\nprivate final boolean mRequestLegacyExternalStorage\nprivate final boolean mUserDataFragile\nprivate final boolean mClearUserDataOnFailedRestoreAllowed\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
+ inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.lang.String mPath\nprivate final @android.annotation.NonNull java.lang.String mBaseApkPath\nprivate final @android.annotation.Nullable java.lang.String[] mSplitApkPaths\nprivate final @android.annotation.Nullable java.lang.String[] mSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mUsesSplitNames\nprivate final @android.annotation.Nullable java.lang.String[] mConfigForSplit\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String> mBaseRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mRequiredSplitTypes\nprivate final @android.annotation.Nullable java.util.Set<java.lang.String>[] mSplitTypes\nprivate final int mVersionCodeMajor\nprivate final int mVersionCode\nprivate final int mTargetSdk\nprivate final int mBaseRevisionCode\nprivate final @android.annotation.Nullable int[] mSplitRevisionCodes\nprivate final int mInstallLocation\nprivate final @android.annotation.NonNull android.content.pm.VerifierInfo[] mVerifiers\nprivate final @android.annotation.NonNull android.content.pm.SigningDetails mSigningDetails\nprivate final @android.annotation.Nullable boolean[] mIsFeatureSplits\nprivate final boolean mIsolatedSplits\nprivate final boolean mSplitRequired\nprivate final boolean mCoreApp\nprivate final boolean mDebuggable\nprivate final boolean mMultiArch\nprivate final boolean mUse32bitAbi\nprivate final boolean mExtractNativeLibs\nprivate final boolean mProfileableByShell\nprivate final boolean mUseEmbeddedDex\nprivate final boolean mIsSdkLibrary\nprivate final @android.annotation.Nullable android.content.pm.ArchivedPackageParcel mArchivedPackage\npublic java.util.List<java.lang.String> getAllApkPaths()\npublic long getLongVersionCode()\nprivate boolean hasAnyRequiredSplitTypes()\nclass PackageLite extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genConstDefs=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/content/res/OWNERS b/core/java/android/content/res/OWNERS
index a7bce122eb35..141d58d51353 100644
--- a/core/java/android/content/res/OWNERS
+++ b/core/java/android/content/res/OWNERS
@@ -1,8 +1,7 @@
# Bug component: 568761
-toddke@android.com
-toddke@google.com
patb@google.com
zyy@google.com
+branliu@google.com
per-file FontScaleConverter*=fuego@google.com \ No newline at end of file
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index eedb25b1aa8f..408869ec76bc 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -19,6 +19,7 @@ package android.credentials;
import static java.util.Objects.requireNonNull;
import android.annotation.CallbackExecutor;
+import android.annotation.Hide;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -117,6 +118,32 @@ public final class CredentialManager {
}
/**
+ * Returns a list of candidate credentials returned from credential manager providers
+ *
+ * @param request the request specifying type(s) of credentials to get from the
+ * credential providers
+ * @param cancellationSignal an optional signal that allows for cancelling this call
+ * @param executor the callback will take place on this {@link Executor}
+ * @param callback the callback invoked when the request succeeds or fails
+ *
+ * @hide
+ */
+ @Hide
+ public void getCandidateCredentials(
+ @NonNull GetCredentialRequest request,
+ @Nullable CancellationSignal cancellationSignal,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+ requireNonNull(request, "request must not be null");
+ requireNonNull(executor, "executor must not be null");
+ requireNonNull(callback, "callback must not be null");
+
+ if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+ Log.w(TAG, "getCredential already canceled");
+ }
+ }
+
+ /**
* Launches the necessary flows to retrieve an app credential from the user.
*
* <p>The execution can potentially launch UI flows to collect user consent to using a
@@ -641,6 +668,44 @@ public final class CredentialManager {
}
}
+ private static class GetCandidateCredentialsTransport
+ extends IGetCandidateCredentialsCallback.Stub {
+
+ private final Executor mExecutor;
+ private final OutcomeReceiver<GetCandidateCredentialsResponse,
+ GetCandidateCredentialsException> mCallback;
+
+ private GetCandidateCredentialsTransport(
+ Executor executor,
+ OutcomeReceiver<GetCandidateCredentialsResponse,
+ GetCandidateCredentialsException> callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResponse(GetCandidateCredentialsResponse response) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onResult(response));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onError(String errorType, String message) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallback.onError(new GetCandidateCredentialsException(
+ errorType, message)));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
diff --git a/core/java/android/credentials/GetCandidateCredentialsException.java b/core/java/android/credentials/GetCandidateCredentialsException.java
new file mode 100644
index 000000000000..40650d02a93e
--- /dev/null
+++ b/core/java/android/credentials/GetCandidateCredentialsException.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 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 android.credentials;
+
+import android.annotation.Hide;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Represents an error encountered during the
+ * {@link CredentialManager#getCandidateCredentials} operation.
+ *
+ * @hide
+ */
+@Hide
+public class GetCandidateCredentialsException extends Exception {
+ /**
+ * The error type value for when the given operation failed due to an unknown reason.
+ */
+ @NonNull
+ public static final String TYPE_UNKNOWN =
+ "android.credentials.GetCandidateCredentialsException.TYPE_UNKNOWN";
+
+ /**
+ * The error type value for when no credential is found available for the given {@link
+ * CredentialManager#getCandidateCredentials} request.
+ */
+ @NonNull
+ public static final String TYPE_NO_CREDENTIAL =
+ "android.credentials.GetCandidateCredentialsException.TYPE_NO_CREDENTIAL";
+
+ @NonNull
+ private final String mType;
+
+ /** Returns the specific exception type. */
+ @NonNull
+ public String getType() {
+ return mType;
+ }
+
+ /**
+ * Constructs a {@link GetCandidateCredentialsException}.
+ *
+ * @throws IllegalArgumentException If type is empty.
+ */
+ public GetCandidateCredentialsException(@NonNull String type, @Nullable String message) {
+ this(type, message, null);
+ }
+
+ /**
+ * Constructs a {@link GetCandidateCredentialsException}.
+ *
+ * @throws IllegalArgumentException If type is empty.
+ */
+ public GetCandidateCredentialsException(
+ @NonNull String type, @Nullable String message, @Nullable Throwable cause) {
+ super(message, cause);
+ this.mType = Preconditions.checkStringNotEmpty(type,
+ "type must not be empty");
+ }
+
+ /**
+ * Constructs a {@link GetCandidateCredentialsException}.
+ *
+ * @throws IllegalArgumentException If type is empty.
+ */
+ public GetCandidateCredentialsException(@NonNull String type, @Nullable Throwable cause) {
+ this(type, null, cause);
+ }
+
+ /**
+ * Constructs a {@link GetCandidateCredentialsException}.
+ *
+ * @throws IllegalArgumentException If type is empty.
+ */
+ public GetCandidateCredentialsException(@NonNull String type) {
+ this(type, null, null);
+ }
+}
diff --git a/core/java/android/credentials/GetCandidateCredentialsRequest.aidl b/core/java/android/credentials/GetCandidateCredentialsRequest.aidl
new file mode 100644
index 000000000000..d3610894b418
--- /dev/null
+++ b/core/java/android/credentials/GetCandidateCredentialsRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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 android.credentials;
+
+parcelable GetCandidateCredentialsRequest; \ No newline at end of file
diff --git a/core/java/android/credentials/GetCandidateCredentialsRequest.java b/core/java/android/credentials/GetCandidateCredentialsRequest.java
new file mode 100644
index 000000000000..7f0dcaf060b8
--- /dev/null
+++ b/core/java/android/credentials/GetCandidateCredentialsRequest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 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 android.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.Hide;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A request to retrieve a list of candidate credentials against the list of credential
+ * options
+ *
+ * @hide
+ */
+@Hide
+public final class GetCandidateCredentialsRequest implements Parcelable {
+
+ /**
+ * The list of credential requests.
+ */
+ @NonNull
+ private final List<CredentialOption> mCredentialOptions;
+
+ /**
+ * The top request level data.
+ */
+ @NonNull
+ private final Bundle mData;
+
+ /**
+ * The origin of the calling app. Callers of this special API (e.g. browsers)
+ * can set this origin for an app different from their own, to be able to get credentials
+ * on behalf of that app.
+ */
+ @Nullable
+ private String mOrigin;
+
+ /**
+ * Returns the list of credential options to be requested.
+ */
+ @NonNull
+ public List<CredentialOption> getCredentialOptions() {
+ return mCredentialOptions;
+ }
+
+ /**
+ * Returns the top request level data.
+ */
+ @NonNull
+ public Bundle getData() {
+ return mData;
+ }
+
+ /**
+ * Returns the origin of the calling app if set otherwise returns null.
+ */
+ @Nullable
+ public String getOrigin() {
+ return mOrigin;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedList(mCredentialOptions, flags);
+ dest.writeBundle(mData);
+ dest.writeString8(mOrigin);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "GetCandidateCredentialsRequest {credentialOption=" + mCredentialOptions
+ + ", data=" + mData
+ + ", origin=" + mOrigin
+ + "}";
+ }
+
+ private GetCandidateCredentialsRequest(@NonNull List<CredentialOption> credentialOptions,
+ @NonNull Bundle data, String origin) {
+ Preconditions.checkCollectionNotEmpty(
+ credentialOptions,
+ /*valueName=*/ "credentialOptions");
+ Preconditions.checkCollectionElementsNotNull(
+ credentialOptions,
+ /*valueName=*/ "credentialOptions");
+ mCredentialOptions = credentialOptions;
+ mData = requireNonNull(data,
+ "data must not be null");
+ mOrigin = origin;
+ }
+
+ private GetCandidateCredentialsRequest(@NonNull Parcel in) {
+ List<CredentialOption> credentialOptions = new ArrayList<CredentialOption>();
+ in.readTypedList(credentialOptions, CredentialOption.CREATOR);
+ mCredentialOptions = credentialOptions;
+ AnnotationValidations.validate(NonNull.class, null, mCredentialOptions);
+
+ Bundle data = in.readBundle();
+ mData = data;
+ AnnotationValidations.validate(NonNull.class, null, mData);
+
+ mOrigin = in.readString8();
+ }
+
+ @NonNull
+ public static final Creator<GetCandidateCredentialsRequest> CREATOR =
+ new Creator<>() {
+ @Override
+ public GetCandidateCredentialsRequest[] newArray(int size) {
+ return new GetCandidateCredentialsRequest[size];
+ }
+
+ @Override
+ public GetCandidateCredentialsRequest createFromParcel(@NonNull Parcel in) {
+ return new GetCandidateCredentialsRequest(in);
+ }
+ };
+}
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.aidl b/core/java/android/credentials/GetCandidateCredentialsResponse.aidl
new file mode 100644
index 000000000000..ffcd3e7078e8
--- /dev/null
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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 android.credentials;
+
+parcelable GetCandidateCredentialsResponse; \ No newline at end of file
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
new file mode 100644
index 000000000000..1d649eb92fde
--- /dev/null
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 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 android.credentials;
+
+import android.annotation.Hide;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A list of candidate credentials.
+ *
+ * @hide
+ */
+@Hide
+public final class GetCandidateCredentialsResponse implements Parcelable {
+ // TODO(b/299321990): Add members
+ protected GetCandidateCredentialsResponse(Parcel in) {
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator<GetCandidateCredentialsResponse> CREATOR =
+ new Creator<GetCandidateCredentialsResponse>() {
+ @Override
+ public GetCandidateCredentialsResponse createFromParcel(Parcel in) {
+ return new GetCandidateCredentialsResponse(in);
+ }
+
+ @Override
+ public GetCandidateCredentialsResponse[] newArray(int size) {
+ return new GetCandidateCredentialsResponse[size];
+ }
+ };
+}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index dec729f4a19f..42323d66e533 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -21,12 +21,14 @@ import java.util.List;
import android.credentials.CredentialProviderInfo;
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
+import android.credentials.GetCandidateCredentialsRequest;
import android.credentials.GetCredentialRequest;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
+import android.credentials.IGetCandidateCredentialsCallback;
import android.credentials.IPrepareGetCredentialCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.content.ComponentName;
@@ -45,6 +47,8 @@ interface ICredentialManager {
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
+ @nullable ICancellationSignal getCandidateCredentials(in GetCandidateCredentialsRequest request, in IGetCandidateCredentialsCallback callback, String callingPackage);
+
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
void setEnabledProviders(in List<String> primaryProviders, in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
diff --git a/core/java/android/credentials/IGetCandidateCredentialsCallback.aidl b/core/java/android/credentials/IGetCandidateCredentialsCallback.aidl
new file mode 100644
index 000000000000..729176a9919d
--- /dev/null
+++ b/core/java/android/credentials/IGetCandidateCredentialsCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 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 android.credentials;
+
+import android.app.PendingIntent;
+import android.credentials.GetCandidateCredentialsResponse;
+
+/**
+ * Listener for a getCandidateCredentials request.
+ *
+ * @hide
+ */
+interface IGetCandidateCredentialsCallback {
+ oneway void onResponse(in GetCandidateCredentialsResponse response);
+ oneway void onError(String errorType, String message);
+} \ No newline at end of file
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index c3fae55fd00c..88d7231bc7be 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -41,6 +41,7 @@ import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputMonitor;
import android.view.PointerIcon;
+import android.view.KeyCharacterMap;
import android.view.VerifiedInputEvent;
/** @hide */
@@ -63,6 +64,8 @@ interface IInputManager {
// active keyboard layout.
int getKeyCodeForKeyLocation(int deviceId, in int locationKeyCode);
+ KeyCharacterMap getKeyCharacterMap(String layoutDescriptor);
+
// Temporarily changes the pointer speed.
void tryPointerSpeed(int speed);
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index a0cceae98ba9..ff1a6acd8e4e 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,6 +16,8 @@
package android.hardware.input;
+import static com.android.hardware.input.Flags.keyboardLayoutPreviewFlag;
+
import android.Manifest;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -31,6 +33,7 @@ import android.app.ActivityThread;
import android.compat.annotation.ChangeId;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.hardware.BatteryState;
import android.os.Build;
import android.os.Handler;
@@ -931,6 +934,31 @@ public final class InputManager {
}
/**
+ * Provides a Keyboard layout preview of a particular dimension.
+ *
+ * @param keyboardLayout Layout whose preview is requested. If null, will return preview of
+ * the default Keyboard layout defined by {@code Generic.kl}.
+ * @param width Expected width of the drawable
+ * @param height Expected height of the drawable
+ *
+ * NOTE: Width and height will auto-adjust to the width and height of the ImageView that
+ * shows the drawable but this allows the caller to provide an intrinsic width and height of
+ * the drawable allowing the ImageView to properly wrap the drawable content.
+ *
+ * @hide
+ */
+ @Nullable
+ public Drawable getKeyboardLayoutPreview(@Nullable KeyboardLayout keyboardLayout, int width,
+ int height) {
+ if (!keyboardLayoutPreviewFlag()) {
+ return null;
+ }
+ PhysicalKeyLayout keyLayout = new PhysicalKeyLayout(
+ mGlobal.getKeyCharacterMap(keyboardLayout), keyboardLayout);
+ return new KeyboardLayoutPreviewDrawable(mContext, keyLayout, width, height);
+ }
+
+ /**
* Injects an input event into the event system, targeting windows owned by the provided uid.
*
* If a valid targetUid is provided, the system will only consider injecting the input event
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index e886f685263c..8c598aeae67c 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -51,6 +51,7 @@ import android.view.Display;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputMonitor;
+import android.view.KeyCharacterMap;
import android.view.PointerIcon;
import com.android.internal.annotations.GuardedBy;
@@ -1206,6 +1207,21 @@ public final class InputManagerGlobal {
}
/**
+ * Returns KeyCharacterMap for the provided Keyboard layout. If provided layout is null it will
+ * return KeyCharacter map for the default layout {@code Generic.kl}.
+ */
+ public KeyCharacterMap getKeyCharacterMap(@Nullable KeyboardLayout keyboardLayout) {
+ if (keyboardLayout == null) {
+ return KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ }
+ try {
+ return mIm.getKeyCharacterMap(keyboardLayout.getDescriptor());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @see InputManager#injectInputEvent(InputEvent, int, int)
*/
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index 4403251e0488..bbfed24f9dc1 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -22,6 +22,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.Objects;
@@ -230,6 +231,33 @@ public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayo
return mProductId;
}
+ /**
+ * Returns if the Keyboard layout follows the ANSI Physical key layout.
+ */
+ public boolean isAnsiLayout() {
+ for (int i = 0; i < mLocales.size(); i++) {
+ Locale locale = mLocales.get(i);
+ if (locale != null && locale.getCountry().equalsIgnoreCase("us")
+ && mLayoutType != LayoutType.EXTENDED) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns if the Keyboard layout follows the JIS Physical key layout.
+ */
+ public boolean isJisLayout() {
+ for (int i = 0; i < mLocales.size(); i++) {
+ Locale locale = mLocales.get(i);
+ if (locale != null && locale.getCountry().equalsIgnoreCase("jp")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
new file mode 100644
index 000000000000..d943c37e9e5b
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardLayoutPreviewDrawable.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright 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.hardware.input;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A custom drawable class that draws preview of a Physical keyboard layout.
+ */
+final class KeyboardLayoutPreviewDrawable extends Drawable {
+
+ private static final String TAG = "KeyboardLayoutPreview";
+ private static final int GRAVITY_LEFT = 0x1;
+ private static final int GRAVITY_RIGHT = 0x2;
+ private static final int GRAVITY_TOP = 0x4;
+ private static final int GRAVITY_BOTTOM = 0x8;
+ private static final int GRAVITY_CENTER =
+ GRAVITY_LEFT | GRAVITY_RIGHT | GRAVITY_TOP | GRAVITY_BOTTOM;
+ private static final int GRAVITY_CENTER_HORIZONTAL = GRAVITY_LEFT | GRAVITY_RIGHT;
+ private static final int KEY_PADDING_IN_DP = 3;
+ private static final int KEYBOARD_PADDING_IN_DP = 10;
+ private static final int KEY_RADIUS_IN_DP = 5;
+ private static final int KEYBOARD_RADIUS_IN_DP = 10;
+ private static final int GLYPH_TEXT_SIZE_IN_SP = 10;
+
+ private final List<KeyDrawable> mKeyDrawables = new ArrayList<>();
+
+ private final int mWidth;
+ private final int mHeight;
+ private final RectF mKeyboardBackground = new RectF();
+ private final ResourceProvider mResourceProvider;
+ private final PhysicalKeyLayout mKeyLayout;
+
+ public KeyboardLayoutPreviewDrawable(Context context, PhysicalKeyLayout keyLayout, int width,
+ int height) {
+ mWidth = width;
+ mHeight = height;
+ mResourceProvider = new ResourceProvider(context);
+ mKeyLayout = keyLayout;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mHeight;
+ }
+
+ @Override
+ protected void onBoundsChange(@NonNull Rect bounds) {
+ super.onBoundsChange(bounds);
+ mKeyDrawables.clear();
+ final PhysicalKeyLayout.LayoutKey[][] keys = mKeyLayout.getKeys();
+ if (keys == null) {
+ return;
+ }
+ final PhysicalKeyLayout.EnterKey enterKey = mKeyLayout.getEnterKey();
+ int width = bounds.width();
+ int height = bounds.height();
+ final int keyboardPadding = mResourceProvider.getKeyboardPadding();
+ final int keyPadding = mResourceProvider.getKeyPadding();
+ final float keyRadius = mResourceProvider.getKeyRadius();
+ mKeyboardBackground.set(0, 0, width, height);
+ width -= keyboardPadding * 2;
+ height -= keyboardPadding * 2;
+ if (width <= 0 || height <= 0) {
+ Slog.e(TAG, "Invalid width and height to draw layout preview, width = " + width
+ + ", height = " + height);
+ return;
+ }
+ int rowCount = keys.length;
+ float keyHeight = (float) (height - rowCount * 2 * keyPadding) / rowCount;
+ float isoEnterKeyLeft = 0;
+ float isoEnterKeyTop = 0;
+ float isoEnterWidthUnit = 0;
+ for (int i = 0; i < rowCount; i++) {
+ PhysicalKeyLayout.LayoutKey[] row = keys[i];
+ float totalRowWeight = 0;
+ int keysInRow = row.length;
+ for (PhysicalKeyLayout.LayoutKey layoutKey : row) {
+ totalRowWeight += layoutKey.keyWeight();
+ }
+ float keyWidthInPx = (width - keysInRow * 2 * keyPadding) / totalRowWeight;
+ float rowWeightOnLeft = 0;
+ float top = keyboardPadding + keyPadding * (2 * i + 1) + i * keyHeight;
+ for (int j = 0; j < keysInRow; j++) {
+ float left =
+ keyboardPadding + keyPadding * (2 * j + 1) + rowWeightOnLeft * keyWidthInPx;
+ rowWeightOnLeft += row[j].keyWeight();
+ RectF keyRect = new RectF(left, top, left + keyWidthInPx * row[j].keyWeight(),
+ top + keyHeight);
+ if (enterKey != null && row[j].keyCode() == KeyEvent.KEYCODE_ENTER) {
+ if (enterKey.row() == i && enterKey.column() == j) {
+ isoEnterKeyLeft = keyRect.left;
+ isoEnterKeyTop = keyRect.top;
+ isoEnterWidthUnit = keyWidthInPx;
+ }
+ continue;
+ }
+ if (PhysicalKeyLayout.isSpecialKey(row[j])) {
+ mKeyDrawables.add(new TypingKey(null, keyRect, keyRadius,
+ mResourceProvider.getSpecialKeyPaint(),
+ mResourceProvider.getSpecialKeyPaint(),
+ mResourceProvider.getSpecialKeyPaint()));
+ } else if (PhysicalKeyLayout.isKeyPositionUnsure(row[j])) {
+ mKeyDrawables.add(new UnsureTypingKey(row[j].glyph(), keyRect,
+ keyRadius, mResourceProvider.getTypingKeyPaint(),
+ mResourceProvider.getPrimaryGlyphPaint(),
+ mResourceProvider.getSecondaryGlyphPaint()));
+ } else {
+ mKeyDrawables.add(new TypingKey(row[j].glyph(), keyRect, keyRadius,
+ mResourceProvider.getTypingKeyPaint(),
+ mResourceProvider.getPrimaryGlyphPaint(),
+ mResourceProvider.getSecondaryGlyphPaint()));
+ }
+ }
+ }
+ if (enterKey != null) {
+ IsoEnterKey.Builder isoEnterKeyBuilder = new IsoEnterKey.Builder(keyRadius,
+ mResourceProvider.getSpecialKeyPaint());
+ isoEnterKeyBuilder.setTopWidth(enterKey.topKeyWeight() * isoEnterWidthUnit)
+ .setStartPoint(isoEnterKeyLeft, isoEnterKeyTop)
+ .setVerticalEdges(keyHeight, 2 * (keyHeight + keyPadding))
+ .setBottomWidth(enterKey.bottomKeyWeight() * isoEnterWidthUnit);
+ mKeyDrawables.add(isoEnterKeyBuilder.build());
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ final float keyboardRadius = mResourceProvider.getBackgroundRadius();
+ canvas.drawRoundRect(mKeyboardBackground, keyboardRadius, keyboardRadius,
+ mResourceProvider.getBackgroundPaint());
+ for (KeyDrawable key : mKeyDrawables) {
+ key.draw(canvas);
+ }
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ // Do nothing
+ }
+
+ @Override
+ public void setColorFilter(@Nullable ColorFilter colorFilter) {
+ // Do nothing
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.OPAQUE;
+ }
+
+ private static class TypingKey implements KeyDrawable {
+
+ private final RectF mKeyRect;
+ private final float mKeyRadius;
+ private final Paint mKeyPaint;
+ private final Paint mBaseTextPaint;
+ private final Paint mModifierTextPaint;
+ private final List<GlyphDrawable> mGlyphDrawables = new ArrayList<>();
+
+ private TypingKey(@Nullable PhysicalKeyLayout.KeyGlyph glyphData, RectF keyRect,
+ float keyRadius, Paint keyPaint, Paint baseTextPaint, Paint modifierTextPaint) {
+ mKeyRect = keyRect;
+ mKeyRadius = keyRadius;
+ mKeyPaint = keyPaint;
+ mBaseTextPaint = baseTextPaint;
+ mModifierTextPaint = modifierTextPaint;
+ initGlyphs(glyphData);
+ }
+
+ private void initGlyphs(@Nullable PhysicalKeyLayout.KeyGlyph glyphData) {
+ createGlyphs(glyphData);
+ measureGlyphs();
+ }
+
+ private void createGlyphs(@Nullable PhysicalKeyLayout.KeyGlyph glyphData) {
+ if (glyphData == null) {
+ return;
+ }
+ if (!glyphData.hasBaseText()) {
+ return;
+ }
+ if (glyphData.hasValidShiftText() && glyphData.hasValidAltGrText()) {
+ mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
+ GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
+ mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
+ GRAVITY_TOP | GRAVITY_LEFT, mModifierTextPaint));
+ mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
+ GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
+ } else if (glyphData.hasValidShiftText()) {
+ mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
+ GRAVITY_BOTTOM | GRAVITY_CENTER_HORIZONTAL, mBaseTextPaint));
+ mGlyphDrawables.add(new GlyphDrawable(glyphData.getShiftText(), new RectF(),
+ GRAVITY_TOP | GRAVITY_CENTER_HORIZONTAL, mModifierTextPaint));
+ } else if (glyphData.hasValidAltGrText()) {
+ mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
+ GRAVITY_BOTTOM | GRAVITY_LEFT, mBaseTextPaint));
+ mGlyphDrawables.add(new GlyphDrawable(glyphData.getAltGrText(), new RectF(),
+ GRAVITY_BOTTOM | GRAVITY_RIGHT, mModifierTextPaint));
+ } else {
+ mGlyphDrawables.add(new GlyphDrawable(glyphData.getBaseText(), new RectF(),
+ GRAVITY_CENTER, mBaseTextPaint));
+ }
+ }
+
+ private void measureGlyphs() {
+ float keyWidth = mKeyRect.width();
+ float keyHeight = mKeyRect.height();
+ for (GlyphDrawable glyph : mGlyphDrawables) {
+ float centerX = keyWidth / 2;
+ float centerY = keyHeight / 2;
+ if ((glyph.gravity & GRAVITY_LEFT) != 0) {
+ centerX -= keyWidth / 4;
+ }
+ if ((glyph.gravity & GRAVITY_RIGHT) != 0) {
+ centerX += keyWidth / 4;
+ }
+ if ((glyph.gravity & GRAVITY_TOP) != 0) {
+ centerY -= keyHeight / 4;
+ }
+ if ((glyph.gravity & GRAVITY_BOTTOM) != 0) {
+ centerY += keyHeight / 4;
+ }
+ Rect textBounds = new Rect();
+ glyph.paint.getTextBounds(glyph.text, 0, glyph.text.length(), textBounds);
+ float textWidth = textBounds.width();
+ float textHeight = textBounds.height();
+ glyph.rect.set(centerX - textWidth / 2, centerY - textHeight / 2 - textBounds.top,
+ centerX + textWidth / 2, centerY + textHeight / 2 - textBounds.top);
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawRoundRect(mKeyRect, mKeyRadius, mKeyRadius, mKeyPaint);
+ for (GlyphDrawable glyph : mGlyphDrawables) {
+ float textWidth = glyph.rect.width();
+ float textHeight = glyph.rect.height();
+ float keyWidth = mKeyRect.width();
+ float keyHeight = mKeyRect.height();
+ if (textWidth == 0 || textHeight == 0 || keyWidth == 0 || keyHeight == 0) {
+ return;
+ }
+ canvas.drawText(glyph.text, 0, glyph.text.length(), mKeyRect.left + glyph.rect.left,
+ mKeyRect.top + glyph.rect.top, glyph.paint);
+ }
+ }
+ }
+
+ private static class UnsureTypingKey extends TypingKey {
+
+ private UnsureTypingKey(@Nullable PhysicalKeyLayout.KeyGlyph glyphData,
+ RectF keyRect, float keyRadius, Paint keyPaint, Paint baseTextPaint,
+ Paint modifierTextPaint) {
+ super(glyphData, keyRect, keyRadius, createGreyedOutPaint(keyPaint),
+ createGreyedOutPaint(baseTextPaint), createGreyedOutPaint(modifierTextPaint));
+ }
+ }
+
+ private static class IsoEnterKey implements KeyDrawable {
+
+ private final Paint mKeyPaint;
+ private final Path mPath;
+
+ private IsoEnterKey(Paint keyPaint, @NonNull Path path) {
+ mKeyPaint = keyPaint;
+ mPath = path;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawPath(mPath, mKeyPaint);
+ }
+
+ private static class Builder {
+ private final float mKeyRadius;
+ private final Paint mKeyPaint;
+ private float mLeft;
+ private float mTop;
+ private float mTopWidth;
+ private float mBottomWidth;
+ private float mLeftHeight;
+ private float mRightHeight;
+
+ private Builder(float keyRadius, Paint keyPaint) {
+ mKeyRadius = keyRadius;
+ mKeyPaint = keyPaint;
+ }
+
+ private Builder setStartPoint(float left, float top) {
+ mLeft = left;
+ mTop = top;
+ return this;
+ }
+
+ private Builder setTopWidth(float width) {
+ mTopWidth = width;
+ return this;
+ }
+
+ private Builder setBottomWidth(float width) {
+ mBottomWidth = width;
+ return this;
+ }
+
+ private Builder setVerticalEdges(float leftHeight, float rightHeight) {
+ mLeftHeight = leftHeight;
+ mRightHeight = rightHeight;
+ return this;
+ }
+
+ private IsoEnterKey build() {
+ Path enterKey = new Path();
+ RectF oval = new RectF(-mKeyRadius, -mKeyRadius, mKeyRadius, mKeyRadius);
+ // Horizontal top line
+ enterKey.moveTo(mLeft + mKeyRadius, mTop);
+ enterKey.lineTo(mLeft + mTopWidth - mKeyRadius, mTop);
+ // Rounded top right corner
+ oval.offsetTo(mLeft + mTopWidth - 2 * mKeyRadius, mTop);
+ enterKey.arcTo(oval, 270, 90);
+ // Vertical right line
+ enterKey.lineTo(mLeft + mTopWidth, mTop + mRightHeight - mKeyRadius);
+ // Rounded bottom right corner
+ oval.offsetTo(mLeft + mTopWidth - 2 * mKeyRadius,
+ mTop + mRightHeight - 2 * mKeyRadius);
+ enterKey.arcTo(oval, 0, 90);
+ // Horizontal bottom line
+ enterKey.lineTo(mLeft + mTopWidth - mBottomWidth + mKeyRadius, mTop + mRightHeight);
+ // Rounded bottom left corner
+ oval.offsetTo(mLeft + mTopWidth - mBottomWidth,
+ mTop + mRightHeight - 2 * mKeyRadius);
+ enterKey.arcTo(oval, 90, 90);
+ // Vertical left line (bottom half)
+ enterKey.lineTo(mLeft + mTopWidth - mBottomWidth, mTop + mLeftHeight - mKeyRadius);
+ // Rounded corner
+ oval.offsetTo(mLeft + mTopWidth - mBottomWidth - 2 * mKeyRadius,
+ mTop + mLeftHeight);
+ enterKey.arcTo(oval, 0, -90);
+ // Horizontal line in the mid part
+ enterKey.lineTo(mLeft + mKeyRadius, mTop + mLeftHeight);
+ // Rounded corner
+ oval.offsetTo(mLeft, mTop + mLeftHeight - 2 * mKeyRadius);
+ enterKey.arcTo(oval, 90, 90);
+ // Vertical left line (top half)
+ enterKey.lineTo(mLeft, mTop + mKeyRadius);
+ // Rounded top left corner
+ oval.offsetTo(mLeft, mTop);
+ enterKey.arcTo(oval, 180, 90);
+ enterKey.close();
+ return new IsoEnterKey(mKeyPaint, enterKey);
+ }
+ }
+ }
+
+ private record GlyphDrawable(String text, RectF rect, int gravity, Paint paint) {}
+
+ private interface KeyDrawable {
+ void draw(Canvas canvas);
+ }
+
+ private static class ResourceProvider {
+ // Resources
+ private final Paint mBackgroundPaint;
+ private final Paint mTypingKeyPaint;
+ private final Paint mSpecialKeyPaint;
+ private final Paint mPrimaryGlyphPaint;
+ private final Paint mSecondaryGlyphPaint;
+ private final int mKeyPadding;
+ private final int mKeyboardPadding;
+ private final float mKeyRadius;
+ private final float mBackgroundRadius;
+
+ private ResourceProvider(Context context) {
+ mKeyPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ KEY_PADDING_IN_DP, context.getResources().getDisplayMetrics());
+ mKeyboardPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ KEYBOARD_PADDING_IN_DP, context.getResources().getDisplayMetrics());
+ mKeyRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ KEY_RADIUS_IN_DP, context.getResources().getDisplayMetrics());
+ mBackgroundRadius = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ KEYBOARD_RADIUS_IN_DP, context.getResources().getDisplayMetrics());
+ int textSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+ GLYPH_TEXT_SIZE_IN_SP, context.getResources().getDisplayMetrics());
+ boolean isDark = (context.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+ int typingKeyColor = context.getColor(
+ isDark ? android.R.color.system_outline_variant_dark
+ : android.R.color.system_surface_container_lowest_light);
+ int specialKeyColor = context.getColor(isDark ? android.R.color.system_neutral1_800
+ : android.R.color.system_secondary_container_light);
+ int primaryGlyphColor = context.getColor(isDark ? android.R.color.system_on_surface_dark
+ : android.R.color.system_on_surface_light);
+ int secondaryGlyphColor = context.getColor(isDark ? android.R.color.system_outline_dark
+ : android.R.color.system_outline_light);
+ int backgroundColor = context.getColor(
+ isDark ? android.R.color.system_surface_container_dark
+ : android.R.color.system_surface_container_light);
+ mPrimaryGlyphPaint = createTextPaint(primaryGlyphColor, textSize,
+ Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
+ mSecondaryGlyphPaint = createTextPaint(secondaryGlyphColor, textSize,
+ Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL));
+ mTypingKeyPaint = createFillPaint(typingKeyColor);
+ mSpecialKeyPaint = createFillPaint(specialKeyColor);
+ mBackgroundPaint = createFillPaint(backgroundColor);
+ }
+
+ private Paint getBackgroundPaint() {
+ return mBackgroundPaint;
+ }
+
+ private Paint getTypingKeyPaint() {
+ return mTypingKeyPaint;
+ }
+
+ private Paint getSpecialKeyPaint() {
+ return mSpecialKeyPaint;
+ }
+
+ private Paint getPrimaryGlyphPaint() {
+ return mPrimaryGlyphPaint;
+ }
+
+ private Paint getSecondaryGlyphPaint() {
+ return mSecondaryGlyphPaint;
+ }
+
+ private int getKeyPadding() {
+ return mKeyPadding;
+ }
+
+ private int getKeyboardPadding() {
+ return mKeyboardPadding;
+ }
+
+ private float getKeyRadius() {
+ return mKeyRadius;
+ }
+
+ private float getBackgroundRadius() {
+ return mBackgroundRadius;
+ }
+ }
+
+ private static Paint createTextPaint(@ColorInt int textColor, int textSize, Typeface typeface) {
+ Paint paint = new Paint();
+ paint.setColor(textColor);
+ paint.setStyle(Paint.Style.FILL);
+ paint.setTextSize(textSize);
+ paint.setTypeface(typeface);
+ return paint;
+ }
+
+ private static Paint createFillPaint(@ColorInt int color) {
+ Paint paint = new Paint();
+ paint.setColor(color);
+ paint.setStyle(Paint.Style.FILL);
+ return paint;
+ }
+
+ private static Paint createGreyedOutPaint(Paint paint) {
+ Paint result = new Paint(paint);
+ result.setAlpha(100);
+ return result;
+ }
+}
diff --git a/core/java/android/hardware/input/PhysicalKeyLayout.java b/core/java/android/hardware/input/PhysicalKeyLayout.java
new file mode 100644
index 000000000000..241c452a75eb
--- /dev/null
+++ b/core/java/android/hardware/input/PhysicalKeyLayout.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright 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.hardware.input;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.SparseIntArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+import java.util.Locale;
+
+/**
+ * A complimentary class to {@link KeyboardLayoutPreviewDrawable} describing the physical key layout
+ * of a Physical keyboard and provides information regarding the scan codes produced by the physical
+ * keys.
+ */
+final class PhysicalKeyLayout {
+
+ private static final String TAG = "KeyboardLayoutPreview";
+ private static final int SCANCODE_1 = 2;
+ private static final int SCANCODE_2 = 3;
+ private static final int SCANCODE_3 = 4;
+ private static final int SCANCODE_4 = 5;
+ private static final int SCANCODE_5 = 6;
+ private static final int SCANCODE_6 = 7;
+ private static final int SCANCODE_7 = 8;
+ private static final int SCANCODE_8 = 9;
+ private static final int SCANCODE_9 = 10;
+ private static final int SCANCODE_0 = 11;
+ private static final int SCANCODE_MINUS = 12;
+ private static final int SCANCODE_EQUALS = 13;
+ private static final int SCANCODE_Q = 16;
+ private static final int SCANCODE_W = 17;
+ private static final int SCANCODE_E = 18;
+ private static final int SCANCODE_R = 19;
+ private static final int SCANCODE_T = 20;
+ private static final int SCANCODE_Y = 21;
+ private static final int SCANCODE_U = 22;
+ private static final int SCANCODE_I = 23;
+ private static final int SCANCODE_O = 24;
+ private static final int SCANCODE_P = 25;
+ private static final int SCANCODE_LEFT_BRACKET = 26;
+ private static final int SCANCODE_RIGHT_BRACKET = 27;
+ private static final int SCANCODE_A = 30;
+ private static final int SCANCODE_S = 31;
+ private static final int SCANCODE_D = 32;
+ private static final int SCANCODE_F = 33;
+ private static final int SCANCODE_G = 34;
+ private static final int SCANCODE_H = 35;
+ private static final int SCANCODE_J = 36;
+ private static final int SCANCODE_K = 37;
+ private static final int SCANCODE_L = 38;
+ private static final int SCANCODE_SEMICOLON = 39;
+ private static final int SCANCODE_APOSTROPHE = 40;
+ private static final int SCANCODE_GRAVE = 41;
+ private static final int SCANCODE_BACKSLASH1 = 43;
+ private static final int SCANCODE_Z = 44;
+ private static final int SCANCODE_X = 45;
+ private static final int SCANCODE_C = 46;
+ private static final int SCANCODE_V = 47;
+ private static final int SCANCODE_B = 48;
+ private static final int SCANCODE_N = 49;
+ private static final int SCANCODE_M = 50;
+ private static final int SCANCODE_COMMA = 51;
+ private static final int SCANCODE_PERIOD = 52;
+ private static final int SCANCODE_SLASH = 53;
+ private static final int SCANCODE_BACKSLASH2 = 86;
+ private static final int SCANCODE_YEN = 124;
+
+ private static final SparseIntArray DEFAULT_KEYCODE_FOR_SCANCODE = new SparseIntArray();
+
+ static {
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_1, KeyEvent.KEYCODE_1);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_2, KeyEvent.KEYCODE_2);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_3, KeyEvent.KEYCODE_3);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_4, KeyEvent.KEYCODE_4);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_5, KeyEvent.KEYCODE_5);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_6, KeyEvent.KEYCODE_6);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_7, KeyEvent.KEYCODE_7);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_8, KeyEvent.KEYCODE_8);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_9, KeyEvent.KEYCODE_9);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_0, KeyEvent.KEYCODE_0);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_MINUS, KeyEvent.KEYCODE_MINUS);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_EQUALS, KeyEvent.KEYCODE_EQUALS);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_Q, KeyEvent.KEYCODE_Q);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_W, KeyEvent.KEYCODE_W);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_E, KeyEvent.KEYCODE_E);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_R, KeyEvent.KEYCODE_R);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_T, KeyEvent.KEYCODE_T);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_Y, KeyEvent.KEYCODE_Y);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_U, KeyEvent.KEYCODE_U);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_I, KeyEvent.KEYCODE_I);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_O, KeyEvent.KEYCODE_O);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_P, KeyEvent.KEYCODE_P);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_LEFT_BRACKET, KeyEvent.KEYCODE_LEFT_BRACKET);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_RIGHT_BRACKET, KeyEvent.KEYCODE_RIGHT_BRACKET);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_A, KeyEvent.KEYCODE_A);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_S, KeyEvent.KEYCODE_S);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_D, KeyEvent.KEYCODE_D);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_F, KeyEvent.KEYCODE_F);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_G, KeyEvent.KEYCODE_G);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_H, KeyEvent.KEYCODE_H);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_J, KeyEvent.KEYCODE_J);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_K, KeyEvent.KEYCODE_K);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_L, KeyEvent.KEYCODE_L);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_SEMICOLON, KeyEvent.KEYCODE_SEMICOLON);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_APOSTROPHE, KeyEvent.KEYCODE_APOSTROPHE);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_GRAVE, KeyEvent.KEYCODE_GRAVE);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_BACKSLASH1, KeyEvent.KEYCODE_BACKSLASH);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_Z, KeyEvent.KEYCODE_Z);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_X, KeyEvent.KEYCODE_X);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_C, KeyEvent.KEYCODE_C);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_V, KeyEvent.KEYCODE_V);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_B, KeyEvent.KEYCODE_B);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_N, KeyEvent.KEYCODE_N);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_M, KeyEvent.KEYCODE_M);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_COMMA, KeyEvent.KEYCODE_COMMA);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_PERIOD, KeyEvent.KEYCODE_PERIOD);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_SLASH, KeyEvent.KEYCODE_SLASH);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_BACKSLASH2, KeyEvent.KEYCODE_BACKSLASH);
+ DEFAULT_KEYCODE_FOR_SCANCODE.put(SCANCODE_YEN, KeyEvent.KEYCODE_YEN);
+ }
+
+ private LayoutKey[][] mKeys = null;
+ private EnterKey mEnterKey = null;
+
+ public PhysicalKeyLayout(@NonNull KeyCharacterMap kcm, @Nullable KeyboardLayout layout) {
+ initLayoutKeys(kcm, layout);
+ }
+
+ private void initLayoutKeys(KeyCharacterMap kcm, KeyboardLayout layout) {
+ if (layout == null) {
+ createIsoLayout(kcm);
+ return;
+ }
+ if (layout.isAnsiLayout()) {
+ createAnsiLayout(kcm);
+ } else if (layout.isJisLayout()) {
+ createJisLayout(kcm);
+ } else {
+ createIsoLayout(kcm);
+ }
+ }
+
+ public LayoutKey[][] getKeys() {
+ return mKeys;
+ }
+
+ /**
+ * @return Special enter key (if required) that can span multiple rows like ISO enter key.
+ */
+ @Nullable
+ public EnterKey getEnterKey() {
+ return mEnterKey;
+ }
+
+ private void createAnsiLayout(KeyCharacterMap kcm) {
+ mKeys = new LayoutKey[][]{
+ {
+ getKey(kcm, SCANCODE_GRAVE), getKey(kcm, SCANCODE_1),
+ getKey(kcm, SCANCODE_2), getKey(kcm, SCANCODE_3), getKey(kcm, SCANCODE_4),
+ getKey(kcm, SCANCODE_5), getKey(kcm, SCANCODE_6), getKey(kcm, SCANCODE_7),
+ getKey(kcm, SCANCODE_8), getKey(kcm, SCANCODE_9), getKey(kcm, SCANCODE_0),
+ getKey(kcm, SCANCODE_MINUS), getKey(kcm, SCANCODE_EQUALS),
+ getKey(KeyEvent.KEYCODE_DEL, 1.5F)
+ },
+ {
+ getKey(KeyEvent.KEYCODE_TAB, 1.5F), getKey(kcm, SCANCODE_Q),
+ getKey(kcm, SCANCODE_W), getKey(kcm, SCANCODE_E), getKey(kcm, SCANCODE_R),
+ getKey(kcm, SCANCODE_T), getKey(kcm, SCANCODE_Y), getKey(kcm, SCANCODE_U),
+ getKey(kcm, SCANCODE_I), getKey(kcm, SCANCODE_O), getKey(kcm, SCANCODE_P),
+ getKey(kcm, SCANCODE_LEFT_BRACKET), getKey(kcm, SCANCODE_RIGHT_BRACKET),
+ getKey(kcm, SCANCODE_BACKSLASH1)
+ },
+ {
+ getKey(KeyEvent.KEYCODE_CAPS_LOCK, 1.75F),
+ getKey(kcm, SCANCODE_A), getKey(kcm, SCANCODE_S), getKey(kcm, SCANCODE_D),
+ getKey(kcm, SCANCODE_F), getKey(kcm, SCANCODE_G), getKey(kcm, SCANCODE_H),
+ getKey(kcm, SCANCODE_J), getKey(kcm, SCANCODE_K), getKey(kcm, SCANCODE_L),
+ getKey(kcm, SCANCODE_SEMICOLON), getKey(kcm, SCANCODE_APOSTROPHE),
+ getKey(KeyEvent.KEYCODE_ENTER, 1.75F)
+ },
+ {
+ getKey(KeyEvent.KEYCODE_SHIFT_LEFT, 2.5F),
+ getKey(kcm, SCANCODE_Z), getKey(kcm, SCANCODE_X), getKey(kcm, SCANCODE_C),
+ getKey(kcm, SCANCODE_V), getKey(kcm, SCANCODE_B), getKey(kcm, SCANCODE_N),
+ getKey(kcm, SCANCODE_M), getKey(kcm, SCANCODE_COMMA),
+ getKey(kcm, SCANCODE_PERIOD), getKey(kcm, SCANCODE_SLASH),
+ getKey(KeyEvent.KEYCODE_SHIFT_RIGHT, 2.5F),
+ },
+ {
+ getKey(KeyEvent.KEYCODE_CTRL_LEFT, 1.0F),
+ getKey(KeyEvent.KEYCODE_FUNCTION, 1.0F),
+ getKey(KeyEvent.KEYCODE_META_LEFT, 1.0F),
+ getKey(KeyEvent.KEYCODE_ALT_LEFT, 1.0F),
+ getKey(KeyEvent.KEYCODE_SPACE, 6.5F),
+ getKey(KeyEvent.KEYCODE_ALT_RIGHT, 1.0F),
+ getKey(KeyEvent.KEYCODE_META_RIGHT, 1.0F),
+ getKey(KeyEvent.KEYCODE_MENU, 1.0F),
+ getKey(KeyEvent.KEYCODE_CTRL_RIGHT, 1.0F),
+ }
+ };
+ }
+
+ private void createIsoLayout(KeyCharacterMap kcm) {
+ mKeys = new LayoutKey[][]{
+ {
+ getKey(kcm, SCANCODE_GRAVE), getKey(kcm, SCANCODE_1),
+ getKey(kcm, SCANCODE_2), getKey(kcm, SCANCODE_3), getKey(kcm, SCANCODE_4),
+ getKey(kcm, SCANCODE_5), getKey(kcm, SCANCODE_6), getKey(kcm, SCANCODE_7),
+ getKey(kcm, SCANCODE_8), getKey(kcm, SCANCODE_9), getKey(kcm, SCANCODE_0),
+ getKey(kcm, SCANCODE_MINUS), getKey(kcm, SCANCODE_EQUALS),
+ getKey(KeyEvent.KEYCODE_DEL, 1.5F)
+ },
+ {
+ getKey(KeyEvent.KEYCODE_TAB, 1.15F), getKey(kcm, SCANCODE_Q),
+ getKey(kcm, SCANCODE_W), getKey(kcm, SCANCODE_E), getKey(kcm, SCANCODE_R),
+ getKey(kcm, SCANCODE_T), getKey(kcm, SCANCODE_Y), getKey(kcm, SCANCODE_U),
+ getKey(kcm, SCANCODE_I), getKey(kcm, SCANCODE_O), getKey(kcm, SCANCODE_P),
+ getKey(kcm, SCANCODE_LEFT_BRACKET), getKey(kcm, SCANCODE_RIGHT_BRACKET),
+ getKey(KeyEvent.KEYCODE_ENTER, 1.35F)
+ },
+ {
+ getKey(KeyEvent.KEYCODE_TAB, 1.5F), getKey(kcm, SCANCODE_A),
+ getKey(kcm, SCANCODE_S), getKey(kcm, SCANCODE_D), getKey(kcm, SCANCODE_F),
+ getKey(kcm, SCANCODE_G), getKey(kcm, SCANCODE_H), getKey(kcm, SCANCODE_J),
+ getKey(kcm, SCANCODE_K), getKey(kcm, SCANCODE_L),
+ getKey(kcm, SCANCODE_SEMICOLON), getKey(kcm, SCANCODE_APOSTROPHE),
+ getKey(kcm, SCANCODE_BACKSLASH1),
+ getKey(KeyEvent.KEYCODE_ENTER, 1.0F)
+ },
+ {
+ getKey(KeyEvent.KEYCODE_SHIFT_LEFT, 1.15F),
+ getKey(kcm, SCANCODE_BACKSLASH2), getKey(kcm, SCANCODE_Z),
+ getKey(kcm, SCANCODE_X), getKey(kcm, SCANCODE_C), getKey(kcm, SCANCODE_V),
+ getKey(kcm, SCANCODE_B), getKey(kcm, SCANCODE_N), getKey(kcm, SCANCODE_M),
+ getKey(kcm, SCANCODE_COMMA), getKey(kcm, SCANCODE_PERIOD),
+ getKey(kcm, SCANCODE_SLASH),
+ getKey(KeyEvent.KEYCODE_SHIFT_RIGHT, 2.35F)
+ },
+ {
+ getKey(KeyEvent.KEYCODE_CTRL_LEFT, 1.0F),
+ getKey(KeyEvent.KEYCODE_FUNCTION, 1.0F),
+ getKey(KeyEvent.KEYCODE_META_LEFT, 1.0F),
+ getKey(KeyEvent.KEYCODE_ALT_LEFT, 1.0F),
+ getKey(KeyEvent.KEYCODE_SPACE, 6.5F),
+ getKey(KeyEvent.KEYCODE_ALT_RIGHT, 1.0F),
+ getKey(KeyEvent.KEYCODE_META_RIGHT, 1.0F),
+ getKey(KeyEvent.KEYCODE_MENU, 1.0F),
+ getKey(KeyEvent.KEYCODE_CTRL_RIGHT, 1.0F),
+ }
+ };
+ mEnterKey = new EnterKey(1, 13, 1.35F, 1.0F);
+ }
+
+ private void createJisLayout(KeyCharacterMap kcm) {
+ mKeys = new LayoutKey[][]{
+ {
+ getKey(kcm, SCANCODE_GRAVE), getKey(kcm, SCANCODE_1),
+ getKey(kcm, SCANCODE_2), getKey(kcm, SCANCODE_3), getKey(kcm, SCANCODE_4),
+ getKey(kcm, SCANCODE_5), getKey(kcm, SCANCODE_6), getKey(kcm, SCANCODE_7),
+ getKey(kcm, SCANCODE_8), getKey(kcm, SCANCODE_9), getKey(kcm, SCANCODE_0),
+ getKey(kcm, SCANCODE_MINUS, 0.8F), getKey(kcm, SCANCODE_EQUALS, 0.8f),
+ getKey(kcm, SCANCODE_YEN, 0.8f), getKey(KeyEvent.KEYCODE_DEL, 1.1F)
+ },
+ {
+ getKey(KeyEvent.KEYCODE_TAB, 1.15F), getKey(kcm, SCANCODE_Q),
+ getKey(kcm, SCANCODE_W), getKey(kcm, SCANCODE_E), getKey(kcm, SCANCODE_R),
+ getKey(kcm, SCANCODE_T), getKey(kcm, SCANCODE_Y), getKey(kcm, SCANCODE_U),
+ getKey(kcm, SCANCODE_I), getKey(kcm, SCANCODE_O), getKey(kcm, SCANCODE_P),
+ getKey(kcm, SCANCODE_LEFT_BRACKET), getKey(kcm, SCANCODE_RIGHT_BRACKET),
+ getKey(KeyEvent.KEYCODE_ENTER, 1.35F)
+ },
+ {
+ getKey(KeyEvent.KEYCODE_TAB, 1.5F), getKey(kcm, SCANCODE_A),
+ getKey(kcm, SCANCODE_S), getKey(kcm, SCANCODE_D), getKey(kcm, SCANCODE_F),
+ getKey(kcm, SCANCODE_G), getKey(kcm, SCANCODE_H), getKey(kcm, SCANCODE_J),
+ getKey(kcm, SCANCODE_K), getKey(kcm, SCANCODE_L),
+ getKey(kcm, SCANCODE_SEMICOLON), getKey(kcm, SCANCODE_APOSTROPHE),
+ getKey(kcm, SCANCODE_BACKSLASH2),
+ getKey(KeyEvent.KEYCODE_ENTER, 1.0F)
+ },
+ {
+ getKey(KeyEvent.KEYCODE_SHIFT_LEFT, 1.15F),
+ getKey(kcm, SCANCODE_Z), getKey(kcm, SCANCODE_X), getKey(kcm, SCANCODE_C),
+ getKey(kcm, SCANCODE_V), getKey(kcm, SCANCODE_B), getKey(kcm, SCANCODE_N),
+ getKey(kcm, SCANCODE_M), getKey(kcm, SCANCODE_COMMA),
+ getKey(kcm, SCANCODE_PERIOD), getKey(kcm, SCANCODE_SLASH),
+ getKey(kcm, SCANCODE_BACKSLASH1),
+ getKey(KeyEvent.KEYCODE_SHIFT_RIGHT, 2.35F)
+ },
+ {
+ getKey(KeyEvent.KEYCODE_CTRL_LEFT, 1.0F),
+ getKey(KeyEvent.KEYCODE_FUNCTION, 1.0F),
+ getKey(KeyEvent.KEYCODE_META_LEFT, 1.0F),
+ getKey(KeyEvent.KEYCODE_ALT_LEFT, 1.0F),
+ getKey(KeyEvent.KEYCODE_UNKNOWN, 1.0F),
+ getKey(KeyEvent.KEYCODE_SPACE, 3.5F),
+ getKey(KeyEvent.KEYCODE_UNKNOWN, 1.0F),
+ getKey(KeyEvent.KEYCODE_UNKNOWN, 1.0F),
+ getKey(KeyEvent.KEYCODE_ALT_RIGHT, 1.0F),
+ getKey(KeyEvent.KEYCODE_META_RIGHT, 1.0F),
+ getKey(KeyEvent.KEYCODE_MENU, 1.0F),
+ getKey(KeyEvent.KEYCODE_CTRL_RIGHT, 1.0F),
+ }
+ };
+ mEnterKey = new EnterKey(1, 13, 1.35F, 1.0F);
+ }
+
+ private static LayoutKey getKey(KeyCharacterMap kcm, int scanCode, float keyWeight) {
+ int keyCode = kcm.getMappedKeyOrDefault(scanCode,
+ DEFAULT_KEYCODE_FOR_SCANCODE.get(scanCode, KeyEvent.KEYCODE_UNKNOWN));
+ return new LayoutKey(keyCode, scanCode, keyWeight, new KeyGlyph(kcm, keyCode));
+ }
+
+ private static LayoutKey getKey(KeyCharacterMap kcm, int scanCode) {
+ return getKey(kcm, scanCode, 1.0F);
+ }
+
+ private static String getKeyText(KeyCharacterMap kcm, int keyCode, int modifierState) {
+ if (isSpecialKey(keyCode)) {
+ return "";
+ }
+ int utf8Char = (kcm.get(keyCode, modifierState) & KeyCharacterMap.COMBINING_ACCENT_MASK);
+ if (Character.isValidCodePoint(utf8Char)) {
+ return String.valueOf(Character.toChars(utf8Char)).toUpperCase(Locale.getDefault());
+ } else {
+ return String.valueOf(kcm.getDisplayLabel(keyCode)).toUpperCase(Locale.getDefault());
+ }
+ }
+
+ private static LayoutKey getKey(int keyCode, float keyWeight) {
+ return new LayoutKey(keyCode, keyCode, keyWeight, null);
+ }
+
+ /**
+ * Util function that tells if a key corresponds to a special key which are keys on a Physical
+ * layout that perform some special action like modifier keys, enter key, space key, character
+ * set changing keys, etc.
+ */
+ private static boolean isSpecialKey(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DEL:
+ case KeyEvent.KEYCODE_TAB:
+ case KeyEvent.KEYCODE_CAPS_LOCK:
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_SHIFT_LEFT:
+ case KeyEvent.KEYCODE_SHIFT_RIGHT:
+ case KeyEvent.KEYCODE_CTRL_LEFT:
+ case KeyEvent.KEYCODE_CTRL_RIGHT:
+ case KeyEvent.KEYCODE_FUNCTION:
+ case KeyEvent.KEYCODE_ALT_LEFT:
+ case KeyEvent.KEYCODE_ALT_RIGHT:
+ case KeyEvent.KEYCODE_META_LEFT:
+ case KeyEvent.KEYCODE_META_RIGHT:
+ case KeyEvent.KEYCODE_SPACE:
+ case KeyEvent.KEYCODE_MENU:
+ case KeyEvent.KEYCODE_UNKNOWN:
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean isSpecialKey(LayoutKey key) {
+ return isSpecialKey(key.keyCode);
+ }
+
+ public static boolean isKeyPositionUnsure(LayoutKey key) {
+ switch (key.scanCode) {
+ case SCANCODE_GRAVE:
+ case SCANCODE_BACKSLASH1:
+ case SCANCODE_BACKSLASH2:
+ return true;
+ }
+ return false;
+ }
+
+ public record LayoutKey(int keyCode, int scanCode, float keyWeight, KeyGlyph glyph) {}
+ public record EnterKey(int row, int column, float topKeyWeight, float bottomKeyWeight) {}
+
+ public static class KeyGlyph {
+ private final String mBaseText;
+ private final String mShiftText;
+ private final String mAltGrText;
+
+ public KeyGlyph(KeyCharacterMap kcm, int keyCode) {
+ mBaseText = getKeyText(kcm, keyCode, 0);
+ mShiftText = getKeyText(kcm, keyCode,
+ KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON);
+ mAltGrText = getKeyText(kcm, keyCode,
+ KeyEvent.META_ALT_ON | KeyEvent.META_ALT_RIGHT_ON);
+ }
+
+ public String getBaseText() {
+ return mBaseText;
+ }
+
+ public String getShiftText() {
+ return mShiftText;
+ }
+
+ public String getAltGrText() {
+ return mAltGrText;
+ }
+
+ public boolean hasBaseText() {
+ return !TextUtils.isEmpty(mBaseText);
+ }
+
+ public boolean hasValidShiftText() {
+ return !TextUtils.isEmpty(mShiftText) && !TextUtils.equals(mBaseText, mShiftText);
+ }
+
+ public boolean hasValidAltGrText() {
+ return !TextUtils.isEmpty(mAltGrText) && !TextUtils.equals(mBaseText, mAltGrText);
+ }
+ }
+}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 62d9c69565da..c527cb5344c1 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppGlobals;
@@ -1953,6 +1954,23 @@ public final class Debug
*/
public static native long getPss(int pid, long[] outUssSwapPssRss, long[] outMemtrack);
+ /**
+ * Retrieves the RSS memory used by the process as given by the status file.
+ */
+ @FlaggedApi(Flags.FLAG_REMOVE_APP_PROFILER_PSS_COLLECTION)
+ public static native long getRss();
+
+ /**
+ * Retrieves the RSS memory used by the process as given by the status file. Optionally supply a
+ * long array of up to 4 entries to retrieve the total memtrack reported size, memtrack
+ * graphics, memtrack gl, and memtrack other.
+ *
+ * @return The RSS memory usage, or 0 if retrieval failed (i.e. the PID is gone).
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_REMOVE_APP_PROFILER_PSS_COLLECTION)
+ public static native long getRss(int pid, long[] outMemtrack);
+
/** @hide */
public static final int MEMINFO_TOTAL = 0;
/** @hide */
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
new file mode 100644
index 000000000000..77be5d400853
--- /dev/null
+++ b/core/java/android/permission/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.permission.flags"
+
+flag {
+ name: "device_aware_permission_apis"
+ namespace: "permissions"
+ description: "enable device aware permission APIs"
+ bug: "274852670"
+}
diff --git a/core/java/android/security/TEST_MAPPING b/core/java/android/security/TEST_MAPPING
index 7e43381ee6a9..5a679b1a2bf7 100644
--- a/core/java/android/security/TEST_MAPPING
+++ b/core/java/android/security/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "CtsSecurityTestCases",
"options": [
diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java
index 00c30b12c93d..83f96629a582 100644
--- a/core/java/android/service/autofill/AutofillServiceInfo.java
+++ b/core/java/android/service/autofill/AutofillServiceInfo.java
@@ -63,6 +63,10 @@ public final class AutofillServiceInfo {
private static final String TAG_AUTOFILL_SERVICE = "autofill-service";
private static final String TAG_COMPATIBILITY_PACKAGE = "compatibility-package";
+ private static final ComponentName CREDMAN_SERVICE_COMPONENT_NAME =
+ new ComponentName("com.android.credentialmanager",
+ "com.android.credentialmanager.autofill.CredentialAutofillService");
+
private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, int userHandle)
throws PackageManager.NameNotFoundException {
try {
@@ -307,6 +311,11 @@ public final class AutofillServiceInfo {
for (ResolveInfo resolveInfo : resolveInfos) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
try {
+ if (serviceInfo != null && isCredentialManagerAutofillService(
+ serviceInfo.getComponentName())) {
+ // Skip this service as it is for internal use only
+ continue;
+ }
services.add(new AutofillServiceInfo(context, serviceInfo));
} catch (SecurityException e) {
// Service does not declare the proper permission, ignore it.
@@ -316,6 +325,13 @@ public final class AutofillServiceInfo {
return services;
}
+ private static boolean isCredentialManagerAutofillService(ComponentName componentName) {
+ if (componentName == null) {
+ return false;
+ }
+ return componentName.equals(CREDMAN_SERVICE_COMPONENT_NAME);
+ }
+
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index b48b7ecd73a2..3f41c56ac7f1 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -46,6 +46,7 @@ import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SharedMemory;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.Log;
@@ -131,6 +132,9 @@ public class VoiceInteractionService extends Service {
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
static final long MULTIPLE_ACTIVE_HOTWORD_DETECTORS = 193232191L;
+ private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED =
+ SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false);
+
IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
@Override
public void ready() {
@@ -947,6 +951,10 @@ public class VoiceInteractionService extends Service {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
+ if (!SYSPROP_VISUAL_QUERY_SERVICE_ENABLED) {
+ throw new IllegalStateException("VisualQueryDetectionService is not enabled on this "
+ + "system. Please set ro.hotword.visual_query_service_enabled to true.");
+ }
if (mSystemService == null) {
throw new IllegalStateException("Not available until onReady() is called");
}
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index bc837501037f..2761aaeb4a7d 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -35,8 +35,6 @@ import java.lang.ref.WeakReference;
* @hide
*/
public final class InputWindowHandle {
- // TODO (b/300094445): Convert to use correct flagging infrastructure
- public static final boolean USE_SURFACE_TRUSTED_OVERLAY = true;
/**
* An internal annotation for all the {@link android.os.InputConfig} flags that can be
@@ -61,6 +59,7 @@ public final class InputWindowHandle {
InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER,
InputConfig.IS_WALLPAPER,
InputConfig.PAUSE_DISPATCHING,
+ InputConfig.TRUSTED_OVERLAY,
InputConfig.WATCH_OUTSIDE_TOUCH,
InputConfig.SLIPPERY,
InputConfig.DISABLE_USER_ACTIVITY,
@@ -273,13 +272,4 @@ public final class InputWindowHandle {
}
this.inputConfig &= ~inputConfig;
}
-
- public void setTrustedOverlay(SurfaceControl.Transaction t, SurfaceControl sc,
- boolean isTrusted) {
- if (USE_SURFACE_TRUSTED_OVERLAY) {
- t.setTrustedOverlay(sc, isTrusted);
- } else if (isTrusted) {
- inputConfig |= InputConfig.TRUSTED_OVERLAY;
- }
- }
}
diff --git a/core/java/android/view/KeyCharacterMap.aidl b/core/java/android/view/KeyCharacterMap.aidl
new file mode 100644
index 000000000000..1a761a67b520
--- /dev/null
+++ b/core/java/android/view/KeyCharacterMap.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.view;
+
+parcelable KeyCharacterMap; \ No newline at end of file
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index d8221a6267fd..4fe53c2410f5 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.input.InputManagerGlobal;
@@ -309,6 +310,10 @@ public class KeyCharacterMap implements Parcelable {
private static native KeyCharacterMap nativeObtainEmptyKeyCharacterMap(int deviceId);
private static native boolean nativeEquals(long ptr1, long ptr2);
+ private static native void nativeApplyOverlay(long ptr, String layoutDescriptor,
+ String overlay);
+ private static native int nativeGetMappedKey(long ptr, int scanCode);
+
private KeyCharacterMap(Parcel in) {
if (in == null) {
throw new IllegalArgumentException("parcel must not be null");
@@ -368,6 +373,38 @@ public class KeyCharacterMap implements Parcelable {
}
/**
+ * Loads the key character map with applied KCM overlay.
+ *
+ * @param layoutDescriptor descriptor of the applied overlay KCM
+ * @param overlay string describing the overlay KCM
+ * @return The resultant key character map.
+ * @throws {@link UnavailableException} if the key character map
+ * could not be loaded because it was malformed or the default key character map
+ * is missing from the system.
+ * @hide
+ */
+ public static KeyCharacterMap load(@NonNull String layoutDescriptor, @NonNull String overlay) {
+ KeyCharacterMap kcm = KeyCharacterMap.load(VIRTUAL_KEYBOARD);
+ kcm.applyOverlay(layoutDescriptor, overlay);
+ return kcm;
+ }
+
+ private void applyOverlay(@NonNull String layoutDescriptor, @NonNull String overlay) {
+ nativeApplyOverlay(mPtr, layoutDescriptor, overlay);
+ }
+
+ /**
+ * Gets the mapped key for the provided scan code. Returns the provided default if no mapping
+ * found in the KeyCharacterMap.
+ *
+ * @hide
+ */
+ public int getMappedKeyOrDefault(int scanCode, int defaultKeyCode) {
+ int keyCode = nativeGetMappedKey(mPtr, scanCode);
+ return keyCode == KeyEvent.KEYCODE_UNKNOWN ? defaultKeyCode : keyCode;
+ }
+
+ /**
* Gets the Unicode character generated by the specified key and meta
* key state combination.
* <p>
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 85d7c10ef91e..fe515cd3091a 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4437,7 +4437,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @param drawingPosition the drawing order position.
* @return the container position of a child for this drawing order position.
*
- * @see #getChildDrawingOrder(int, int)}
+ * @see #getChildDrawingOrder(int, int)
*/
public final int getChildDrawingOrder(int drawingPosition) {
return getChildDrawingOrder(getChildCount(), drawingPosition);
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 3b8298ed3627..dda399357d8c 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -124,16 +124,16 @@ public class WindowLayout {
|| cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
final Insets systemBarsInsets = state.calculateInsets(
displayFrame, systemBars(), requestedVisibleTypes);
- if (systemBarsInsets.left > 0) {
+ if (systemBarsInsets.left >= cutout.getSafeInsetLeft()) {
displayCutoutSafeExceptMaybeBars.left = MIN_X;
}
- if (systemBarsInsets.top > 0) {
+ if (systemBarsInsets.top >= cutout.getSafeInsetTop()) {
displayCutoutSafeExceptMaybeBars.top = MIN_Y;
}
- if (systemBarsInsets.right > 0) {
+ if (systemBarsInsets.right >= cutout.getSafeInsetRight()) {
displayCutoutSafeExceptMaybeBars.right = MAX_X;
}
- if (systemBarsInsets.bottom > 0) {
+ if (systemBarsInsets.bottom >= cutout.getSafeInsetBottom()) {
displayCutoutSafeExceptMaybeBars.bottom = MAX_Y;
}
}
diff --git a/core/java/android/view/inputmethod/TEST_MAPPING b/core/java/android/view/inputmethod/TEST_MAPPING
index 4b2ea1a096c8..ad59463ea1f1 100644
--- a/core/java/android/view/inputmethod/TEST_MAPPING
+++ b/core/java/android/view/inputmethod/TEST_MAPPING
@@ -11,6 +11,9 @@
},
{
"exclude-annotation": "android.platform.test.annotations.AppModeFull"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
}
]
}
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index 065089f53633..53c73c6bf161 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -16,10 +16,7 @@
package android.widget;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.os.Message;
import android.util.AttributeSet;
@@ -48,7 +45,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
private boolean mRunning = false;
private boolean mStarted = false;
private boolean mVisible = false;
- private boolean mUserPresent = true;
private boolean mAdvancedByHost = false;
public AdapterViewFlipper(Context context) {
@@ -82,40 +78,10 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
a.recycle();
}
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- mUserPresent = false;
- updateRunning();
- } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
- mUserPresent = true;
- updateRunning(false);
- }
- }
- };
-
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- // Listen for broadcasts related to user-presence
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_USER_PRESENT);
-
- // OK, this is gross but needed. This class is supported by the
- // remote views machanism and as a part of that the remote views
- // can be inflated by a context for another user without the app
- // having interact users permission - just for loading resources.
- // For exmaple, when adding widgets from a user profile to the
- // home screen. Therefore, we register the receiver as the current
- // user not the one the context is for.
- getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
- filter, null, getHandler());
-
-
if (mAutoStart) {
// Automatically start when requested
startFlipping();
@@ -126,8 +92,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mVisible = false;
-
- getContext().unregisterReceiver(mReceiver);
updateRunning();
}
@@ -235,8 +199,7 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
* true.
*/
private void updateRunning(boolean flipNow) {
- boolean running = !mAdvancedByHost && mVisible && mStarted && mUserPresent
- && mAdapter != null;
+ boolean running = !mAdvancedByHost && mVisible && mStarted && mAdapter != null;
if (running != mRunning) {
if (running) {
showOnly(mWhichChild, flipNow);
@@ -248,7 +211,7 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
}
if (LOGD) {
Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
- + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
+ + ", mRunning=" + mRunning);
}
}
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 1f0e95ea305a..e01583322979 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -23,7 +23,6 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.BlendMode;
@@ -37,6 +36,9 @@ import android.view.RemotableViewMethod;
import android.view.View;
import android.view.inspector.InspectableProperty;
import android.widget.RemoteViews.RemoteView;
+import android.widget.TextClock.ClockEventDelegate;
+
+import com.android.internal.util.Preconditions;
import java.time.Clock;
import java.time.DateTimeException;
@@ -112,6 +114,7 @@ public class AnalogClock extends View {
public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ mClockEventDelegate = new ClockEventDelegate(context);
mSecondsHandFps = AppGlobals.getIntCoreSetting(
WidgetFlags.KEY_ANALOG_CLOCK_SECONDS_HAND_FPS,
context.getResources()
@@ -584,21 +587,9 @@ public class AnalogClock extends View {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- IntentFilter filter = new IntentFilter();
if (!mReceiverAttached) {
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-
- // OK, this is gross but needed. This class is supported by the
- // remote views mechanism and as a part of that the remote views
- // can be inflated by a context for another user without the app
- // having interact users permission - just for loading resources.
- // For example, when adding widgets from a user profile to the
- // home screen. Therefore, we register the receiver as the current
- // user not the one the context is for.
- getContext().registerReceiverAsUser(mIntentReceiver,
- android.os.Process.myUserHandle(), filter, null, getHandler());
+ mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler());
mReceiverAttached = true;
}
@@ -615,12 +606,23 @@ public class AnalogClock extends View {
@Override
protected void onDetachedFromWindow() {
if (mReceiverAttached) {
- getContext().unregisterReceiver(mIntentReceiver);
+ mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver);
mReceiverAttached = false;
}
super.onDetachedFromWindow();
}
+ /**
+ * Sets a delegate to handle clock event registration. This must be called before the view is
+ * attached to the window
+ *
+ * @hide
+ */
+ public void setClockEventDelegate(ClockEventDelegate delegate) {
+ Preconditions.checkState(!mReceiverAttached, "Clock events already registered");
+ mClockEventDelegate = delegate;
+ }
+
private void onVisible() {
if (!mVisible) {
mVisible = true;
@@ -797,6 +799,7 @@ public class AnalogClock extends View {
}
};
private boolean mReceiverAttached;
+ private ClockEventDelegate mClockEventDelegate;
private final Runnable mTick = new Runnable() {
@Override
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index e48afb2a3cc8..255bd679dc35 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -16,6 +16,7 @@
package android.widget;
+import static android.os.Process.myUserHandle;
import static android.view.ViewDebug.ExportedProperty;
import static android.widget.RemoteViews.RemoteView;
@@ -24,7 +25,6 @@ import android.annotation.TestApi;
import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -43,6 +43,7 @@ import android.view.ViewHierarchyEncoder;
import android.view.inspector.InspectableProperty;
import com.android.internal.R;
+import com.android.internal.util.Preconditions;
import java.time.Duration;
import java.time.Instant;
@@ -141,6 +142,8 @@ public class TextClock extends TextView {
private boolean mRegistered;
private boolean mShouldRunTicker;
+ private ClockEventDelegate mClockEventDelegate;
+
private Calendar mTime;
private String mTimeZone;
@@ -178,8 +181,7 @@ public class TextClock extends TextView {
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
final String timeZone = intent.getStringExtra(Intent.EXTRA_TIMEZONE);
createTime(timeZone);
- } else if (!mShouldRunTicker && (Intent.ACTION_TIME_TICK.equals(intent.getAction())
- || Intent.ACTION_TIME_CHANGED.equals(intent.getAction()))) {
+ } else if (!mShouldRunTicker && Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
return;
}
onTimeChanged();
@@ -282,6 +284,7 @@ public class TextClock extends TextView {
if (mFormat24 == null) {
mFormat24 = getBestDateTimePattern("Hm");
}
+ mClockEventDelegate = new ClockEventDelegate(getContext());
createTime(mTimeZone);
chooseFormat();
@@ -431,6 +434,17 @@ public class TextClock extends TextView {
}
/**
+ * Sets a delegate to handle clock event registration. This must be called before the view is
+ * attached to the window
+ *
+ * @hide
+ */
+ public void setClockEventDelegate(ClockEventDelegate delegate) {
+ Preconditions.checkState(!mRegistered, "Clock events already registered");
+ mClockEventDelegate = delegate;
+ }
+
+ /**
* Update the displayed time if necessary and invalidate the view.
*/
public void refreshTime() {
@@ -557,7 +571,7 @@ public class TextClock extends TextView {
if (!mRegistered) {
mRegistered = true;
- registerReceiver();
+ mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler());
registerObserver();
createTime(mTimeZone);
@@ -582,7 +596,7 @@ public class TextClock extends TextView {
super.onDetachedFromWindow();
if (mRegistered) {
- unregisterReceiver();
+ mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver);
unregisterObserver();
mRegistered = false;
@@ -598,56 +612,27 @@ public class TextClock extends TextView {
mStopTicking = true;
}
- private void registerReceiver() {
- final IntentFilter filter = new IntentFilter();
-
- filter.addAction(Intent.ACTION_TIME_CHANGED);
- filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
-
- // OK, this is gross but needed. This class is supported by the
- // remote views mechanism and as a part of that the remote views
- // can be inflated by a context for another user without the app
- // having interact users permission - just for loading resources.
- // For example, when adding widgets from a managed profile to the
- // home screen. Therefore, we register the receiver as the user
- // the app is running as not the one the context is for.
- getContext().registerReceiverAsUser(mIntentReceiver, android.os.Process.myUserHandle(),
- filter, null, getHandler());
- }
-
private void registerObserver() {
if (mRegistered) {
if (mFormatChangeObserver == null) {
mFormatChangeObserver = new FormatChangeObserver(getHandler());
}
- final ContentResolver resolver = getContext().getContentResolver();
- Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24);
- if (mShowCurrentUserTime) {
- resolver.registerContentObserver(uri, true,
- mFormatChangeObserver, UserHandle.USER_ALL);
- } else {
- // UserHandle.myUserId() is needed. This class is supported by the
- // remote views mechanism and as a part of that the remote views
- // can be inflated by a context for another user without the app
- // having interact users permission - just for loading resources.
- // For example, when adding widgets from a managed profile to the
- // home screen. Therefore, we register the ContentObserver with the user
- // the app is running (e.g. the launcher) and not the user of the
- // context (e.g. the widget's profile).
- resolver.registerContentObserver(uri, true,
- mFormatChangeObserver, UserHandle.myUserId());
- }
+ // UserHandle.myUserId() is needed. This class is supported by the
+ // remote views mechanism and as a part of that the remote views
+ // can be inflated by a context for another user without the app
+ // having interact users permission - just for loading resources.
+ // For example, when adding widgets from a managed profile to the
+ // home screen. Therefore, we register the ContentObserver with the user
+ // the app is running (e.g. the launcher) and not the user of the
+ // context (e.g. the widget's profile).
+ int userHandle = mShowCurrentUserTime ? UserHandle.USER_ALL : UserHandle.myUserId();
+ mClockEventDelegate.registerFormatChangeObserver(mFormatChangeObserver, userHandle);
}
}
- private void unregisterReceiver() {
- getContext().unregisterReceiver(mIntentReceiver);
- }
-
private void unregisterObserver() {
if (mFormatChangeObserver != null) {
- final ContentResolver resolver = getContext().getContentResolver();
- resolver.unregisterContentObserver(mFormatChangeObserver);
+ mClockEventDelegate.unregisterFormatChangeObserver(mFormatChangeObserver);
}
}
@@ -674,4 +659,59 @@ public class TextClock extends TextView {
stream.addProperty("format", mFormat == null ? null : mFormat.toString());
stream.addProperty("hasSeconds", mHasSeconds);
}
+
+ /**
+ * Utility class to delegate some system event handling to allow overring the default behavior
+ *
+ * @hide
+ */
+ public static class ClockEventDelegate {
+
+ private final Context mContext;
+
+ public ClockEventDelegate(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Registers a receiver for actions {@link Intent#ACTION_TIME_CHANGED} and
+ * {@link Intent#ACTION_TIMEZONE_CHANGED}
+ *
+ * OK, this is gross but needed. This class is supported by the remote views mechanism and
+ * as a part of that the remote views can be inflated by a context for another user without
+ * the app having interact users permission - just for loading resources. For example,
+ * when adding widgets from a managed profile to the home screen. Therefore, we register
+ * the receiver as the user the app is running as not the one the context is for.
+ */
+ public void registerTimeChangeReceiver(BroadcastReceiver receiver, Handler handler) {
+ final IntentFilter filter = new IntentFilter();
+
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+
+ mContext.registerReceiverAsUser(receiver, myUserHandle(), filter, null, handler);
+ }
+
+ /**
+ * Unregisters a previously registered receiver
+ */
+ public void unregisterTimeChangeReceiver(BroadcastReceiver receiver) {
+ mContext.unregisterReceiver(receiver);
+ }
+
+ /**
+ * Registers an observer for time format changes
+ */
+ public void registerFormatChangeObserver(ContentObserver observer, int userHandle) {
+ Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24);
+ mContext.getContentResolver().registerContentObserver(uri, true, observer, userHandle);
+ }
+
+ /**
+ * Unregisters a previously registered observer
+ */
+ public void unregisterFormatChangeObserver(ContentObserver observer) {
+ mContext.getContentResolver().unregisterContentObserver(observer);
+ }
+ }
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 59344b00e79a..05063365561f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -15143,6 +15143,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final ClipDescription description =
getClipboardManagerForUser().getPrimaryClipDescription();
+ if (description == null) {
+ return false;
+ }
final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
return (isPlainType && description.isStyledText())
|| description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML);
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index 5abb6e1637e7..eaf037e68976 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -18,10 +18,7 @@ package android.widget;
import android.annotation.IntRange;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Message;
@@ -51,8 +48,6 @@ public class ViewFlipper extends ViewAnimator {
private boolean mRunning = false;
private boolean mStarted = false;
private boolean mVisible = false;
- @UnsupportedAppUsage
- private boolean mUserPresent = true;
public ViewFlipper(Context context) {
super(context);
@@ -70,39 +65,10 @@ public class ViewFlipper extends ViewAnimator {
a.recycle();
}
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_SCREEN_OFF.equals(action)) {
- mUserPresent = false;
- updateRunning();
- } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
- mUserPresent = true;
- updateRunning(false);
- }
- }
- };
-
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- // Listen for broadcasts related to user-presence
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_USER_PRESENT);
-
- // OK, this is gross but needed. This class is supported by the
- // remote views machanism and as a part of that the remote views
- // can be inflated by a context for another user without the app
- // having interact users permission - just for loading resources.
- // For exmaple, when adding widgets from a user profile to the
- // home screen. Therefore, we register the receiver as the current
- // user not the one the context is for.
- getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
- filter, null, getHandler());
-
if (mAutoStart) {
// Automatically start when requested
startFlipping();
@@ -113,8 +79,6 @@ public class ViewFlipper extends ViewAnimator {
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mVisible = false;
-
- getContext().unregisterReceiver(mReceiver);
updateRunning();
}
@@ -186,7 +150,7 @@ public class ViewFlipper extends ViewAnimator {
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void updateRunning(boolean flipNow) {
- boolean running = mVisible && mStarted && mUserPresent;
+ boolean running = mVisible && mStarted;
if (running != mRunning) {
if (running) {
showOnly(mWhichChild, flipNow);
@@ -198,7 +162,7 @@ public class ViewFlipper extends ViewAnimator {
}
if (LOGD) {
Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
- + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
+ + ", mRunning=" + mRunning);
}
}
diff --git a/core/java/android/widget/inline/TEST_MAPPING b/core/java/android/widget/inline/TEST_MAPPING
index 26a556906dd1..82c6f61c3486 100644
--- a/core/java/android/widget/inline/TEST_MAPPING
+++ b/core/java/android/widget/inline/TEST_MAPPING
@@ -8,6 +8,9 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
}
]
}
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index f40874b77536..758582615a46 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -237,13 +237,14 @@ public class SnapshotDrawerUtils {
PixelFormat.RGBA_8888,
GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
| GraphicBuffer.USAGE_SW_WRITE_RARELY);
- if (background == null) {
+ final Canvas c = background != null ? background.lockCanvas() : null;
+ if (c == null) {
Log.e(TAG, "Unable to draw snapshot: failed to allocate graphic buffer for "
+ mTitle);
+ mTransaction.clear();
+ childSurfaceControl.release();
return;
}
- // TODO: Support this on HardwareBuffer
- final Canvas c = background.lockCanvas();
drawBackgroundAndBars(c, frame);
background.unlockCanvasAndPost(c);
mTransaction.setBuffer(mRootSurface,
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index edea2978c340..9b10a7ff5d12 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -36,11 +36,17 @@ public final class TransitionRequestInfo implements Parcelable {
private final @WindowManager.TransitionType int mType;
/**
- * If non-null, If non-null, the task containing the activity whose lifecycle change (start or
+ * If non-null, the task containing the activity whose lifecycle change (start or
* finish) has caused this transition to occur.
*/
private @Nullable ActivityManager.RunningTaskInfo mTriggerTask;
+ /**
+ * If non-null, the task containing the pip activity that participates in this
+ * transition.
+ */
+ private @Nullable ActivityManager.RunningTaskInfo mPipTask;
+
/** If non-null, a remote-transition associated with the source of this transition. */
private @Nullable RemoteTransition mRemoteTransition;
@@ -59,7 +65,8 @@ public final class TransitionRequestInfo implements Parcelable {
@WindowManager.TransitionType int type,
@Nullable ActivityManager.RunningTaskInfo triggerTask,
@Nullable RemoteTransition remoteTransition) {
- this(type, triggerTask, remoteTransition, null /* displayChange */, 0 /* flags */);
+ this(type, triggerTask, null /* pipTask */,
+ remoteTransition, null /* displayChange */, 0 /* flags */);
}
/** constructor override */
@@ -68,7 +75,17 @@ public final class TransitionRequestInfo implements Parcelable {
@Nullable ActivityManager.RunningTaskInfo triggerTask,
@Nullable RemoteTransition remoteTransition,
int flags) {
- this(type, triggerTask, remoteTransition, null /* displayChange */, flags);
+ this(type, triggerTask, null /* pipTask */,
+ remoteTransition, null /* displayChange */, flags);
+ }
+
+ public TransitionRequestInfo(
+ @WindowManager.TransitionType int type,
+ @Nullable ActivityManager.RunningTaskInfo triggerTask,
+ @Nullable RemoteTransition remoteTransition,
+ @Nullable TransitionRequestInfo.DisplayChange displayChange,
+ int flags) {
+ this(type, triggerTask, null /* pipTask */, remoteTransition, displayChange, flags);
}
/** Requested change to a display. */
@@ -246,7 +263,7 @@ public final class TransitionRequestInfo implements Parcelable {
};
@DataClass.Generated(
- time = 1691627678294L,
+ time = 1693425051905L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
inputSignatures = "private final int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate int mStartRotation\nprivate int mEndRotation\nprivate boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)")
@@ -283,6 +300,9 @@ public final class TransitionRequestInfo implements Parcelable {
* @param triggerTask
* If non-null, If non-null, the task containing the activity whose lifecycle change (start or
* finish) has caused this transition to occur.
+ * @param pipTask
+ * If non-null, If non-null, the task containing the activity whose lifecycle change (start or
+ * finish) has caused this transition to occur.
* @param remoteTransition
* If non-null, a remote-transition associated with the source of this transition.
* @param displayChange
@@ -296,6 +316,7 @@ public final class TransitionRequestInfo implements Parcelable {
public TransitionRequestInfo(
@WindowManager.TransitionType int type,
@Nullable ActivityManager.RunningTaskInfo triggerTask,
+ @Nullable ActivityManager.RunningTaskInfo pipTask,
@Nullable RemoteTransition remoteTransition,
@Nullable TransitionRequestInfo.DisplayChange displayChange,
int flags) {
@@ -303,6 +324,7 @@ public final class TransitionRequestInfo implements Parcelable {
com.android.internal.util.AnnotationValidations.validate(
WindowManager.TransitionType.class, null, mType);
this.mTriggerTask = triggerTask;
+ this.mPipTask = pipTask;
this.mRemoteTransition = remoteTransition;
this.mDisplayChange = displayChange;
this.mFlags = flags;
@@ -319,7 +341,7 @@ public final class TransitionRequestInfo implements Parcelable {
}
/**
- * If non-null, If non-null, the task containing the activity whose lifecycle change (start or
+ * If non-null, the task containing the activity whose lifecycle change (start or
* finish) has caused this transition to occur.
*/
@DataClass.Generated.Member
@@ -328,6 +350,15 @@ public final class TransitionRequestInfo implements Parcelable {
}
/**
+ * If non-null, the task containing the pip activity that participates in this
+ * transition.
+ */
+ @DataClass.Generated.Member
+ public @Nullable ActivityManager.RunningTaskInfo getPipTask() {
+ return mPipTask;
+ }
+
+ /**
* If non-null, a remote-transition associated with the source of this transition.
*/
@DataClass.Generated.Member
@@ -354,7 +385,7 @@ public final class TransitionRequestInfo implements Parcelable {
}
/**
- * If non-null, If non-null, the task containing the activity whose lifecycle change (start or
+ * If non-null, the task containing the activity whose lifecycle change (start or
* finish) has caused this transition to occur.
*/
@DataClass.Generated.Member
@@ -364,6 +395,16 @@ public final class TransitionRequestInfo implements Parcelable {
}
/**
+ * If non-null, the task containing the pip activity that participates in this
+ * transition.
+ */
+ @DataClass.Generated.Member
+ public @android.annotation.NonNull TransitionRequestInfo setPipTask(@android.annotation.NonNull ActivityManager.RunningTaskInfo value) {
+ mPipTask = value;
+ return this;
+ }
+
+ /**
* If non-null, a remote-transition associated with the source of this transition.
*/
@DataClass.Generated.Member
@@ -392,6 +433,7 @@ public final class TransitionRequestInfo implements Parcelable {
return "TransitionRequestInfo { " +
"type = " + mType + ", " +
"triggerTask = " + mTriggerTask + ", " +
+ "pipTask = " + mPipTask + ", " +
"remoteTransition = " + mRemoteTransition + ", " +
"displayChange = " + mDisplayChange + ", " +
"flags = " + mFlags +
@@ -406,11 +448,13 @@ public final class TransitionRequestInfo implements Parcelable {
byte flg = 0;
if (mTriggerTask != null) flg |= 0x2;
- if (mRemoteTransition != null) flg |= 0x4;
- if (mDisplayChange != null) flg |= 0x8;
+ if (mPipTask != null) flg |= 0x4;
+ if (mRemoteTransition != null) flg |= 0x8;
+ if (mDisplayChange != null) flg |= 0x10;
dest.writeByte(flg);
dest.writeInt(mType);
if (mTriggerTask != null) dest.writeTypedObject(mTriggerTask, flags);
+ if (mPipTask != null) dest.writeTypedObject(mPipTask, flags);
if (mRemoteTransition != null) dest.writeTypedObject(mRemoteTransition, flags);
if (mDisplayChange != null) dest.writeTypedObject(mDisplayChange, flags);
dest.writeInt(mFlags);
@@ -430,14 +474,16 @@ public final class TransitionRequestInfo implements Parcelable {
byte flg = in.readByte();
int type = in.readInt();
ActivityManager.RunningTaskInfo triggerTask = (flg & 0x2) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
- RemoteTransition remoteTransition = (flg & 0x4) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
- TransitionRequestInfo.DisplayChange displayChange = (flg & 0x8) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR);
+ ActivityManager.RunningTaskInfo pipTask = (flg & 0x4) == 0 ? null : (ActivityManager.RunningTaskInfo) in.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
+ RemoteTransition remoteTransition = (flg & 0x8) == 0 ? null : (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
+ TransitionRequestInfo.DisplayChange displayChange = (flg & 0x10) == 0 ? null : (TransitionRequestInfo.DisplayChange) in.readTypedObject(TransitionRequestInfo.DisplayChange.CREATOR);
int flags = in.readInt();
this.mType = type;
com.android.internal.util.AnnotationValidations.validate(
WindowManager.TransitionType.class, null, mType);
this.mTriggerTask = triggerTask;
+ this.mPipTask = pipTask;
this.mRemoteTransition = remoteTransition;
this.mDisplayChange = displayChange;
this.mFlags = flags;
@@ -460,10 +506,10 @@ public final class TransitionRequestInfo implements Parcelable {
};
@DataClass.Generated(
- time = 1691627678327L,
+ time = 1693425051928L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java",
- inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
+ inputSignatures = "private final @android.view.WindowManager.TransitionType int mType\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mTriggerTask\nprivate @android.annotation.Nullable android.app.ActivityManager.RunningTaskInfo mPipTask\nprivate @android.annotation.Nullable android.window.RemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.window.TransitionRequestInfo.DisplayChange mDisplayChange\nprivate final int mFlags\nclass TransitionRequestInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 6d8512c9d07c..7e2c0179b327 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -3024,28 +3024,31 @@ public class ChooserActivity extends ResolverActivity implements
return shouldShowTabs()
&& (mMultiProfilePagerAdapter.getListAdapterForUserHandle(
UserHandle.of(UserHandle.myUserId())).getCount() > 0
- || shouldShowContentPreviewWhenEmpty())
+ || shouldShowStickyContentPreviewWhenEmpty())
&& shouldShowContentPreview();
}
/**
- * This method could be used to override the default behavior when we hide the preview area
- * when the current tab doesn't have any items.
+ * This method could be used to override the default behavior when we hide the sticky preview
+ * area when the current tab doesn't have any items.
*
- * @return true if we want to show the content preview area even if the tab for the current
- * user is empty
+ * @return {@code true} if we want to show the sticky content preview area even if the tab for
+ * the current user is empty
*/
- protected boolean shouldShowContentPreviewWhenEmpty() {
+ protected boolean shouldShowStickyContentPreviewWhenEmpty() {
return false;
}
- /**
- * @return true if we want to show the content preview area
- */
- protected boolean shouldShowContentPreview() {
+ @Override
+ public boolean shouldShowContentPreview() {
return isSendAction(getTargetIntent());
}
+ @Override
+ public boolean shouldShowServiceTargets() {
+ return shouldShowContentPreview() && !ActivityManager.isLowRamDeviceStatic();
+ }
+
private void updateStickyContentPreview() {
if (shouldShowStickyContentPreviewNoOrientationCheck()) {
// The sticky content preview is only shown when we show the work and personal tabs.
@@ -3407,11 +3410,7 @@ public class ChooserActivity extends ResolverActivity implements
// There can be at most one row in the listview, that is internally
// a ViewGroup with 2 rows
public int getServiceTargetRowCount() {
- if (shouldShowContentPreview()
- && !ActivityManager.isLowRamDeviceStatic()) {
- return 1;
- }
- return 0;
+ return shouldShowServiceTargets() ? 1 : 0;
}
public int getAzLabelRowCount() {
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index f77e71863125..b3e828d15737 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -19,7 +19,6 @@ package com.android.internal.app;
import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
-import android.app.ActivityManager;
import android.app.prediction.AppPredictor;
import android.content.ComponentName;
import android.content.Context;
@@ -426,11 +425,9 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
public int getServiceTargetCount() {
- if (mChooserListCommunicator.isSendAction(mChooserListCommunicator.getTargetIntent())
- && !ActivityManager.isLowRamDeviceStatic()) {
+ if (mChooserListCommunicator.shouldShowServiceTargets()) {
return Math.min(mServiceTargets.size(), mChooserListCommunicator.getMaxRankedTargets());
}
-
return 0;
}
@@ -779,6 +776,10 @@ public class ChooserListAdapter extends ResolverListAdapter {
void sendListViewUpdateMessage(UserHandle userHandle);
boolean isSendAction(Intent targetIntent);
+
+ boolean shouldShowContentPreview();
+
+ boolean shouldShowServiceTargets();
}
/**
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index fe957624cf4b..e0bcef642d82 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -565,6 +565,51 @@ static jlong android_os_Debug_getPss(JNIEnv *env, jobject clazz)
return android_os_Debug_getPssPid(env, clazz, getpid(), NULL, NULL);
}
+static jlong android_os_Debug_getRssPid(JNIEnv* env, jobject clazz, jint pid,
+ jlongArray outMemtrack) {
+ jlong rss = 0;
+ jlong memtrack = 0;
+
+ struct graphics_memory_pss graphics_mem;
+ if (read_memtrack_memory(pid, &graphics_mem) == 0) {
+ rss = memtrack = graphics_mem.graphics + graphics_mem.gl + graphics_mem.other;
+ }
+
+ ::android::meminfo::ProcMemInfo proc_mem(pid);
+ uint64_t status_rss;
+ if (proc_mem.StatusVmRSS(&status_rss)) {
+ rss += status_rss;
+ } else {
+ return 0;
+ }
+
+ if (outMemtrack != NULL) {
+ int outLen = env->GetArrayLength(outMemtrack);
+ if (outLen >= 1) {
+ jlong* outMemtrackArray = env->GetLongArrayElements(outMemtrack, 0);
+ if (outMemtrackArray != NULL) {
+ outMemtrackArray[0] = memtrack;
+ if (outLen >= 2) {
+ outMemtrackArray[1] = graphics_mem.graphics;
+ }
+ if (outLen >= 3) {
+ outMemtrackArray[2] = graphics_mem.gl;
+ }
+ if (outLen >= 4) {
+ outMemtrackArray[3] = graphics_mem.other;
+ }
+ }
+ env->ReleaseLongArrayElements(outMemtrack, outMemtrackArray, 0);
+ }
+ }
+
+ return rss;
+}
+
+static jlong android_os_Debug_getRss(JNIEnv* env, jobject clazz) {
+ return android_os_Debug_getRssPid(env, clazz, getpid(), NULL);
+}
+
// The 1:1 mapping of MEMINFO_* enums here must match with the constants from
// Debug.java.
enum {
@@ -974,62 +1019,43 @@ static jboolean android_os_Debug_isVmapStack(JNIEnv *env, jobject clazz)
*/
static const JNINativeMethod gMethods[] = {
- { "getNativeHeapSize", "()J",
- (void*) android_os_Debug_getNativeHeapSize },
- { "getNativeHeapAllocatedSize", "()J",
- (void*) android_os_Debug_getNativeHeapAllocatedSize },
- { "getNativeHeapFreeSize", "()J",
- (void*) android_os_Debug_getNativeHeapFreeSize },
- { "getMemoryInfo", "(Landroid/os/Debug$MemoryInfo;)V",
- (void*) android_os_Debug_getDirtyPages },
- { "getMemoryInfo", "(ILandroid/os/Debug$MemoryInfo;)Z",
- (void*) android_os_Debug_getDirtyPagesPid },
- { "getPss", "()J",
- (void*) android_os_Debug_getPss },
- { "getPss", "(I[J[J)J",
- (void*) android_os_Debug_getPssPid },
- { "getMemInfo", "([J)V",
- (void*) android_os_Debug_getMemInfo },
- { "dumpNativeHeap", "(Ljava/io/FileDescriptor;)V",
- (void*) android_os_Debug_dumpNativeHeap },
- { "dumpNativeMallocInfo", "(Ljava/io/FileDescriptor;)V",
- (void*) android_os_Debug_dumpNativeMallocInfo },
- { "getBinderSentTransactions", "()I",
- (void*) android_os_Debug_getBinderSentTransactions },
- { "getBinderReceivedTransactions", "()I",
- (void*) android_os_getBinderReceivedTransactions },
- { "getBinderLocalObjectCount", "()I",
- (void*)android_os_Debug_getLocalObjectCount },
- { "getBinderProxyObjectCount", "()I",
- (void*)android_os_Debug_getProxyObjectCount },
- { "getBinderDeathObjectCount", "()I",
- (void*)android_os_Debug_getDeathObjectCount },
- { "dumpJavaBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
- (void*)android_os_Debug_dumpJavaBacktraceToFileTimeout },
- { "dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
- (void*)android_os_Debug_dumpNativeBacktraceToFileTimeout },
- { "getUnreachableMemory", "(IZ)Ljava/lang/String;",
- (void*)android_os_Debug_getUnreachableMemory },
- { "getZramFreeKb", "()J",
- (void*)android_os_Debug_getFreeZramKb },
- { "getIonHeapsSizeKb", "()J",
- (void*)android_os_Debug_getIonHeapsSizeKb },
- { "getDmabufTotalExportedKb", "()J",
- (void*)android_os_Debug_getDmabufTotalExportedKb },
- { "getGpuPrivateMemoryKb", "()J",
- (void*)android_os_Debug_getGpuPrivateMemoryKb },
- { "getDmabufHeapTotalExportedKb", "()J",
- (void*)android_os_Debug_getDmabufHeapTotalExportedKb },
- { "getIonPoolsSizeKb", "()J",
- (void*)android_os_Debug_getIonPoolsSizeKb },
- { "getDmabufMappedSizeKb", "()J",
- (void*)android_os_Debug_getDmabufMappedSizeKb },
- { "getDmabufHeapPoolsSizeKb", "()J",
- (void*)android_os_Debug_getDmabufHeapPoolsSizeKb },
- { "getGpuTotalUsageKb", "()J",
- (void*)android_os_Debug_getGpuTotalUsageKb },
- { "isVmapStack", "()Z",
- (void*)android_os_Debug_isVmapStack },
+ {"getNativeHeapSize", "()J", (void*)android_os_Debug_getNativeHeapSize},
+ {"getNativeHeapAllocatedSize", "()J", (void*)android_os_Debug_getNativeHeapAllocatedSize},
+ {"getNativeHeapFreeSize", "()J", (void*)android_os_Debug_getNativeHeapFreeSize},
+ {"getMemoryInfo", "(Landroid/os/Debug$MemoryInfo;)V",
+ (void*)android_os_Debug_getDirtyPages},
+ {"getMemoryInfo", "(ILandroid/os/Debug$MemoryInfo;)Z",
+ (void*)android_os_Debug_getDirtyPagesPid},
+ {"getPss", "()J", (void*)android_os_Debug_getPss},
+ {"getPss", "(I[J[J)J", (void*)android_os_Debug_getPssPid},
+ {"getRss", "()J", (void*)android_os_Debug_getRss},
+ {"getRss", "(I[J)J", (void*)android_os_Debug_getRssPid},
+ {"getMemInfo", "([J)V", (void*)android_os_Debug_getMemInfo},
+ {"dumpNativeHeap", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Debug_dumpNativeHeap},
+ {"dumpNativeMallocInfo", "(Ljava/io/FileDescriptor;)V",
+ (void*)android_os_Debug_dumpNativeMallocInfo},
+ {"getBinderSentTransactions", "()I", (void*)android_os_Debug_getBinderSentTransactions},
+ {"getBinderReceivedTransactions", "()I", (void*)android_os_getBinderReceivedTransactions},
+ {"getBinderLocalObjectCount", "()I", (void*)android_os_Debug_getLocalObjectCount},
+ {"getBinderProxyObjectCount", "()I", (void*)android_os_Debug_getProxyObjectCount},
+ {"getBinderDeathObjectCount", "()I", (void*)android_os_Debug_getDeathObjectCount},
+ {"dumpJavaBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
+ (void*)android_os_Debug_dumpJavaBacktraceToFileTimeout},
+ {"dumpNativeBacktraceToFileTimeout", "(ILjava/lang/String;I)Z",
+ (void*)android_os_Debug_dumpNativeBacktraceToFileTimeout},
+ {"getUnreachableMemory", "(IZ)Ljava/lang/String;",
+ (void*)android_os_Debug_getUnreachableMemory},
+ {"getZramFreeKb", "()J", (void*)android_os_Debug_getFreeZramKb},
+ {"getIonHeapsSizeKb", "()J", (void*)android_os_Debug_getIonHeapsSizeKb},
+ {"getDmabufTotalExportedKb", "()J", (void*)android_os_Debug_getDmabufTotalExportedKb},
+ {"getGpuPrivateMemoryKb", "()J", (void*)android_os_Debug_getGpuPrivateMemoryKb},
+ {"getDmabufHeapTotalExportedKb", "()J",
+ (void*)android_os_Debug_getDmabufHeapTotalExportedKb},
+ {"getIonPoolsSizeKb", "()J", (void*)android_os_Debug_getIonPoolsSizeKb},
+ {"getDmabufMappedSizeKb", "()J", (void*)android_os_Debug_getDmabufMappedSizeKb},
+ {"getDmabufHeapPoolsSizeKb", "()J", (void*)android_os_Debug_getDmabufHeapPoolsSizeKb},
+ {"getGpuTotalUsageKb", "()J", (void*)android_os_Debug_getGpuTotalUsageKb},
+ {"isVmapStack", "()Z", (void*)android_os_Debug_isVmapStack},
};
int register_android_os_Debug(JNIEnv *env)
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index ddaeb5a4d272..7f69e22fb0d1 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -240,6 +240,36 @@ static jboolean nativeEquals(JNIEnv* env, jobject clazz, jlong ptr1, jlong ptr2)
return static_cast<jboolean>(*map1 == *map2);
}
+static void nativeApplyOverlay(JNIEnv* env, jobject clazz, jlong ptr, jstring nameObj,
+ jstring overlayObj) {
+ NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
+ if (!map || !map->getMap()) {
+ return;
+ }
+ ScopedUtfChars nameChars(env, nameObj);
+ ScopedUtfChars overlayChars(env, overlayObj);
+ base::Result<std::shared_ptr<KeyCharacterMap>> ret =
+ KeyCharacterMap::loadContents(nameChars.c_str(), overlayChars.c_str(),
+ KeyCharacterMap::Format::OVERLAY);
+ if (ret.ok()) {
+ std::shared_ptr<KeyCharacterMap> overlay = *ret;
+ map->getMap()->combine(*overlay);
+ }
+}
+
+static jint nativeGetMappedKey(JNIEnv* env, jobject clazz, jlong ptr, jint scanCode) {
+ NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
+ if (!map || !map->getMap()) {
+ return 0;
+ }
+ int32_t outKeyCode;
+ status_t mapKeyRes = map->getMap()->mapKey(scanCode, /*usageCode=*/0, &outKeyCode);
+ if (mapKeyRes != OK) {
+ return 0;
+ }
+ return static_cast<jint>(outKeyCode);
+}
+
/*
* JNI registration.
*/
@@ -260,7 +290,9 @@ static const JNINativeMethod g_methods[] = {
{"nativeObtainEmptyKeyCharacterMap", "(I)Landroid/view/KeyCharacterMap;",
(void*)nativeObtainEmptyKeyCharacterMap},
{"nativeEquals", "(JJ)Z", (void*)nativeEquals},
-};
+ {"nativeApplyOverlay", "(JLjava/lang/String;Ljava/lang/String;)V",
+ (void*)nativeApplyOverlay},
+ {"nativeGetMappedKey", "(JI)I", (void*)nativeGetMappedKey}};
int register_android_view_KeyCharacterMap(JNIEnv* env)
{
diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto
index 279a5d0c17f8..b9905e8cf446 100644
--- a/core/proto/android/content/package_item_info.proto
+++ b/core/proto/android/content/package_item_info.proto
@@ -30,6 +30,7 @@ message PackageItemInfoProto {
optional string non_localized_label = 4;
optional int32 icon = 5;
optional int32 banner = 6;
+ optional bool is_archived = 7;
}
// Proto of android.content.pm.ApplicationInfo which extends PackageItemInfo
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d000b23bff64..c09f0a3ed624 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7663,6 +7663,10 @@
<permission android:name="android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA"
android:protectionLevel="signature" />
+ <!-- @hide @TestApi Allows tests running in CTS-in-sandbox mode to launch activities -->
+ <permission android:name="android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows the holder to call health connect migration APIs.
@hide -->
<permission android:name="android.permission.MIGRATE_HEALTH_CONNECT_DATA"
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 7f56eb70b153..9cde296b2cab 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -61,11 +61,11 @@ android_test {
"testng",
"servicestests-utils",
"device-time-shell-utils",
+ "testables",
],
libs: [
"android.test.runner",
- "testables",
"org.apache.http.legacy",
"android.test.base",
"android.test.mock",
diff --git a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
index 5dbeac2f32e9..407c6c3e2e2c 100644
--- a/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
+++ b/core/tests/coretests/src/android/content/BroadcastReceiverTests.java
@@ -26,6 +26,9 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.List;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class BroadcastReceiverTests {
@@ -47,15 +50,22 @@ public class BroadcastReceiverTests {
@Test
public void testReceiverLimit() {
final IntentFilter mockFilter = new IntentFilter("android.content.tests.TestAction");
+ final List<EmptyReceiver> receivers = new ArrayList<>(RECEIVER_LIMIT_PER_APP);
try {
for (int i = 0; i < RECEIVER_LIMIT_PER_APP + 1; i++) {
- mContext.registerReceiver(new EmptyReceiver(), mockFilter,
+ final EmptyReceiver receiver = new EmptyReceiver();
+ mContext.registerReceiver(receiver, mockFilter,
Context.RECEIVER_EXPORTED_UNAUDITED);
+ receivers.add(receiver);
}
fail("No exception thrown when registering "
+ (RECEIVER_LIMIT_PER_APP + 1) + " receivers");
} catch (IllegalStateException ise) {
// Expected
+ } finally {
+ for (int i = receivers.size() - 1; i >= 0; i--) {
+ mContext.unregisterReceiver(receivers.remove(i));
+ }
}
}
}
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 555390231983..37ef6cba8814 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -85,9 +85,11 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
assertEquals(2, cache.getAllServicesSize(U0));
assertEquals(2, cache.getPersistentServicesSize(U0));
assertNotEmptyFileCreated(cache, U0);
+ cache.unregisterReceivers();
// Make sure all services can be loaded from xml
cache = new TestServicesCache();
assertEquals(2, cache.getPersistentServicesSize(U0));
+ cache.unregisterReceivers();
}
public void testGetAllServicesReplaceUid() {
@@ -110,6 +112,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
assertTrue("UID must be updated to the new value",
uids.contains(SYSTEM_IMAGE_UID));
assertFalse("UID must be updated to the new value", uids.contains(UID2));
+ cache.unregisterReceivers();
}
public void testGetAllServicesServiceRemoved() {
@@ -118,6 +121,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
cache.addServiceForQuerying(U0, r2, newServiceInfo(t2, UID2));
assertEquals(2, cache.getAllServicesSize(U0));
assertEquals(2, cache.getPersistentServicesSize(U0));
+ cache.unregisterReceivers();
// Re-read data from disk and verify services were saved
cache = new TestServicesCache();
assertEquals(2, cache.getPersistentServicesSize(U0));
@@ -125,6 +129,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
cache.addServiceForQuerying(U0, r1, newServiceInfo(t1, UID1));
assertEquals(1, cache.getAllServicesSize(U0));
assertEquals(1, cache.getPersistentServicesSize(U0));
+ cache.unregisterReceivers();
}
public void testGetAllServicesMultiUser() {
@@ -137,12 +142,14 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
assertEquals(1, cache.getAllServicesSize(U1));
assertEquals(1, cache.getPersistentServicesSize(U1));
assertEquals("No services should be available for user 3", 0, cache.getAllServicesSize(3));
+ cache.unregisterReceivers();
// Re-read data from disk and verify services were saved
cache = new TestServicesCache();
assertEquals(1, cache.getPersistentServicesSize(U0));
assertEquals(1, cache.getPersistentServicesSize(U1));
assertNotEmptyFileCreated(cache, U0);
assertNotEmptyFileCreated(cache, U1);
+ cache.unregisterReceivers();
}
public void testOnRemove() {
@@ -158,6 +165,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
cache.clearServicesForQuerying();
assertEquals(1, cache.getAllServicesSize(U0));
assertEquals(0, cache.getAllServicesSize(U1));
+ cache.unregisterReceivers();
}
public void testMigration() {
@@ -186,10 +194,12 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
cache.addServiceForQuerying(0, r2, newServiceInfo(t2, 2));
assertEquals(2, cache.getAllServicesSize(u0));
assertEquals(0, cache.getAllServicesSize(u1));
+ cache.unregisterReceivers();
// Re-read data from disk. Verify that services were saved and old file was ignored
cache = new TestServicesCache();
assertEquals(2, cache.getPersistentServicesSize(u0));
assertEquals(0, cache.getPersistentServicesSize(u1));
+ cache.unregisterReceivers();
}
private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo(
diff --git a/core/tests/coretests/src/android/service/quicksettings/OWNERS b/core/tests/coretests/src/android/service/quicksettings/OWNERS
new file mode 100644
index 000000000000..5665490ac3bd
--- /dev/null
+++ b/core/tests/coretests/src/android/service/quicksettings/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/core/java/android/service/quicksettings/OWNERS
diff --git a/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java b/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java
index d28eeffae742..04af5d739fc0 100644
--- a/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java
+++ b/core/tests/coretests/src/android/service/quicksettings/TileServiceTest.java
@@ -28,10 +28,10 @@ import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
@@ -40,13 +40,15 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class TileServiceTest {
@Mock
private IQSService.Stub mIQSService;
+ private TestableLooper mTestableLooper;
+
private IBinder mTileToken;
private TileService mTileService;
private Tile mTile;
@@ -55,6 +57,8 @@ public class TileServiceTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mTestableLooper = TestableLooper.get(this);
+
mTileToken = new Binder();
when(mIQSService.asBinder()).thenCallRealMethod();
when(mIQSService.queryLocalInterface(anyString())).thenReturn(mIQSService);
@@ -72,6 +76,7 @@ public class TileServiceTest {
when(mIQSService.getTile(mTileToken)).thenThrow(new RemoteException());
IBinder result = mTileService.onBind(intent);
+ mTestableLooper.processAllMessages();
assertNull(result);
}
@@ -83,6 +88,7 @@ public class TileServiceTest {
when(mIQSService.getTile(mTileToken)).thenReturn(null);
IBinder result = mTileService.onBind(intent);
+ mTestableLooper.processAllMessages();
assertNotNull(result);
verify(mIQSService, never()).onStartSuccessful(any());
@@ -96,6 +102,7 @@ public class TileServiceTest {
when(mIQSService.getTile(mTileToken)).thenReturn(mTile);
IBinder result = mTileService.onBind(intent);
+ mTestableLooper.processAllMessages();
assertNotNull(result);
verify(mIQSService).onStartSuccessful(mTileToken);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
index 2eeaf53ef146..1c9f04a299cc 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserListAdapterTest.kt
@@ -19,8 +19,12 @@ package com.android.internal.app
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
+import android.content.pm.ShortcutInfo
+import android.graphics.drawable.Icon
import android.os.Bundle
import android.os.UserHandle
import android.service.chooser.ChooserTarget
@@ -32,12 +36,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.R
import com.android.internal.app.ChooserListAdapter.LoadDirectShareIconTask
+import com.android.internal.app.chooser.DisplayResolveInfo
import com.android.internal.app.chooser.SelectableTargetInfo
import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator
import com.android.internal.app.chooser.TargetInfo
import com.android.server.testutils.any
import com.android.server.testutils.mock
import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.anyInt
@@ -46,22 +53,25 @@ import org.mockito.Mockito.verify
@RunWith(AndroidJUnit4::class)
class ChooserListAdapterTest {
- private val packageManager = mock<PackageManager> {
- whenever(resolveActivity(any(), anyInt())).thenReturn(mock())
- }
+ private val packageManager =
+ mock<PackageManager> { whenever(resolveActivity(any(), anyInt())).thenReturn(mock()) }
private val context = InstrumentationRegistry.getInstrumentation().getContext()
private val resolverListController = mock<ResolverListController>()
- private val chooserListCommunicator = mock<ChooserListAdapter.ChooserListCommunicator> {
- whenever(maxRankedTargets).thenReturn(0)
- }
- private val selectableTargetInfoCommunicator =
- mock<SelectableTargetInfoCommunicator> {
- whenever(targetIntent).thenReturn(mock())
+ private val chooserListCommunicator =
+ mock<ChooserListAdapter.ChooserListCommunicator> {
+ whenever(maxRankedTargets).thenReturn(0)
}
+ private val selectableTargetInfoCommunicator =
+ mock<SelectableTargetInfoCommunicator> { whenever(targetIntent).thenReturn(mock()) }
private val chooserActivityLogger = mock<ChooserActivityLogger>()
+ @Before
+ fun setUp() {
+ whenever(resolverListController.userHandle).thenReturn(UserHandle.CURRENT)
+ }
+
private fun createChooserListAdapter(
- taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask
+ taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask = createTaskProvider()
) =
ChooserListAdapterOverride(
context,
@@ -98,9 +108,8 @@ class ChooserListAdapterTest {
view.tag = viewHolderOne
val targetInfo = createSelectableTargetInfo()
val iconTaskOne = mock<LoadDirectShareIconTask>()
- val testTaskProvider = mock<() -> LoadDirectShareIconTask> {
- whenever(invoke()).thenReturn(iconTaskOne)
- }
+ val testTaskProvider =
+ mock<() -> LoadDirectShareIconTask> { whenever(invoke()).thenReturn(iconTaskOne) }
val testSubject = createChooserListAdapter { testTaskProvider.invoke() }
testSubject.testViewBind(view, targetInfo, 0)
@@ -114,6 +123,65 @@ class ChooserListAdapterTest {
verify(testTaskProvider, times(1)).invoke()
}
+ @Test
+ fun getServiceTargetCount_shouldNotShowServiceTargets_returnsZero() {
+ whenever(chooserListCommunicator.shouldShowServiceTargets()).thenReturn(false)
+ val adapter = createChooserListAdapter()
+ whenever(chooserListCommunicator.maxRankedTargets).thenReturn(10)
+ addServiceTargets(adapter, targetCount = 50)
+
+ assertThat(adapter.serviceTargetCount).isEqualTo(0)
+ }
+
+ private fun createTaskProvider(): (SelectableTargetInfo?) -> LoadDirectShareIconTask {
+ val iconTaskOne = mock<LoadDirectShareIconTask>()
+ val testTaskProvider =
+ mock<() -> LoadDirectShareIconTask> { whenever(invoke()).thenReturn(iconTaskOne) }
+ return { testTaskProvider.invoke() }
+ }
+
+ private fun addServiceTargets(adapter: ChooserListAdapter, targetCount: Int) {
+ val origTarget =
+ DisplayResolveInfo(
+ Intent(),
+ createResolveInfo(),
+ Intent(),
+ ResolverListAdapter.ResolveInfoPresentationGetter(context, 200, createResolveInfo())
+ )
+ val targets = mutableListOf<ChooserTarget>()
+ for (i in 1..targetCount) {
+ val score = 1f
+ val componentName = ComponentName("chooser.list.adapter", "Test$i")
+ val extras = Bundle()
+ val icon: Icon? = null
+ targets += ChooserTarget("Title $i", icon, score, componentName, extras)
+ }
+ val directShareToShortcutInfos = mapOf<ChooserTarget, ShortcutInfo>()
+ adapter.addServiceResults(
+ origTarget,
+ targets,
+ ChooserActivity.TARGET_TYPE_DEFAULT,
+ directShareToShortcutInfos
+ )
+ }
+
+ private fun createResolveInfo(): ResolveInfo {
+ val applicationInfo =
+ ApplicationInfo().apply {
+ packageName = "chooser.list.adapter"
+ name = "ChooserListAdapterTestApplication"
+ }
+ val activityInfo =
+ ActivityInfo().apply {
+ packageName = applicationInfo.packageName
+ name = "ChooserListAdapterTest"
+ }
+ activityInfo.applicationInfo = applicationInfo
+ val resolveInfo = ResolveInfo()
+ resolveInfo.activityInfo = activityInfo
+ return resolveInfo
+ }
+
private fun createSelectableTargetInfo(): SelectableTargetInfo =
SelectableTargetInfo(
context,
@@ -125,13 +193,7 @@ class ChooserListAdapterTest {
)
private fun createChooserTarget(): ChooserTarget =
- ChooserTarget(
- "Title",
- null,
- 1f,
- ComponentName("package", "package.Class"),
- Bundle()
- )
+ ChooserTarget("Title", null, 1f, ComponentName("package", "package.Class"), Bundle())
private fun createView(): View {
val view = FrameLayout(context)
@@ -164,23 +226,23 @@ private class ChooserListAdapterOverride(
chooserActivityLogger: ChooserActivityLogger?,
initialIntentsUserHandle: UserHandle?,
private val taskProvider: (SelectableTargetInfo?) -> LoadDirectShareIconTask
-) : ChooserListAdapter(
- context,
- payloadIntents,
- initialIntents,
- rList,
- filterLastUsed,
- resolverListController,
- chooserListCommunicator,
- selectableTargetInfoCommunicator,
- packageManager,
- chooserActivityLogger,
- initialIntentsUserHandle,
-) {
+) :
+ ChooserListAdapter(
+ context,
+ payloadIntents,
+ initialIntents,
+ rList,
+ filterLastUsed,
+ resolverListController,
+ chooserListCommunicator,
+ selectableTargetInfoCommunicator,
+ packageManager,
+ chooserActivityLogger,
+ initialIntentsUserHandle,
+ ) {
override fun createLoadDirectShareIconTask(
info: SelectableTargetInfo?
- ): LoadDirectShareIconTask =
- taskProvider.invoke(info)
+ ): LoadDirectShareIconTask = taskProvider.invoke(info)
fun testViewBind(view: View?, info: TargetInfo?, position: Int) {
onBindView(view, info, position)
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
index e870d6022058..8d825e4deb81 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryInputSuspendTest.java
@@ -99,6 +99,7 @@ public class BatteryInputSuspendTest {
if (isCharging(intent) == mExpectedChargingState) {
mReady.open();
}
+ context.unregisterReceiver(this);
}
}, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index ee2eacfd304f..28a4b49e9d00 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -253,12 +253,6 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-1883484959": {
- "message": "Content Recording: Display %d state is now (%d), so update recording?",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/DisplayContent.java"
- },
"-1872288685": {
"message": "applyAnimation: anim=%s nextAppTransition=%s transit=%s isEntrance=%b Callers=%s",
"level": "VERBOSE",
@@ -445,6 +439,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/ResetTargetTaskHelper.java"
},
+ "-1700778361": {
+ "message": "Content Recording: Going ahead with updating recording for display %d to new bounds %s and\/or orientation %d and\/or surface size %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"-1699018375": {
"message": "Adding activity %s to task %s callers: %s",
"level": "INFO",
@@ -649,12 +649,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
- "-1480264178": {
- "message": "Content Recording: Unable to update recording for display %d to new bounds %s and\/or orientation %d, since the surface is not available.",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-1478175541": {
"message": "No longer animating wallpaper targets!",
"level": "VERBOSE",
@@ -1855,12 +1849,6 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/Task.java"
},
- "-452750194": {
- "message": "Content Recording: Going ahead with updating recording for display %d to new bounds %s and\/or orientation %d.",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"-451552570": {
"message": "Current focused window being animated by recents. Overriding back callback to recents controller callback.",
"level": "DEBUG",
@@ -2377,6 +2365,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "34106798": {
+ "message": "Content Recording: Display %d state was (%d), is now (%d), so update recording?",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/DisplayContent.java"
+ },
"34682671": {
"message": "Not moving display (displayId=%d) to top. Top focused displayId=%d. Reason: FLAG_STEAL_TOP_FOCUS_DISABLED",
"level": "INFO",
@@ -2413,6 +2407,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "61363198": {
+ "message": "Auto-PIP allowed, requesting PIP mode via requestStartTransition(): %s, willAutoPip: %b",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"74885950": {
"message": "Waiting for top state to be released by %s",
"level": "VERBOSE",
@@ -2425,12 +2425,6 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
- "90764070": {
- "message": "Could not report token removal to the window token client.",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowContextListenerController.java"
- },
"95216706": {
"message": "hideIme target: %s ",
"level": "DEBUG",
@@ -3079,12 +3073,6 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
- "643263584": {
- "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop %d x %d for display %d",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONTENT_RECORDING",
- "at": "com\/android\/server\/wm\/ContentRecorder.java"
- },
"644675193": {
"message": "Real start recents",
"level": "DEBUG",
@@ -4105,6 +4093,12 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "1687944543": {
+ "message": "Content Recording: Unable to update recording for display %d to new bounds %s and\/or orientation %d and\/or surface size %s, since the surface is not available.",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"1699269281": {
"message": "Don't organize or trigger events for untrusted displayId=%d",
"level": "WARN",
@@ -4333,6 +4327,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "1936800105": {
+ "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka recorded content size) %d x %d for display %d; display has size %d x %d; surface has size %d x %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONTENT_RECORDING",
+ "at": "com\/android\/server\/wm\/ContentRecorder.java"
+ },
"1945495497": {
"message": "Focused window didn't have a valid surface drawn.",
"level": "DEBUG",
@@ -4351,12 +4351,6 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
- "1948483534": {
- "message": "Could not report config changes to the window token client.",
- "level": "WARN",
- "group": "WM_ERROR",
- "at": "com\/android\/server\/wm\/WindowContextListenerController.java"
- },
"1964565370": {
"message": "Starting remote animation",
"level": "INFO",
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 919a93b8f107..0f3488bbe8d1 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
+import android.os.StrictMode;
import android.security.maintenance.IKeystoreMaintenance;
import android.system.keystore2.Domain;
import android.system.keystore2.KeyDescriptor;
@@ -51,6 +52,7 @@ public class AndroidKeyStoreMaintenance {
* @hide
*/
public static int onUserAdded(@NonNull int userId) {
+ StrictMode.noteDiskWrite();
try {
getService().onUserAdded(userId);
return 0;
@@ -71,6 +73,7 @@ public class AndroidKeyStoreMaintenance {
* @hide
*/
public static int onUserRemoved(int userId) {
+ StrictMode.noteDiskWrite();
try {
getService().onUserRemoved(userId);
return 0;
@@ -93,6 +96,7 @@ public class AndroidKeyStoreMaintenance {
* @hide
*/
public static int onUserPasswordChanged(int userId, @Nullable byte[] password) {
+ StrictMode.noteDiskWrite();
try {
getService().onUserPasswordChanged(userId, password);
return 0;
@@ -110,6 +114,7 @@ public class AndroidKeyStoreMaintenance {
* be cleared.
*/
public static int clearNamespace(@Domain int domain, long namespace) {
+ StrictMode.noteDiskWrite();
try {
getService().clearNamespace(domain, namespace);
return 0;
@@ -129,6 +134,7 @@ public class AndroidKeyStoreMaintenance {
* @return UserState enum variant as integer if successful or an error
*/
public static int getState(int userId) {
+ StrictMode.noteDiskRead();
try {
return getService().getState(userId);
} catch (ServiceSpecificException e) {
@@ -144,6 +150,7 @@ public class AndroidKeyStoreMaintenance {
* Informs Keystore 2.0 that an off body event was detected.
*/
public static void onDeviceOffBody() {
+ StrictMode.noteDiskWrite();
try {
getService().onDeviceOffBody();
} catch (Exception e) {
@@ -172,6 +179,7 @@ public class AndroidKeyStoreMaintenance {
* * SYSTEM_ERROR if an unexpected error occurred.
*/
public static int migrateKeyNamespace(KeyDescriptor source, KeyDescriptor destination) {
+ StrictMode.noteDiskWrite();
try {
getService().migrateKeyNamespace(source, destination);
return 0;
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index 00219e7f28ac..2d2dd24763c4 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -22,6 +22,7 @@ import android.hardware.security.keymint.HardwareAuthToken;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
+import android.os.StrictMode;
import android.security.authorization.IKeystoreAuthorization;
import android.security.authorization.LockScreenEvent;
import android.system.keystore2.ResponseCode;
@@ -48,6 +49,7 @@ public class Authorization {
* @return 0 if successful or {@code ResponseCode.SYSTEM_ERROR}.
*/
public static int addAuthToken(@NonNull HardwareAuthToken authToken) {
+ StrictMode.noteSlowCall("addAuthToken");
try {
getService().addAuthToken(authToken);
return 0;
@@ -81,6 +83,7 @@ public class Authorization {
*/
public static int onLockScreenEvent(@NonNull boolean locked, @NonNull int userId,
@Nullable byte[] syntheticPassword, @Nullable long[] unlockingSids) {
+ StrictMode.noteDiskWrite();
try {
if (locked) {
getService().onLockScreenEvent(LockScreenEvent.LOCK, userId, null, unlockingSids);
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 8811a7fec932..8045f55f6b4c 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -18,6 +18,7 @@ package android.security;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
+import android.os.StrictMode;
import android.os.UserHandle;
import android.security.maintenance.UserState;
@@ -126,6 +127,8 @@ public class KeyStore {
* a {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode.
*/
public int addAuthToken(byte[] authToken) {
+ StrictMode.noteDiskWrite();
+
return Authorization.addAuthToken(authToken);
}
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
index c83f298046a5..5e16bcee1a0e 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -23,6 +23,7 @@ import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
+import android.os.StrictMode;
import android.security.keymaster.KeymasterDefs;
import android.system.keystore2.Domain;
import android.system.keystore2.IKeystoreService;
@@ -148,6 +149,8 @@ public class KeyStore2 {
}
void delete(KeyDescriptor descriptor) throws KeyStoreException {
+ StrictMode.noteDiskWrite();
+
handleRemoteExceptionWithRetry((service) -> {
service.deleteKey(descriptor);
return 0;
@@ -158,6 +161,8 @@ public class KeyStore2 {
* List all entries in the keystore for in the given namespace.
*/
public KeyDescriptor[] list(int domain, long namespace) throws KeyStoreException {
+ StrictMode.noteDiskRead();
+
return handleRemoteExceptionWithRetry((service) -> service.listEntries(domain, namespace));
}
@@ -166,6 +171,8 @@ public class KeyStore2 {
*/
public KeyDescriptor[] listBatch(int domain, long namespace, String startPastAlias)
throws KeyStoreException {
+ StrictMode.noteDiskRead();
+
return handleRemoteExceptionWithRetry(
(service) -> service.listEntriesBatched(domain, namespace, startPastAlias));
}
@@ -228,6 +235,8 @@ public class KeyStore2 {
*/
public KeyDescriptor grant(KeyDescriptor descriptor, int granteeUid, int accessVector)
throws KeyStoreException {
+ StrictMode.noteDiskWrite();
+
return handleRemoteExceptionWithRetry(
(service) -> service.grant(descriptor, granteeUid, accessVector)
);
@@ -243,6 +252,8 @@ public class KeyStore2 {
*/
public void ungrant(KeyDescriptor descriptor, int granteeUid)
throws KeyStoreException {
+ StrictMode.noteDiskWrite();
+
handleRemoteExceptionWithRetry((service) -> {
service.ungrant(descriptor, granteeUid);
return 0;
@@ -259,6 +270,8 @@ public class KeyStore2 {
*/
public KeyEntryResponse getKeyEntry(@NonNull KeyDescriptor descriptor)
throws KeyStoreException {
+ StrictMode.noteDiskRead();
+
return handleRemoteExceptionWithRetry((service) -> service.getKeyEntry(descriptor));
}
@@ -290,6 +303,8 @@ public class KeyStore2 {
*/
public void updateSubcomponents(@NonNull KeyDescriptor key, byte[] publicCert,
byte[] publicCertChain) throws KeyStoreException {
+ StrictMode.noteDiskWrite();
+
handleRemoteExceptionWithRetry((service) -> {
service.updateSubcomponent(key, publicCert, publicCertChain);
return 0;
@@ -305,6 +320,8 @@ public class KeyStore2 {
*/
public void deleteKey(@NonNull KeyDescriptor descriptor)
throws KeyStoreException {
+ StrictMode.noteDiskWrite();
+
handleRemoteExceptionWithRetry((service) -> {
service.deleteKey(descriptor);
return 0;
@@ -315,6 +332,8 @@ public class KeyStore2 {
* Returns the number of Keystore entries for a given domain and namespace.
*/
public int getNumberOfEntries(int domain, long namespace) throws KeyStoreException {
+ StrictMode.noteDiskRead();
+
return handleRemoteExceptionWithRetry((service)
-> service.getNumberOfEntries(domain, namespace));
}
diff --git a/keystore/java/android/security/KeyStoreOperation.java b/keystore/java/android/security/KeyStoreOperation.java
index 737ff2b4822f..7c9b8eb06764 100644
--- a/keystore/java/android/security/KeyStoreOperation.java
+++ b/keystore/java/android/security/KeyStoreOperation.java
@@ -21,6 +21,7 @@ import android.hardware.security.keymint.KeyParameter;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.os.StrictMode;
import android.security.keymaster.KeymasterDefs;
import android.system.keystore2.IKeystoreOperation;
import android.system.keystore2.ResponseCode;
@@ -97,6 +98,7 @@ public class KeyStoreOperation {
* @throws KeyStoreException
*/
public void updateAad(@NonNull byte[] input) throws KeyStoreException {
+ StrictMode.noteSlowCall("updateAad");
handleExceptions(() -> {
mOperation.updateAad(input);
return 0;
@@ -112,6 +114,7 @@ public class KeyStoreOperation {
* @hide
*/
public byte[] update(@NonNull byte[] input) throws KeyStoreException {
+ StrictMode.noteSlowCall("update");
return handleExceptions(() -> mOperation.update(input));
}
@@ -125,6 +128,7 @@ public class KeyStoreOperation {
* @hide
*/
public byte[] finish(byte[] input, byte[] signature) throws KeyStoreException {
+ StrictMode.noteSlowCall("finish");
return handleExceptions(() -> mOperation.finish(input, signature));
}
@@ -135,6 +139,7 @@ public class KeyStoreOperation {
* @hide
*/
public void abort() throws KeyStoreException {
+ StrictMode.noteSlowCall("abort");
handleExceptions(() -> {
mOperation.abort();
return 0;
diff --git a/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java
index 9c0b46c8e87b..6ab148a8b4ea 100644
--- a/keystore/java/android/security/KeyStoreSecurityLevel.java
+++ b/keystore/java/android/security/KeyStoreSecurityLevel.java
@@ -22,6 +22,7 @@ import android.hardware.security.keymint.KeyParameter;
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.os.StrictMode;
import android.security.keystore.BackendBusyException;
import android.security.keystore.KeyStoreConnectException;
import android.system.keystore2.AuthenticatorSpec;
@@ -75,6 +76,7 @@ public class KeyStoreSecurityLevel {
*/
public KeyStoreOperation createOperation(@NonNull KeyDescriptor keyDescriptor,
Collection<KeyParameter> args) throws KeyStoreException {
+ StrictMode.noteDiskWrite();
while (true) {
try {
CreateOperationResponse createOperationResponse =
@@ -142,6 +144,8 @@ public class KeyStoreSecurityLevel {
public KeyMetadata generateKey(@NonNull KeyDescriptor descriptor, KeyDescriptor attestationKey,
Collection<KeyParameter> args, int flags, byte[] entropy)
throws KeyStoreException {
+ StrictMode.noteDiskWrite();
+
return handleExceptions(() -> mSecurityLevel.generateKey(
descriptor, attestationKey, args.toArray(new KeyParameter[args.size()]),
flags, entropy));
@@ -163,6 +167,8 @@ public class KeyStoreSecurityLevel {
public KeyMetadata importKey(KeyDescriptor descriptor, KeyDescriptor attestationKey,
Collection<KeyParameter> args, int flags, byte[] keyData)
throws KeyStoreException {
+ StrictMode.noteDiskWrite();
+
return handleExceptions(() -> mSecurityLevel.importKey(descriptor, attestationKey,
args.toArray(new KeyParameter[args.size()]), flags, keyData));
}
@@ -186,6 +192,7 @@ public class KeyStoreSecurityLevel {
@NonNull byte[] wrappedKey, byte[] maskingKey,
Collection<KeyParameter> args, @NonNull AuthenticatorSpec[] authenticatorSpecs)
throws KeyStoreException {
+ StrictMode.noteDiskWrite();
KeyDescriptor keyDescriptor = new KeyDescriptor();
keyDescriptor.alias = wrappedKeyDescriptor.alias;
keyDescriptor.nspace = wrappedKeyDescriptor.nspace;
diff --git a/keystore/java/android/security/LegacyVpnProfileStore.java b/keystore/java/android/security/LegacyVpnProfileStore.java
index c85b6b1efd9a..0cc4dfab12f8 100644
--- a/keystore/java/android/security/LegacyVpnProfileStore.java
+++ b/keystore/java/android/security/LegacyVpnProfileStore.java
@@ -19,6 +19,7 @@ package android.security;
import android.annotation.NonNull;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
+import android.os.StrictMode;
import android.security.legacykeystore.ILegacyKeystore;
import android.util.Log;
@@ -51,6 +52,7 @@ public class LegacyVpnProfileStore {
* @hide
*/
public static boolean put(@NonNull String alias, @NonNull byte[] profile) {
+ StrictMode.noteDiskWrite();
try {
getService().put(alias, ILegacyKeystore.UID_SELF, profile);
return true;
@@ -70,6 +72,7 @@ public class LegacyVpnProfileStore {
* @hide
*/
public static byte[] get(@NonNull String alias) {
+ StrictMode.noteDiskRead();
try {
return getService().get(alias, ILegacyKeystore.UID_SELF);
} catch (ServiceSpecificException e) {
@@ -89,6 +92,7 @@ public class LegacyVpnProfileStore {
* @hide
*/
public static boolean remove(@NonNull String alias) {
+ StrictMode.noteDiskWrite();
try {
getService().remove(alias, ILegacyKeystore.UID_SELF);
return true;
@@ -109,6 +113,7 @@ public class LegacyVpnProfileStore {
* @hide
*/
public static @NonNull String[] list(@NonNull String prefix) {
+ StrictMode.noteDiskRead();
try {
final String[] aliases = getService().list(prefix, ILegacyKeystore.UID_SELF);
for (int i = 0; i < aliases.length; ++i) {
diff --git a/keystore/java/android/security/SystemKeyStore.java b/keystore/java/android/security/SystemKeyStore.java
index e07eaa2e32ed..d481a078ab00 100644
--- a/keystore/java/android/security/SystemKeyStore.java
+++ b/keystore/java/android/security/SystemKeyStore.java
@@ -18,6 +18,9 @@ package android.security;
import android.os.Environment;
import android.os.FileUtils;
+import android.os.StrictMode;
+
+import libcore.io.IoUtils;
import java.io.File;
import java.io.FileOutputStream;
@@ -28,8 +31,6 @@ import java.security.SecureRandom;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
-import libcore.io.IoUtils;
-
/**
*@hide
*/
@@ -69,6 +70,7 @@ public class SystemKeyStore {
public byte[] generateNewKey(int numBits, String algName, String keyName)
throws NoSuchAlgorithmException {
+ StrictMode.noteDiskWrite();
// Check if key with similar name exists. If so, return null.
File keyFile = getKeyFile(keyName);
@@ -103,6 +105,7 @@ public class SystemKeyStore {
}
private File getKeyFile(String keyName) {
+ StrictMode.noteDiskWrite();
File sysKeystoreDir = new File(Environment.getDataDirectory(),
SYSTEM_KEYSTORE_DIRECTORY);
File keyFile = new File(sysKeystoreDir, keyName + KEY_FILE_EXTENSION);
@@ -114,6 +117,7 @@ public class SystemKeyStore {
}
public byte[] retrieveKey(String keyName) throws IOException {
+ StrictMode.noteDiskRead();
File keyFile = getKeyFile(keyName);
if (!keyFile.exists()) {
return null;
@@ -122,6 +126,7 @@ public class SystemKeyStore {
}
public void deleteKey(String keyName) {
+ StrictMode.noteDiskWrite();
// Get the file first.
File keyFile = getKeyFile(keyName);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
index d12989187281..9ac0f6d304f6 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
@@ -20,6 +20,7 @@ import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.security.keymint.KeyParameter;
+import android.os.StrictMode;
import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keymaster.KeymasterDefs;
@@ -137,6 +138,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
if (!(key instanceof AndroidKeyStorePrivateKey)
&& (key instanceof PrivateKey || key instanceof PublicKey)) {
try {
+ StrictMode.noteSlowCall("engineInit");
mCipher = Cipher.getInstance(getTransform());
String transform = getTransform();
@@ -203,6 +205,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
if (!(key instanceof AndroidKeyStorePrivateKey)
&& (key instanceof PrivateKey || key instanceof PublicKey)) {
try {
+ StrictMode.noteSlowCall("engineInit");
mCipher = Cipher.getInstance(getTransform());
mCipher.init(opmode, key, params, random);
return;
@@ -233,6 +236,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
if (!(key instanceof AndroidKeyStorePrivateKey)
&& (key instanceof PrivateKey || key instanceof PublicKey)) {
try {
+ StrictMode.noteSlowCall("engineInit");
mCipher = Cipher.getInstance(getTransform());
mCipher.init(opmode, key, params, random);
return;
@@ -346,6 +350,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
parameters.add(KeyStore2ParameterUtils.makeEnum(KeymasterDefs.KM_TAG_PURPOSE, purpose));
try {
+ StrictMode.noteDiskRead();
mOperation = mKey.getSecurityLevel().createOperation(
mKey.getKeyIdDescriptor(),
parameters
@@ -521,6 +526,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
if (mCipher != null) {
+ StrictMode.noteSlowCall("engineUpdateAAD");
mCipher.updateAAD(input, inputOffset, inputLen);
return;
}
@@ -562,6 +568,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final void engineUpdateAAD(ByteBuffer src) {
if (mCipher != null) {
+ StrictMode.noteSlowCall("engineUpdateAAD");
mCipher.updateAAD(src);
return;
}
@@ -715,6 +722,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
throw new NullPointerException("key == null");
}
byte[] encoded = null;
+ StrictMode.noteSlowCall("engineWrap");
if (key instanceof SecretKey) {
if ("RAW".equalsIgnoreCase(key.getFormat())) {
encoded = key.getEncoded();
@@ -807,6 +815,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
throw new InvalidKeyException("Failed to unwrap key", e);
}
+ StrictMode.noteSlowCall("engineUnwrap");
switch (wrappedKeyType) {
case Cipher.SECRET_KEY:
{
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
index ace2053cc1a7..9d3fca86903b 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
@@ -195,7 +195,7 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature
protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
if (!ACCEPTED_SIGNING_SCHEMES.contains(key.getAlgorithm().toLowerCase())) {
throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
- + ". Only" + Arrays.toString(ACCEPTED_SIGNING_SCHEMES.stream().toArray())
+ + ". Only " + Arrays.toString(ACCEPTED_SIGNING_SCHEMES.stream().toArray())
+ " supported");
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
index 7292cd3c5fb1..66e9f71a1f7b 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyAgreementSpi.java
@@ -20,6 +20,7 @@ import android.hardware.security.keymint.Algorithm;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.KeyPurpose;
import android.hardware.security.keymint.Tag;
+import android.os.StrictMode;
import android.security.KeyStoreException;
import android.security.KeyStoreOperation;
import android.security.keystore.KeyStoreCryptoOperation;
@@ -174,6 +175,7 @@ public class AndroidKeyStoreKeyAgreementSpi extends KeyAgreementSpi
}
byte[] otherPartyKeyEncoded = mOtherPartyKey.getEncoded();
+ StrictMode.noteSlowCall("engineGenerateSecret");
try {
return mOperation.finish(otherPartyKeyEncoded, null);
} catch (KeyStoreException e) {
@@ -245,6 +247,7 @@ public class AndroidKeyStoreKeyAgreementSpi extends KeyAgreementSpi
Tag.PURPOSE, KeyPurpose.AGREE_KEY
));
+ StrictMode.noteDiskWrite();
try {
mOperation =
mKey.getSecurityLevel().createOperation(mKey.getKeyIdDescriptor(), parameters);
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
index f1681ec1f7d2..d283b05a85e1 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
@@ -18,6 +18,7 @@ package android.security.keystore2;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.SecurityLevel;
+import android.os.StrictMode;
import android.security.KeyStore2;
import android.security.KeyStoreSecurityLevel;
import android.security.keymaster.KeymasterDefs;
@@ -281,6 +282,7 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi {
@Override
protected SecretKey engineGenerateKey() {
+ StrictMode.noteSlowCall("engineGenerateKey");
KeyGenParameterSpec spec = mSpec;
if (spec == null) {
throw new IllegalStateException("Not initialized");
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index 474b7ea56be9..1398da3f5ef2 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -27,6 +27,7 @@ import android.hardware.security.keymint.KeyPurpose;
import android.hardware.security.keymint.SecurityLevel;
import android.hardware.security.keymint.Tag;
import android.os.Build;
+import android.os.StrictMode;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore2;
import android.security.KeyStoreException;
@@ -617,6 +618,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
@Override
public KeyPair generateKeyPair() {
+ StrictMode.noteSlowCall("generateKeyPair");
if (mKeyStore == null || mSpec == null) {
throw new IllegalStateException("Not initialized");
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
index 931c2f864eba..d5fb49a5cb94 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
@@ -189,7 +189,7 @@ abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSp
protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException {
if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) {
throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm()
- + ". Only" + KeyProperties.KEY_ALGORITHM_RSA + " supported");
+ + ". Only " + KeyProperties.KEY_ALGORITHM_RSA + " supported");
}
super.initKey(key);
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index b4d8defd4f90..273dff1cb8b3 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -24,6 +24,7 @@ import android.hardware.security.keymint.EcCurve;
import android.hardware.security.keymint.HardwareAuthenticatorType;
import android.hardware.security.keymint.KeyParameter;
import android.hardware.security.keymint.SecurityLevel;
+import android.os.StrictMode;
import android.security.GateKeeper;
import android.security.KeyStore2;
import android.security.KeyStoreParameter;
@@ -164,6 +165,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
KeyDescriptor descriptor = makeKeyDescriptor(alias);
try {
+ StrictMode.noteDiskRead();
return mKeyStore.getKeyEntry(descriptor);
} catch (android.security.KeyStoreException e) {
if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) {
@@ -447,6 +449,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
assertCanReplace(alias, targetDomain, mNamespace, descriptor);
try {
+ StrictMode.noteDiskWrite();
mKeyStore.updateSubcomponents(
((AndroidKeyStorePrivateKey) key).getKeyIdDescriptor(),
userCertBytes, chainBytes);
@@ -597,6 +600,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
importArgs, flags, pkcs8EncodedPrivateKeyBytes);
try {
+ StrictMode.noteDiskWrite();
mKeyStore.updateSubcomponents(metadata.key, userCertBytes, chainBytes);
} catch (android.security.KeyStoreException e) {
mKeyStore.deleteKey(metadata.key);
@@ -938,6 +942,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
KeyEntryResponse response = null;
try {
+ StrictMode.noteDiskRead();
response = mKeyStore.getKeyEntry(wrappingkey);
} catch (android.security.KeyStoreException e) {
throw new KeyStoreException("Failed to import wrapped key. Keystore error code: "
@@ -994,6 +999,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
}
try {
+ StrictMode.noteDiskWrite();
securityLevel.importWrappedKey(
wrappedKey, wrappingkey,
entry.getWrappedKeyBytes(),
@@ -1054,6 +1060,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
}
try {
+ StrictMode.noteDiskWrite();
mKeyStore.updateSubcomponents(makeKeyDescriptor(alias),
null /* publicCert - unused when used as pure certificate store. */,
encoded);
@@ -1066,6 +1073,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
public void engineDeleteEntry(String alias) throws KeyStoreException {
KeyDescriptor descriptor = makeKeyDescriptor(alias);
try {
+ StrictMode.noteDiskWrite();
mKeyStore.deleteKey(descriptor);
} catch (android.security.KeyStoreException e) {
if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) {
@@ -1076,6 +1084,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
private KeyDescriptor[] getAliasesBatch(String startPastAlias) {
try {
+ StrictMode.noteDiskRead();
return mKeyStore.listBatch(
getTargetDomain(),
mNamespace,
@@ -1103,6 +1112,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
@Override
public int engineSize() {
try {
+ StrictMode.noteDiskRead();
return mKeyStore.getNumberOfEntries(
getTargetDomain(),
mNamespace
@@ -1166,6 +1176,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
KeyDescriptor[] keyDescriptors = null;
try {
+ StrictMode.noteDiskRead();
keyDescriptors = mKeyStore.list(
getTargetDomain(),
mNamespace
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 0112e32e05a7..15d14e87fcf6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -213,9 +213,6 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
if (mRearDisplayStateRequest != null || isRearDisplayActive()) {
mRearDisplayStateRequest = null;
mDeviceStateManager.cancelStateRequest();
- } else {
- throw new IllegalStateException(
- "Unable to cancel a rear display session as there is no active session");
}
}
}
@@ -432,10 +429,6 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
synchronized (mLock) {
if (mRearDisplayPresentationController != null) {
mDeviceStateManager.cancelStateRequest();
- } else {
- throw new IllegalStateException(
- "Unable to cancel a rear display presentation session as there is no "
- + "active session");
}
}
}
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
index 5f9dbdb60516..da8abde8407f 100644
--- a/libs/WindowManager/Shell/res/values-television/config.xml
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -44,6 +44,12 @@
if a custom action is present before closing it. -->
<integer name="config_pipForceCloseDelay">5000</integer>
+ <!-- Animation duration when exit starting window: fade out icon -->
+ <integer name="starting_window_app_reveal_icon_fade_out_duration">500</integer>
+
+ <!-- Animation delay when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_delay">0</integer>
+
<!-- Animation duration when exit starting window: reveal app -->
<integer name="starting_window_app_reveal_anim_duration">500</integer>
diff --git a/libs/WindowManager/Shell/res/values-watch/config.xml b/libs/WindowManager/Shell/res/values-watch/config.xml
new file mode 100644
index 000000000000..03736edc4ec6
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values-watch/config.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<!-- These resources are around just to allow their values to be customized
+ for watch products. Do not translate. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Animation duration when exit starting window: fade out icon -->
+ <integer name="starting_window_app_reveal_icon_fade_out_duration">50</integer>
+
+ <!-- Animation delay when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_delay">50</integer>
+
+ <!-- Animation duration when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_duration">200</integer>
+
+ <!-- Default animation type when hiding the starting window. The possible values are:
+ - 0 for radial vanish + slide up
+ - 1 for fade out -->
+ <integer name="starting_window_exit_animation_type">1</integer>
+</resources>
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 9ce01184c044..f8dd208f96db 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
@@ -17,7 +17,6 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -61,6 +60,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksLi
import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.TO_DESKTOP_INDICATOR
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.splitscreen.SplitScreenController
+import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
@@ -213,6 +213,7 @@ class DesktopTasksController(
"DesktopTasksController: moveToDesktop taskId=%d",
task.taskId
)
+ exitSplitIfApplicable(wct, task)
// Bring other apps to front first
bringDesktopAppsToFront(task.displayId, wct)
addMoveToDesktopChanges(wct, task)
@@ -240,6 +241,7 @@ class DesktopTasksController(
taskInfo.taskId
)
val wct = WindowContainerTransaction()
+ exitSplitIfApplicable(wct, taskInfo)
moveHomeTaskToFront(wct)
addMoveToDesktopChanges(wct, taskInfo)
wct.setBounds(taskInfo.token, startBounds)
@@ -319,7 +321,7 @@ class DesktopTasksController(
task.taskId
)
val wct = WindowContainerTransaction()
- wct.setWindowingMode(task.token, WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW)
+ wct.setWindowingMode(task.token, WINDOWING_MODE_MULTI_WINDOW)
wct.setBounds(task.token, Rect())
wct.setDensityDpi(task.token, getDefaultDensityDpi())
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -329,6 +331,13 @@ class DesktopTasksController(
}
}
+ private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
+ if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+ splitScreenController.prepareExitSplitScreen(wct,
+ splitScreenController.getStageOfTask(taskInfo.taskId), EXIT_REASON_ENTER_DESKTOP)
+ }
+ }
+
/**
* The second part of the animated move to desktop transition, called after
* {@link startMoveToDesktop}. Move a task to fullscreen after being dragged from fullscreen
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 016771d5ad9c..6cd33bc342c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -226,6 +226,13 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
private ArrayList<TaskState> mPausingTasks = null;
/**
+ * List of tasks were pausing but closed in a subsequent merged transition. If a
+ * closing task is reopened, the leash is not initially hidden since it is already
+ * visible.
+ */
+ private ArrayList<TaskState> mClosingTasks = null;
+
+ /**
* List of tasks that we are switching to. Upon finish, these will remain visible and
* on top.
*/
@@ -376,6 +383,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
}
mFinishTransaction = null;
mPausingTasks = null;
+ mClosingTasks = null;
mOpeningTasks = null;
mInfo = null;
mTransition = null;
@@ -420,6 +428,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
mFinishCB = finishCB;
mFinishTransaction = finishT;
mPausingTasks = new ArrayList<>();
+ mClosingTasks = new ArrayList<>();
mOpeningTasks = new ArrayList<>();
mLeashMap = new ArrayMap<>();
mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0;
@@ -572,7 +581,9 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
final TransitionUtil.LeafTaskFilter leafTaskFilter =
new TransitionUtil.LeafTaskFilter();
boolean hasTaskChange = false;
- for (int i = 0; i < info.getChanges().size(); ++i) {
+ // Walk backwards so that higher z-order changes are recorded *last* in the assorted
+ // task lists. This way, when the are added, the on-top tasks are drawn on top.
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo != null
@@ -671,7 +682,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
final TransitionInfo.Change change = closingTasks.get(i);
final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
if (pausingIdx >= 0) {
- mPausingTasks.remove(pausingIdx);
+ // We are closing the pausing task, but it is still visible and can be
+ // restart by another transition prior to this transition finishing
+ final TaskState closingTask = mPausingTasks.remove(pausingIdx);
+ mClosingTasks.add(closingTask);
didMergeThings = true;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
" closing pausing taskId=%d", change.getTaskInfo().taskId);
@@ -707,7 +721,12 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
for (int i = 0; i < openingTasks.size(); ++i) {
final TransitionInfo.Change change = openingTasks.get(i);
final boolean isLeaf = openingTaskIsLeafs.get(i) == 1;
- int pausingIdx = TaskState.indexOf(mPausingTasks, change);
+ final int closingIdx = TaskState.indexOf(mClosingTasks, change);
+ if (closingIdx >= 0) {
+ // Remove opening tasks from closing set
+ mClosingTasks.remove(closingIdx);
+ }
+ final int pausingIdx = TaskState.indexOf(mPausingTasks, change);
if (pausingIdx >= 0) {
// Something is showing/opening a previously-pausing app.
if (isLeaf) {
@@ -730,12 +749,23 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
appearedTargets[nextTargetIdx++] = target;
// reparent into the original `mInfo` since that's where we are animating.
final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo);
+ final boolean wasClosing = closingIdx >= 0;
t.reparent(target.leash, mInfo.getRoot(rootIdx).getLeash());
t.setLayer(target.leash, layer);
- // Hide the animation leash, let listener show it.
- t.hide(target.leash);
+ if (wasClosing) {
+ // App was previously visible and is closing
+ t.show(target.leash);
+ t.setAlpha(target.leash, 1f);
+ // Also override the task alpha as it was set earlier when dispatching
+ // the transition and setting up the leash to hide the
+ t.setAlpha(change.getLeash(), 1f);
+ } else {
+ // Hide the animation leash, let the listener show it
+ t.hide(target.leash);
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- " opening new leaf taskId=%d", target.taskId);
+ " opening new leaf taskId=%d wasClosing=%b",
+ target.taskId, wasClosing);
mOpeningTasks.add(new TaskState(change, target.leash));
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
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 991b699161ea..f70b0fc5af48 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
@@ -23,7 +23,6 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
-
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
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;
@@ -131,6 +130,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
public static final int EXIT_REASON_RECREATE_SPLIT = 10;
public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 11;
+ public static final int EXIT_REASON_ENTER_DESKTOP = 12;
@IntDef(value = {
EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -144,6 +144,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
EXIT_REASON_CHILD_TASK_ENTER_PIP,
EXIT_REASON_RECREATE_SPLIT,
EXIT_REASON_FULLSCREEN_SHORTCUT,
+ EXIT_REASON_ENTER_DESKTOP
})
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
@@ -1011,6 +1012,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return "CHILD_TASK_ENTER_PIP";
case EXIT_REASON_RECREATE_SPLIT:
return "RECREATE_SPLIT";
+ case EXIT_REASON_ENTER_DESKTOP:
+ return "ENTER_DESKTOP";
default:
return "unknown reason, reason int = " + exitReason;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 5483fa5d29f6..f4ab2266179a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -25,6 +25,7 @@ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED_
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
@@ -42,6 +43,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ENTER_DESKTOP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
@@ -192,6 +194,8 @@ public class SplitscreenEventLogger {
return SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT;
case EXIT_REASON_FULLSCREEN_SHORTCUT:
return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
+ case EXIT_REASON_ENTER_DESKTOP:
+ return SPLITSCREEN_UICHANGED__EXIT_REASON__ENTER_DESKTOP;
case EXIT_REASON_UNKNOWN:
// Fall through
default:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index cb76044da7e5..edb5aba1e46b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -20,7 +20,6 @@ import static android.view.View.GONE;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_EXIT_ANIM;
import android.animation.Animator;
-import android.annotation.IntDef;
import android.content.Context;
import android.graphics.Rect;
import android.util.Slog;
@@ -47,7 +46,7 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
private final int mIconFadeOutDuration;
private final int mAppRevealDelay;
private final int mAppRevealDuration;
- @ExitAnimationType
+ @SplashScreenExitAnimationUtils.ExitAnimationType
private final int mAnimationType;
private final int mAnimationDuration;
private final float mIconStartAlpha;
@@ -58,24 +57,6 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
private Runnable mFinishCallback;
- /**
- * This splash screen exit animation type uses a radial vanish to hide
- * the starting window and slides up the main window content.
- */
- private static final int TYPE_RADIAL_VANISH_SLIDE_UP = 0;
-
- /**
- * This splash screen exit animation type fades out the starting window
- * to reveal the main window content.
- */
- private static final int TYPE_FADE_OUT = 1;
-
- @IntDef(prefix = { "TYPE_" }, value = {
- TYPE_RADIAL_VANISH_SLIDE_UP,
- TYPE_FADE_OUT,
- })
- private @interface ExitAnimationType {}
-
SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish,
float roundedCornerRadius) {
@@ -121,15 +102,10 @@ public class SplashScreenExitAnimation implements Animator.AnimatorListener {
}
void startAnimations() {
- if (mAnimationType == TYPE_FADE_OUT) {
- SplashScreenExitAnimationUtils.startFadeOutAnimation(mSplashScreenView,
- mAnimationDuration, this);
- } else {
- SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
- mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
- mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
- mAppRevealDuration, this, mRoundedCornerRadius);
- }
+ SplashScreenExitAnimationUtils.startAnimations(mAnimationType, mSplashScreenView,
+ mFirstWindowSurface, mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame,
+ mAnimationDuration, mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha,
+ mAppRevealDelay, mAppRevealDuration, this, mRoundedCornerRadius);
}
private void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index 64f09a77c839..e86b62dee86d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -20,6 +20,7 @@ import static android.view.Choreographer.CALLBACK_COMMIT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.IntDef;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
@@ -54,6 +55,7 @@ import com.android.wm.shell.common.TransactionPool;
public class SplashScreenExitAnimationUtils {
private static final boolean DEBUG_EXIT_ANIMATION = false;
private static final boolean DEBUG_EXIT_ANIMATION_BLEND = false;
+ private static final boolean DEBUG_EXIT_FADE_ANIMATION = false;
private static final String TAG = "SplashScreenExitAnimationUtils";
private static final Interpolator ICON_INTERPOLATOR = new PathInterpolator(0.15f, 0f, 1f, 1f);
@@ -62,19 +64,47 @@ public class SplashScreenExitAnimationUtils {
private static final Interpolator SHIFT_UP_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f);
/**
+ * This splash screen exit animation type uses a radial vanish to hide
+ * the starting window and slides up the main window content.
+ * @hide
+ */
+ public static final int TYPE_RADIAL_VANISH_SLIDE_UP = 0;
+
+ /**
+ * This splash screen exit animation type fades out the starting window
+ * to reveal the main window content.
+ * @hide
+ */
+ public static final int TYPE_FADE_OUT = 1;
+
+ /** @hide */
+ @IntDef(prefix = { "TYPE_" }, value = {
+ TYPE_RADIAL_VANISH_SLIDE_UP,
+ TYPE_FADE_OUT,
+ })
+ public @interface ExitAnimationType {}
+
+ /**
* Creates and starts the animator to fade out the icon, reveal the app, and shift up main
* window with rounded corner radius.
*/
- static void startAnimations(ViewGroup splashScreenView,
- SurfaceControl firstWindowSurface, int mainWindowShiftLength,
- TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
- int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
- int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
- float roundedCornerRadius) {
- ValueAnimator animator = createRadialVanishSlideUpAnimator(splashScreenView,
- firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame,
- animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha,
- appRevealDelay, appRevealDuration, animatorListener, roundedCornerRadius);
+ static void startAnimations(@ExitAnimationType int animationType,
+ ViewGroup splashScreenView, SurfaceControl firstWindowSurface,
+ int mainWindowShiftLength, TransactionPool transactionPool, Rect firstWindowFrame,
+ int animationDuration, int iconFadeOutDuration, float iconStartAlpha,
+ float brandingStartAlpha, int appRevealDelay, int appRevealDuration,
+ Animator.AnimatorListener animatorListener, float roundedCornerRadius) {
+ ValueAnimator animator;
+ if (animationType == TYPE_FADE_OUT) {
+ animator = createFadeOutAnimation(splashScreenView, animationDuration,
+ iconFadeOutDuration, iconStartAlpha, brandingStartAlpha, appRevealDelay,
+ appRevealDuration, animatorListener);
+ } else {
+ animator = createRadialVanishSlideUpAnimator(splashScreenView,
+ firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame,
+ animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha,
+ appRevealDelay, appRevealDuration, animatorListener, roundedCornerRadius);
+ }
animator.start();
}
@@ -88,10 +118,11 @@ public class SplashScreenExitAnimationUtils {
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
- startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength,
- transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
- iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
- animatorListener, 0f /* roundedCornerRadius */);
+ // Start the default 'reveal' animation.
+ startAnimations(TYPE_RADIAL_VANISH_SLIDE_UP, splashScreenView,
+ firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame,
+ animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha,
+ appRevealDelay, appRevealDuration, animatorListener, 0f /* roundedCornerRadius */);
}
/**
@@ -209,18 +240,57 @@ public class SplashScreenExitAnimationUtils {
return nightMode == Configuration.UI_MODE_NIGHT_YES;
}
- static void startFadeOutAnimation(ViewGroup splashScreenView,
- int animationDuration, Animator.AnimatorListener animatorListener) {
- final ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
+ private static ValueAnimator createFadeOutAnimation(ViewGroup splashScreenView,
+ int animationDuration, int iconFadeOutDuration, float iconStartAlpha,
+ float brandingStartAlpha, int appRevealDelay, int appRevealDuration,
+ Animator.AnimatorListener animatorListener) {
+
+ if (DEBUG_EXIT_FADE_ANIMATION) {
+ splashScreenView.setBackgroundColor(Color.BLUE);
+ }
+
+ final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.setDuration(animationDuration);
animator.setInterpolator(Interpolators.LINEAR);
animator.addUpdateListener(animation -> {
- splashScreenView.setAlpha((float) animation.getAnimatedValue());
+
+ float linearProgress = (float) animation.getAnimatedValue();
+
+ // Icon fade out progress (always starts immediately)
+ final float iconFadeProgress = ICON_INTERPOLATOR.getInterpolation(getProgress(
+ linearProgress, 0 /* delay */, iconFadeOutDuration, animationDuration));
+ View iconView = null;
+ View brandingView = null;
+
+ if (splashScreenView instanceof SplashScreenView) {
+ iconView = ((SplashScreenView) splashScreenView).getIconView();
+ brandingView = ((SplashScreenView) splashScreenView).getBrandingView();
+ }
+ if (iconView != null) {
+ iconView.setAlpha(iconStartAlpha * (1f - iconFadeProgress));
+ }
+ if (brandingView != null) {
+ brandingView.setAlpha(brandingStartAlpha * (1f - iconFadeProgress));
+ }
+
+ // Splash screen fade out progress (possibly delayed)
+ final float splashFadeProgress = Interpolators.ALPHA_OUT.getInterpolation(
+ getProgress(linearProgress, appRevealDelay,
+ appRevealDuration, animationDuration));
+
+ splashScreenView.setAlpha(1f - splashFadeProgress);
+
+ if (DEBUG_EXIT_FADE_ANIMATION) {
+ Slog.d(TAG, "progress -> animation: " + linearProgress
+ + "\t icon alpha: " + ((iconView != null) ? iconView.getAlpha() : "n/a")
+ + "\t splash alpha: " + splashScreenView.getAlpha()
+ );
+ }
});
if (animatorListener != null) {
animator.addListener(animatorListener);
}
- animator.start();
+ return animator;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index adae21b20b3c..93d763608b5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -54,12 +54,13 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
private static final String TAG = TaskViewTaskController.class.getSimpleName();
private final CloseGuard mGuard = new CloseGuard();
-
+ private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ /** Used to inset the activity content to allow space for a caption bar. */
+ private final Binder mCaptionInsetsOwner = new Binder();
private final ShellTaskOrganizer mTaskOrganizer;
private final Executor mShellExecutor;
private final SyncTransactionQueue mSyncQueue;
private final TaskViewTransitions mTaskViewTransitions;
- private TaskViewBase mTaskViewBase;
private final Context mContext;
/**
@@ -70,21 +71,19 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
* in this situation to allow us to notify listeners correctly if the task failed to open.
*/
private ActivityManager.RunningTaskInfo mPendingInfo;
- /* Indicates that the task we attempted to launch in the task view failed to launch. */
- private boolean mTaskNotFound;
+ private TaskViewBase mTaskViewBase;
protected ActivityManager.RunningTaskInfo mTaskInfo;
private WindowContainerToken mTaskToken;
private SurfaceControl mTaskLeash;
- private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ /* Indicates that the task we attempted to launch in the task view failed to launch. */
+ private boolean mTaskNotFound;
private boolean mSurfaceCreated;
private SurfaceControl mSurfaceControl;
private boolean mIsInitialized;
private boolean mNotifiedForInitialized;
+ private boolean mHideTaskWithSurface = true;
private TaskView.Listener mListener;
private Executor mListenerExecutor;
-
- /** Used to inset the activity content to allow space for a caption bar. */
- private final Binder mCaptionInsetsOwner = new Binder();
private Rect mCaptionInsets;
public TaskViewTaskController(Context context, ShellTaskOrganizer organizer,
@@ -102,6 +101,19 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
mGuard.open("release");
}
+ /**
+ * Specifies if the task should be hidden when the surface is destroyed.
+ * <p>This is {@code true} by default.
+ *
+ * @param hideTaskWithSurface {@code false} if task needs to remain visible even when the
+ * surface is destroyed, {@code true} otherwise.
+ */
+ public void setHideTaskWithSurface(boolean hideTaskWithSurface) {
+ // TODO(b/299535374): Remove mHideTaskWithSurface once the taskviews with launch root tasks
+ // are moved to a window in SystemUI in auto.
+ mHideTaskWithSurface = hideTaskWithSurface;
+ }
+
SurfaceControl getSurfaceControl() {
return mSurfaceControl;
}
@@ -257,9 +269,17 @@ public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener {
mTaskNotFound = false;
}
+ /** This method shouldn't be called when shell transitions are enabled. */
private void updateTaskVisibility() {
+ boolean visible = mSurfaceCreated;
+ if (!visible && !mHideTaskWithSurface) {
+ return;
+ }
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
+ wct.setHidden(mTaskToken, !visible /* hidden */);
+ if (!visible) {
+ wct.reorder(mTaskToken, false /* onTop */);
+ }
mSyncQueue.queue(wct);
if (mListener == null) {
return;
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 026d0cdd7c52..b4e181808194 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
@@ -68,6 +68,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -348,13 +349,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final int id = v.getId();
if (id == R.id.close_window || id == R.id.close_button) {
mTaskOperations.closeTask(mTaskToken);
- if (mSplitScreenController != null
- && mSplitScreenController.isSplitScreenVisible()) {
- int remainingTaskPosition = mTaskId == mSplitScreenController
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT).taskId
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
- ActivityManager.RunningTaskInfo remainingTask = mSplitScreenController
- .getTaskInfo(remainingTaskPosition);
+ if (isTaskInSplitScreen(mTaskId)) {
+ RunningTaskInfo remainingTask = getOtherSplitTask(mTaskId);
mSplitScreenController.moveTaskToFullscreen(remainingTask.taskId);
}
decoration.closeMaximizeMenu();
@@ -376,6 +372,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
decoration.incrementRelayoutBlock();
mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
+ closeOtherSplitTask(mTaskId);
}
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
@@ -720,6 +717,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
relevantDecor.mTaskInfo.displayId);
if (ev.getY() > statusBarHeight) {
if (mMoveToDesktopAnimator == null) {
+ closeOtherSplitTask(relevantDecor.mTaskInfo.taskId);
mMoveToDesktopAnimator = new MoveToDesktopAnimator(
mDragToDesktopAnimationStartBounds, relevantDecor.mTaskInfo,
relevantDecor.mTaskSurface);
@@ -810,7 +808,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
if (mSplitScreenController != null && mSplitScreenController.isSplitScreenVisible()) {
// We can't look at focused task here as only one task will have focus.
- return getSplitScreenDecor(ev);
+ DesktopModeWindowDecoration splitTaskDecor = getSplitScreenDecor(ev);
+ return splitTaskDecor == null ? getFocusedDecor() : splitTaskDecor;
} else {
return getFocusedDecor();
}
@@ -942,6 +941,24 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
}
+ private RunningTaskInfo getOtherSplitTask(int taskId) {
+ @SplitPosition int remainingTaskPosition = mSplitScreenController
+ .getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT
+ ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
+ return mSplitScreenController.getTaskInfo(remainingTaskPosition);
+ }
+
+ private void closeOtherSplitTask(int taskId) {
+ if (isTaskInSplitScreen(taskId)) {
+ mTaskOperations.closeTask(getOtherSplitTask(taskId).token);
+ }
+ }
+
+ private boolean isTaskInSplitScreen(int taskId) {
+ return mSplitScreenController != null
+ && mSplitScreenController.isTaskInSplitScreen(taskId);
+ }
+
private class DragStartListenerImpl
implements DragPositioningCallbackUtility.DragStartListener {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 8d76fc6c542b..a26927586b61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.windowdecor;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
@@ -220,6 +221,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final Resources resources = mDecorWindowContext.getResources();
final Configuration taskConfig = mTaskInfo.getConfiguration();
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
+ final boolean isFullscreen = taskConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN;
outResult.mWidth = taskBounds.width();
outResult.mHeight = taskBounds.height();
@@ -279,13 +282,24 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
final Point taskPosition = mTaskInfo.positionInParent;
- startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
- .setShadowRadius(mTaskSurface, shadowRadius)
+ if (isFullscreen) {
+ // Setting the task crop to the width/height stops input events from being sent to
+ // some regions of the app window. See b/300324920
+ // TODO(b/296921174): investigate whether crop/position needs to be set by window
+ // decorations at all when transition handlers are already taking ownership of the task
+ // surface placement/crop, especially when in fullscreen where tasks cannot be
+ // drag-resized by the window decoration.
+ startT.setWindowCrop(mTaskSurface, null);
+ finishT.setWindowCrop(mTaskSurface, null);
+ } else {
+ startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ }
+ startT.setShadowRadius(mTaskSurface, shadowRadius)
.setColor(mTaskSurface, mTmpColor)
.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
- .setShadowRadius(mTaskSurface, shadowRadius)
- .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ .setShadowRadius(mTaskSurface, shadowRadius);
if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
index c335d3dc7f4b..2bd6d5777ff6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -80,7 +80,9 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) :
secondAppForSplitScreen.launchViaIntent(wmHelper)
pipApp.launchViaIntent(wmHelper)
tapl.goHome()
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen)
+ SplitScreenUtils.enterSplit(
+ wmHelper, tapl, device, pipApp, secondAppForSplitScreen,
+ flicker.scenario.startRotation)
pipApp.enableAutoEnterForPipActivity()
}
teardown {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
index 245184cf0327..80ab24ddf9ef 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt
@@ -51,7 +51,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
index 1f2f1ecc0aab..4c391047e853 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt
@@ -49,7 +49,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
index ebbf7c5026c7..f6d1afc05a5a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt
@@ -49,7 +49,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
index 71e701c9e2be..db5a32a382fb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt
@@ -49,7 +49,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
index f2cbf24bd26e..3a6a4a17811b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt
@@ -61,7 +61,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@Test
open fun enterSplitScreenFromOverview() {
- SplitScreenUtils.splitFromOverview(tapl, device)
+ SplitScreenUtils.splitFromOverview(tapl, device, rotation)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
index 538ed960dac6..6330d33b4be6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt
@@ -50,9 +50,10 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
fun setup() {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
+
tapl.workspace.switchToOverview().dismissAllTasks()
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
index 0dab5ad622ad..64b75c5fd967 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt
@@ -50,7 +50,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
thirdApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
index ad3a2d41f02a..179501089168 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt
@@ -49,7 +49,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
index b780a1681762..6ff8c53cbac8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt
@@ -50,7 +50,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setExpectedRotation(rotation.value)
tapl.workspace.switchToOverview().dismissAllTasks()
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
index 329d61dcdbf9..251cb50de017 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt
@@ -51,8 +51,8 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
tapl.setEnableRotation(true)
tapl.setExpectedRotation(rotation.value)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp, rotation)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp, rotation)
SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
index e59ed6491eca..13875362a1c8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
@@ -62,8 +62,10 @@ class SwitchBetweenSplitPairsNoPip(override val flicker: LegacyFlickerTest) :
get() = {
setup {
tapl.goHome()
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, pipApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+ secondaryApp, flicker.scenario.startRotation)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, pipApp,
+ flicker.scenario.startRotation)
pipApp.enableAutoEnterForPipActivity()
SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, pipApp)
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
index 4d9007093cea..3b9e53f9ce04 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt
@@ -39,7 +39,8 @@ abstract class CopyContentInSplitBenchmark(override val flicker: LegacyFlickerTe
protected val popupWindowLayer = ComponentNameMatcher("", "PopupWindow:")
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+ textEditApp, flicker.scenario.startRotation) }
transitions {
SplitScreenUtils.copyContentInSplit(
instrumentation,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
index 8360e94a6c3e..5fdde3ad23d2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt
@@ -35,7 +35,8 @@ abstract class DismissSplitScreenByDividerBenchmark(override val flicker: Legacy
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+ secondaryApp, flicker.scenario.startRotation) }
transitions {
if (tapl.isTablet) {
SplitScreenUtils.dragDividerToDismissSplit(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
index e74587843a72..b7f6bfe7efd6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt
@@ -35,7 +35,10 @@ abstract class DismissSplitScreenByGoHomeBenchmark(override val flicker: LegacyF
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup {
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp,
+ flicker.scenario.startRotation)
+ }
transitions {
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
index c3beb366cc66..bb2a7aabed1b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt
@@ -37,7 +37,8 @@ abstract class DragDividerToResizeBenchmark(override val flicker: LegacyFlickerT
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+ secondaryApp, flicker.scenario.startRotation) }
transitions { SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
index be507d833986..5e5e7d7fc3c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt
@@ -46,7 +46,7 @@ abstract class EnterSplitScreenFromOverviewBenchmark(override val flicker: Legac
.waitForAndVerify()
}
transitions {
- SplitScreenUtils.splitFromOverview(tapl, device)
+ SplitScreenUtils.splitFromOverview(tapl, device, flicker.scenario.startRotation)
SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
index ed0debd01408..46b0bd226daf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt
@@ -39,7 +39,8 @@ abstract class SwitchAppByDoubleTapDividerBenchmark(override val flicker: Legacy
SplitScreenBase(flicker) {
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
- setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) }
+ setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+ secondaryApp, flicker.scenario.startRotation) }
transitions {
SplitScreenUtils.doubleTapDividerToSwitch(device)
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
index 9b7939a3a006..baf76932c7ac 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt
@@ -39,7 +39,8 @@ abstract class SwitchBackToSplitFromAnotherAppBenchmark(override val flicker: Le
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+ secondaryApp, flicker.scenario.startRotation)
thirdApp.launchViaIntent(wmHelper)
wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
index 9326ef3024a4..33b55f1a57d8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt
@@ -37,7 +37,8 @@ abstract class SwitchBackToSplitFromHomeBenchmark(override val flicker: LegacyFl
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+ secondaryApp, flicker.scenario.startRotation)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
index b928e40108bf..b79dfb5f0665 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt
@@ -37,7 +37,8 @@ abstract class SwitchBackToSplitFromRecentBenchmark(override val flicker: Legacy
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+ secondaryApp, flicker.scenario.startRotation)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
index f314995fa947..0204d754585a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt
@@ -39,8 +39,10 @@ abstract class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlic
protected val thisTransition: FlickerBuilder.() -> Unit
get() = {
setup {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp,
+ secondaryApp, flicker.scenario.startRotation)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp,
+ flicker.scenario.startRotation)
SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
}
transitions {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
index 3f8a1ae6bd79..87e3860a85f8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/SplitScreenUtils.kt
@@ -19,6 +19,7 @@ package com.android.wm.shell.flicker.utils
import android.app.Instrumentation
import android.graphics.Point
import android.os.SystemClock
+import android.tools.common.Rotation
import android.tools.common.traces.component.ComponentNameMatcher
import android.tools.common.traces.component.IComponentMatcher
import android.tools.common.traces.component.IComponentNameMatcher
@@ -41,6 +42,7 @@ import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary
import org.junit.Assert.assertNotNull
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
object SplitScreenUtils {
private const val TIMEOUT_MS = 3_000L
@@ -101,13 +103,14 @@ object SplitScreenUtils {
tapl: LauncherInstrumentation,
device: UiDevice,
primaryApp: StandardAppHelper,
- secondaryApp: StandardAppHelper
+ secondaryApp: StandardAppHelper,
+ rotation: Rotation
) {
primaryApp.launchViaIntent(wmHelper)
secondaryApp.launchViaIntent(wmHelper)
tapl.goHome()
wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
- splitFromOverview(tapl, device)
+ splitFromOverview(tapl, device, rotation)
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
@@ -121,7 +124,7 @@ object SplitScreenUtils {
waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
}
- fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
+ fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice, rotation: Rotation) {
// Note: The initial split position in landscape is different between tablet and phone.
// In landscape, tablet will let the first app split to right side, and phone will
// split to left side.
@@ -129,7 +132,9 @@ object SplitScreenUtils {
// TAPL's currentTask on tablet is sometimes not what we expected if the overview
// contains more than 3 task views. We need to use uiautomator directly to find the
// second task to split.
- tapl.workspace.switchToOverview().overviewActions.clickSplit()
+ val home = tapl.workspace.switchToOverview()
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ home.overviewActions.clickSplit()
val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
if (snapshots == null || snapshots.size < 1) {
error("Fail to find a overview snapshot to split.")
@@ -145,9 +150,10 @@ object SplitScreenUtils {
}
snapshots[0].click()
} else {
- tapl.workspace
+ val home = tapl.workspace
.switchToOverview()
- .currentTask
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ home.currentTask
.tapMenu()
.tapSplitMenuItem()
.currentTask
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
index 9f0d89bc3128..52375850b9a5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
@@ -27,15 +27,13 @@ import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -66,7 +64,7 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
@Before
fun setup() {
- launcherApps = mock(LauncherApps::class.java)
+ launcherApps = mock<LauncherApps>()
repository = BubbleVolatileRepository(launcherApps)
}
@@ -98,7 +96,7 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
repository.addBubbles(user11.identifier, listOf(bubble12))
assertEquals(listOf(bubble11, bubble12), repository.getEntities(user11.identifier))
- Mockito.verifyNoMoreInteractions(launcherApps)
+ verifyNoMoreInteractions(launcherApps)
}
@Test
@@ -167,9 +165,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
assertThat(ret).isTrue() // bubbles were removed
assertThat(repository.getEntities(user0.identifier).toList()).isEmpty()
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
}
@Test
@@ -184,9 +181,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
assertThat(repository.getEntities(user0.identifier).toList())
.isEqualTo(listOf(bubble1, bubble3))
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
}
@Test
@@ -200,9 +196,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
assertThat(repository.getEntities(user0.identifier).toList())
.isEqualTo(listOf(bubble1, bubble2, bubble3))
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
}
@Test
@@ -219,9 +214,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
user11.identifier))
assertThat(ret).isFalse() // bubbles were NOT removed
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
}
@Test
@@ -237,9 +231,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
assertThat(ret).isTrue() // bubbles were removed
assertThat(repository.getEntities(user0.identifier).toList()).isEmpty()
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
// User 11 bubbles should still be here
assertThat(repository.getEntities(user11.identifier).toList())
@@ -261,9 +254,8 @@ class BubbleVolatileRepositoryTest : ShellTestCase() {
// bubble2 is the work profile bubble and should be removed
assertThat(repository.getEntities(user0.identifier).toList())
.isEqualTo(listOf(bubble1, bubble3))
- verify(launcherApps, never()).uncacheShortcuts(anyString(),
- any(),
- any(UserHandle::class.java), anyInt())
+ verify(launcherApps, never())
+ .uncacheShortcuts(any<String>(), any(), any<UserHandle>(), any<Int>())
// User 11 bubbles should still be here
assertThat(repository.getEntities(user11.identifier).toList())
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 be4a287bff9d..c6cccc059e89 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
@@ -52,6 +52,8 @@ import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
+import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask
+import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
@@ -94,6 +96,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
ToggleResizeDesktopTaskTransitionHandler
@Mock lateinit var launchAdjacentController: LaunchAdjacentController
@Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
+ @Mock lateinit var splitScreenController: SplitScreenController
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
@@ -116,6 +119,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
controller = createController()
+ controller.splitScreenController = splitScreenController
shellInit.init()
}
@@ -341,6 +345,30 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ fun moveToDesktop_splitTaskExitsSplit() {
+ var task = setUpSplitScreenTask()
+ controller.moveToDesktop(desktopModeWindowDecoration, task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(splitScreenController).prepareExitSplitScreen(any(), anyInt(),
+ eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP)
+ )
+ }
+
+ @Test
+ fun moveToDesktop_fullscreenTaskDoesNotExitSplit() {
+ var task = setUpFullscreenTask()
+ controller.moveToDesktop(desktopModeWindowDecoration, task)
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ verify(splitScreenController, never()).prepareExitSplitScreen(any(), anyInt(),
+ eq(SplitScreenController.EXIT_REASON_ENTER_DESKTOP)
+ )
+ }
+
+ @Test
fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
val task = setUpFreeformTask()
task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
@@ -695,6 +723,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
return task
}
+ private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ val task = createSplitScreenTask(displayId)
+ whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
+ runningTasks.add(task)
+ return task
+ }
+
private fun markTaskVisible(task: RunningTaskInfo) {
desktopModeTaskRepository.updateVisibleFreeformTasks(
task.displayId,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
index 29a757c19d98..2f6f3207137d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTestHelpers.kt
@@ -21,6 +21,7 @@ import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.view.Display.DEFAULT_DISPLAY
import com.android.wm.shell.MockToken
import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -45,12 +46,25 @@ class DesktopTestHelpers {
@JvmOverloads
fun createFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
return TestRunningTaskInfoBuilder()
- .setDisplayId(displayId)
- .setToken(MockToken().token())
- .setActivityType(ACTIVITY_TYPE_STANDARD)
- .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
- .setLastActiveTime(100)
- .build()
+ .setDisplayId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build()
+ }
+
+ /** Create a task that has windowing mode set to [WINDOWING_MODE_MULTI_WINDOW] */
+ @JvmStatic
+ @JvmOverloads
+ fun createSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
+ return TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setToken(MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+ .setLastActiveTime(100)
+ .build()
}
/** Create a new home task */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
index d098d332a376..0088051928fb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/taskview/TaskViewTest.java
@@ -221,6 +221,20 @@ public class TaskViewTest extends ShellTestCase {
}
@Test
+ public void testSurfaceDestroyed_withTask_shouldNotHideTask_legacyTransitions() {
+ assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
+ mTaskViewTaskController.setHideTaskWithSurface(false);
+
+ SurfaceHolder sh = mock(SurfaceHolder.class);
+ mTaskViewTaskController.onTaskAppeared(mTaskInfo, mLeash);
+ mTaskView.surfaceCreated(sh);
+ reset(mViewListener);
+ mTaskView.surfaceDestroyed(sh);
+
+ verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean());
+ }
+
+ @Test
public void testSurfaceDestroyed_withTask_legacyTransitions() {
assumeFalse(Transitions.ENABLE_SHELL_TRANSITIONS);
SurfaceHolder sh = mock(SurfaceHolder.class);
diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS
index 436f10737cb4..ef4cc46cb1c8 100644
--- a/libs/androidfw/OWNERS
+++ b/libs/androidfw/OWNERS
@@ -1,5 +1,4 @@
set noparent
-toddke@google.com
zyy@google.com
patb@google.com
diff --git a/packages/CredentialManager/AndroidManifest.xml b/packages/CredentialManager/AndroidManifest.xml
index 4161601e2317..d9ef3a2151ce 100644
--- a/packages/CredentialManager/AndroidManifest.xml
+++ b/packages/CredentialManager/AndroidManifest.xml
@@ -17,31 +17,42 @@
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.credentialmanager">
+ package="com.android.credentialmanager">
<uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
<uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
<application
- android:allowBackup="true"
- android:dataExtractionRules="@xml/data_extraction_rules"
- android:fullBackupContent="@xml/backup_rules"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/Theme.CredentialSelector">
-
- <activity
- android:name=".CredentialSelectorActivity"
- android:exported="true"
- android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR"
- android:launchMode="singleTop"
+ android:allowBackup="true"
+ android:dataExtractionRules="@xml/data_extraction_rules"
+ android:fullBackupContent="@xml/backup_rules"
+ android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
- android:excludeFromRecents="true"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
android:theme="@style/Theme.CredentialSelector">
- </activity>
- </application>
+
+ <activity
+ android:name=".CredentialSelectorActivity"
+ android:exported="true"
+ android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR"
+ android:launchMode="singleTop"
+ android:label="@string/app_name"
+ android:excludeFromRecents="true"
+ android:theme="@style/Theme.CredentialSelector">
+ </activity>
+ <service
+ android:name=".autofill.CredentialAutofillService"
+ android:exported="false"
+ android:permission="android.permission.BIND_AUTOFILL_SERVICE">
+ <meta-data
+ android:name="android.autofill"
+ android:resource="@xml/autofill_service_configuration"/>
+ <intent-filter>
+ <action android:name="android.service.autofill.AutofillService"/>
+ </intent-filter>
+ </service>
+ </application>
</manifest>
diff --git a/packages/CredentialManager/res/xml/autofill_service_configuration.xml b/packages/CredentialManager/res/xml/autofill_service_configuration.xml
new file mode 100644
index 000000000000..25cc094fa44e
--- /dev/null
+++ b/packages/CredentialManager/res/xml/autofill_service_configuration.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ Sample backup rules file; uncomment and customize as necessary.
+ See https://developer.android.com/guide/topics/data/autobackup
+ for details.
+ Note: This file is ignored for devices older that API 31
+ See https://developer.android.com/about/versions/12/backup-restore
+-->
+<autofill-service-configuration
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:supportsInlineSuggestions="true"/> \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
new file mode 100644
index 000000000000..943c2b4b3a5b
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.autofill
+
+import android.os.CancellationSignal
+import android.service.autofill.AutofillService
+import android.service.autofill.FillCallback
+import android.service.autofill.FillRequest
+import android.service.autofill.SaveRequest
+import android.service.autofill.SaveCallback
+
+class CredentialAutofillService : AutofillService() {
+ override fun onFillRequest(
+ request: FillRequest,
+ cancellationSignal: CancellationSignal,
+ callback: FillCallback
+ ) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
+ TODO("Not yet implemented")
+ }
+} \ No newline at end of file
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 5bd422bf7894..a16f9f55b466 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -70,15 +70,18 @@
</intent-filter>
</activity>
+ <!-- NOTE: the workaround to fix the screen flash problem. Remember to check the problem
+ is resolved for new implementation -->
<activity android:name=".InstallStaging"
- android:exported="false" />
+ android:theme="@style/Theme.AlertDialogActivity.NoDim"
+ android:exported="false" />
<activity android:name=".DeleteStagedFileOnResult"
android:theme="@style/Theme.AlertDialogActivity.NoActionBar"
android:exported="false" />
<activity android:name=".PackageInstallerActivity"
- android:exported="false" />
+ android:exported="false" />
<activity android:name=".InstallInstalling"
android:theme="@style/Theme.AlertDialogActivity.NoAnimation"
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index 9a062295608d..aa1fa164b021 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -32,4 +32,9 @@
<item name="android:windowNoTitle">true</item>
</style>
+ <style name="Theme.AlertDialogActivity.NoDim">
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:backgroundDimAmount">0</item>
+ </style>
+
</resources>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 80e876196f7f..d97fb5440cbd 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -306,6 +306,7 @@ public class PackageInstallerActivity extends AlertActivity {
}
private void initiateInstall() {
+ bindUi();
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
@@ -445,7 +446,6 @@ public class PackageInstallerActivity extends AlertActivity {
if (mAppSnippet != null) {
// load placeholder layout with OK button disabled until we override this layout in
// startInstallConfirm
- bindUi();
checkIfAllowedAndInitiateInstall();
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 471f3b93b8aa..cfd1a50abb4c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -32,6 +32,7 @@ import com.android.settingslib.spa.gallery.itemList.ItemListPageProvider
import com.android.settingslib.spa.gallery.itemList.ItemOperatePageProvider
import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsOutlinedTextFieldPageProvider
+import com.android.settingslib.spa.gallery.editor.SettingsTextFieldPasswordPageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
@@ -92,6 +93,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
SettingsOutlinedTextFieldPageProvider,
SettingsExposedDropdownMenuBoxPageProvider,
SettingsExposedDropdownMenuCheckBoxProvider,
+ SettingsTextFieldPasswordPageProvider,
),
rootPages = listOf(
HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
index b74af213a058..4875ea99d4d6 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
@@ -39,6 +39,8 @@ object EditorMainPageProvider : SettingsPageProvider {
.build(),
SettingsExposedDropdownMenuCheckBoxProvider.buildInjectEntry().setLink(fromPage = owner)
.build(),
+ SettingsTextFieldPasswordPageProvider.buildInjectEntry().setLink(fromPage = owner)
+ .build(),
)
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsTextFieldPasswordPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsTextFieldPasswordPageProvider.kt
new file mode 100644
index 000000000000..fc51466ee7e6
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/SettingsTextFieldPasswordPageProvider.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.editor
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.editor.SettingsTextFieldPassword
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+
+private const val TITLE = "Sample SettingsTextFieldPassword"
+
+object SettingsTextFieldPasswordPageProvider : SettingsPageProvider {
+ override val name = "SettingsTextFieldPassword"
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ var value by remember { mutableStateOf("value") }
+ RegularScaffold(title = TITLE) {
+ SettingsTextFieldPassword(
+ value = value,
+ label = "label",
+ onTextChange = { value = it })
+ }
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner = createSettingsPage())
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SettingsTextFieldPasswordPagePreview() {
+ SettingsTheme {
+ SettingsTextFieldPasswordPageProvider.Page(null)
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
index 682b4eac0c84..32600943ac80 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
@@ -131,7 +131,8 @@ private fun ActionButtonsPreview() {
val options = listOf("item1", "item2", "item3")
val selectedOptionsState = remember { mutableStateListOf("item1", "item2") }
SettingsTheme {
- SettingsExposedDropdownMenuCheckBox(label = "label",
+ SettingsExposedDropdownMenuCheckBox(
+ label = "label",
options = options,
selectedOptionsState = selectedOptionsState,
enabled = true,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
new file mode 100644
index 000000000000..d0a61882593c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.editor
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.selection.toggleable
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Visibility
+import androidx.compose.material.icons.outlined.VisibilityOff
+import androidx.compose.material3.Icon
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+fun SettingsTextFieldPassword(
+ value: String,
+ label: String,
+ onTextChange: (String) -> Unit,
+) {
+ var visibility by remember { mutableStateOf(false) }
+ OutlinedTextField(
+ modifier = Modifier
+ .padding(SettingsDimension.itemPadding)
+ .fillMaxWidth(),
+ value = value,
+ onValueChange = onTextChange,
+ label = { Text(text = label) },
+ keyboardOptions = KeyboardOptions(
+ keyboardType = KeyboardType.Password,
+ imeAction = ImeAction.Send
+ ),
+ trailingIcon = {
+ Icon(
+ imageVector = if (visibility) Icons.Outlined.VisibilityOff
+ else Icons.Outlined.Visibility,
+ contentDescription = "Visibility Icon",
+ modifier = Modifier
+ .testTag("Visibility Icon")
+ .size(SettingsDimension.itemIconSize)
+ .toggleable(visibility) {
+ visibility = !visibility
+ },
+ )
+ },
+ visualTransformation = if (visibility) VisualTransformation.None
+ else PasswordVisualTransformation()
+ )
+}
+
+@Preview
+@Composable
+private fun SettingsTextFieldPasswordPreview() {
+ var value by remember { mutableStateOf("value") }
+ SettingsTheme {
+ SettingsTextFieldPassword(
+ value = value,
+ label = "label",
+ onTextChange = { value = it },
+ )
+ }
+} \ 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 b77368a429ae..3a0e51b24c2c 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
@@ -32,8 +32,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.asPaddingValues
-import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -49,10 +49,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
@@ -73,7 +71,6 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
-import com.android.settingslib.spa.framework.compose.horizontalValues
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
import kotlin.math.abs
@@ -129,16 +126,10 @@ internal fun CustomizedLargeTopAppBar(
private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) {
Text(
text = title,
- modifier = Modifier
- .padding(
- WindowInsets.navigationBars
- .asPaddingValues()
- .horizontalValues()
- )
- .padding(
- start = SettingsDimension.itemPaddingAround,
- end = SettingsDimension.itemPaddingEnd,
- ),
+ modifier = Modifier.padding(
+ start = SettingsDimension.itemPaddingAround,
+ end = SettingsDimension.itemPaddingEnd,
+ ),
overflow = TextOverflow.Ellipsis,
maxLines = maxLines,
)
@@ -157,6 +148,15 @@ private fun topAppBarColors() = TopAppBarColors(
* Represents the colors used by a top app bar in different states.
* This implementation animates the container color according to the top app bar scroll state. It
* does not animate the leading, headline, or trailing colors.
+ *
+ * @constructor create an instance with arbitrary colors, see [TopAppBarColors] for a
+ * factory method using the default material3 spec
+ * @param containerColor the color used for the background of this BottomAppBar. Use
+ * [Color.Transparent] to have no color.
+ * @param scrolledContainerColor the container color when content is scrolled behind it
+ * @param navigationIconContentColor the content color used for the navigation icon
+ * @param titleContentColor the content color used for the title
+ * @param actionIconContentColor the content color used for actions
*/
@Stable
private class TopAppBarColors(
@@ -248,7 +248,6 @@ private fun SingleRowTopAppBar(
titleTextStyle = titleTextStyle,
titleAlpha = 1f,
titleVerticalArrangement = Arrangement.Center,
- titleHorizontalArrangement = Arrangement.Start,
titleBottomPadding = 0,
hideTitleSemantics = false,
navigationIcon = navigationIcon,
@@ -312,7 +311,7 @@ private fun TwoRowsTopAppBar(
// This will potentially animate or interpolate a transition between the container color and the
// container's scrolled color according to the app bar's scroll state.
val colorTransitionFraction = scrollBehavior?.state?.collapsedFraction ?: 0f
- val appBarContainerColor by rememberUpdatedState(colors.containerColor(colorTransitionFraction))
+ val appBarContainerColor = colors.containerColor(colorTransitionFraction)
// Wrap the given actions in a Row.
val actionsRow = @Composable {
@@ -364,14 +363,17 @@ private fun TwoRowsTopAppBar(
titleTextStyle = smallTitleTextStyle,
titleAlpha = topTitleAlpha,
titleVerticalArrangement = Arrangement.Center,
- titleHorizontalArrangement = Arrangement.Start,
titleBottomPadding = 0,
hideTitleSemantics = hideTopRowSemantics,
navigationIcon = navigationIcon,
actions = actionsRow,
)
TopAppBarLayout(
- modifier = Modifier.clipToBounds(),
+ modifier = Modifier
+ // only apply the horizontal sides of the window insets padding, since the top
+ // padding will always be applied by the layout above
+ .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Horizontal))
+ .clipToBounds(),
heightPx = maxHeightPx.floatValue - pinnedHeightPx +
(scrollBehavior?.state?.heightOffset ?: 0f),
navigationIconContentColor = colors.navigationIconContentColor,
@@ -392,7 +394,6 @@ private fun TwoRowsTopAppBar(
titleTextStyle = titleTextStyle,
titleAlpha = bottomTitleAlpha,
titleVerticalArrangement = Arrangement.Bottom,
- titleHorizontalArrangement = Arrangement.Start,
titleBottomPadding = titleBottomPaddingPx,
hideTitleSemantics = hideBottomRowSemantics,
navigationIcon = {},
@@ -419,7 +420,6 @@ private fun TwoRowsTopAppBar(
* @param modifier a [Modifier]
* @param titleAlpha the title's alpha
* @param titleVerticalArrangement the title's vertical arrangement
- * @param titleHorizontalArrangement the title's horizontal arrangement
* @param titleBottomPadding the title's bottom padding
* @param hideTitleSemantics hides the title node from the semantic tree. Apply this
* boolean when this layout is part of a [TwoRowsTopAppBar] to hide the title's semantics
@@ -440,7 +440,6 @@ private fun TopAppBarLayout(
titleTextStyle: TextStyle,
titleAlpha: Float,
titleVerticalArrangement: Arrangement.Vertical,
- titleHorizontalArrangement: Arrangement.Horizontal,
titleBottomPadding: Int,
hideTitleSemantics: Boolean,
navigationIcon: @Composable () -> Unit,
@@ -470,10 +469,10 @@ private fun TopAppBarLayout(
CompositionLocalProvider(
LocalContentColor provides titleContentColor,
LocalDensity provides with(LocalDensity.current) {
- Density(
- density = density,
- fontScale = if (titleScaleDisabled) 1f else fontScale,
- )
+ Density(
+ density = density,
+ fontScale = if (titleScaleDisabled) 1f else fontScale,
+ )
},
content = title
)
@@ -528,15 +527,7 @@ private fun TopAppBarLayout(
// Title
titlePlaceable.placeRelative(
- x = when (titleHorizontalArrangement) {
- Arrangement.Center -> (constraints.maxWidth - titlePlaceable.width) / 2
- Arrangement.End ->
- constraints.maxWidth - titlePlaceable.width - actionIconsPlaceable.width
- // Arrangement.Start.
- // A TopAppBarTitleInset will make sure the title is offset in case the
- // navigation icon is missing.
- else -> max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width)
- },
+ x = max(TopAppBarTitleInset.roundToPx(), navigationIconPlaceable.width),
y = when (titleVerticalArrangement) {
Arrangement.Center -> (layoutHeight - titlePlaceable.height) / 2
// Apply bottom padding from the title's baseline only when the Arrangement is
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPasswordTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPasswordTest.kt
new file mode 100644
index 000000000000..6f2d1f98f6cf
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPasswordTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.editor
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertTextContains
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTextReplacement
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsTextFieldPasswordTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+ private val label = "label"
+ private val value = "value"
+ private val valueChanged = "Value Changed"
+ private val visibilityIconTag = "Visibility Icon"
+
+ @Test
+ fun textFieldPassword_displayed() {
+ composeTestRule.setContent {
+ SettingsTextFieldPassword(
+ value = value,
+ label = label,
+ onTextChange = {})
+ }
+ composeTestRule.onNodeWithText(label, substring = true)
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun textFieldPassword_invisible() {
+ composeTestRule.setContent {
+ var value by remember { mutableStateOf(value) }
+ SettingsTextFieldPassword(
+ value = value,
+ label = label,
+ onTextChange = { value = it })
+ }
+ composeTestRule.onNodeWithText(value, substring = true)
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun textFieldPassword_visible_inputValue() {
+ composeTestRule.setContent {
+ var value by remember { mutableStateOf(value) }
+ SettingsTextFieldPassword(
+ value = value,
+ label = label,
+ onTextChange = { value = it })
+ }
+ composeTestRule.onNodeWithTag(visibilityIconTag)
+ .performClick()
+ composeTestRule.onNodeWithText(label, substring = true)
+ .performTextReplacement(valueChanged)
+ composeTestRule.onNodeWithText(label, substring = true)
+ .assertTextContains(valueChanged)
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
index a6a5ed229756..3dac7dbc005d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBarTest.kt
@@ -22,7 +22,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
-import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -307,8 +306,8 @@ class CustomizedAppBarTest {
}
/**
- * Checks the app bar's components positioning when it's a [CustomizedTopAppBar], a
- * [CenterAlignedTopAppBar], or a larger app bar that is scrolled up and collapsed into a small
+ * Checks the app bar's components positioning when it's a [CustomizedTopAppBar]
+ * or a larger app bar that is scrolled up and collapsed into a small
* configuration and there is no navigation icon.
*/
private fun assertSmallPositioningWithoutNavigation(isCenteredTitle: Boolean = false) {
@@ -335,8 +334,7 @@ class CustomizedAppBarTest {
}
/**
- * Checks the app bar's components positioning when it's a [CustomizedTopAppBar] or a
- * [CenterAlignedTopAppBar].
+ * Checks the app bar's components positioning when it's a [CustomizedTopAppBar].
*/
private fun assertSmallDefaultPositioning(isCenteredTitle: Boolean = false) {
val appBarBounds = rule.onNodeWithTag(TopAppBarTestTag).getUnclippedBoundsInRoot()
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 91b852ab9f67..70126ace7d4c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -198,19 +198,19 @@ public class A2dpProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
boolean isA2dpPlaying() {
if (mService == null) return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index c7a5bd86c1cd..38cd04e8a0a3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -140,19 +140,19 @@ final class A2dpSinkProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
boolean isAudioPlaying() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java
index c3f845ca8c0a..45cafc6bf6cb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipSetCoordinatorProfile.java
@@ -178,19 +178,19 @@ public class CsipSetCoordinatorProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null || device == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index 6b7f733b5413..660090ddef2d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -258,19 +258,19 @@ public class HapClientProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null || device == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 7e5c1240cc63..b79f147c321f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -165,19 +165,19 @@ public class HeadsetProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index 5fbb4c3e5712..14fab16de3e6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -231,19 +231,19 @@ public class HearingAidProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null || device == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index 66225a2bffca..b0e0049a5ed4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -150,19 +150,19 @@ final class HfpClientProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
index 8a2c4f8a1230..5468efbdbd3e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
@@ -123,13 +123,13 @@ public class HidDeviceProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
// if set preferred to false, then disconnect to the current device
if (!enabled) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 3c24b4a095b9..5b91ac9d3dab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -126,19 +126,19 @@ public class HidProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index e781f1307072..a93524f6d873 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -24,24 +24,18 @@ import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
-import android.bluetooth.BluetoothCodecConfig;
-import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothUuid;
import android.content.Context;
import android.os.Build;
-import android.os.ParcelUuid;
import android.util.Log;
import androidx.annotation.RequiresApi;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.R;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
public class LeAudioProfile implements LocalBluetoothProfile {
@@ -233,19 +227,19 @@ public class LeAudioProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null || device == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 4881a86b398a..0a803bc7825d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -137,19 +137,19 @@ public final class MapClientProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index 75c1926683ef..db1ba5e3741f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -138,19 +138,19 @@ public class MapProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
index 767df352b70f..dec862b07a6a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
@@ -105,7 +105,7 @@ public class PanProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null) {
return false;
}
@@ -117,12 +117,12 @@ public class PanProfile implements LocalBluetoothProfile {
mService.setConnectionPolicy(sink, CONNECTION_POLICY_FORBIDDEN);
}
}
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 0d11293a01b7..65b89baaea81 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -151,19 +151,19 @@ public final class PbapClientProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index 9e2e4a14124a..784b821745bd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -108,16 +108,16 @@ public class PbapServerProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null) {
return false;
}
if (!enabled) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index 104f1d738000..0f8892f760c1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -136,19 +136,19 @@ final class SapProfile implements LocalBluetoothProfile {
@Override
public boolean setEnabled(BluetoothDevice device, boolean enabled) {
- boolean isEnabled = false;
+ boolean isSuccessful = false;
if (mService == null) {
return false;
}
if (enabled) {
if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+ isSuccessful = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
- return isEnabled;
+ return isSuccessful;
}
public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
deleted file mode 100644
index 54d5c3d63a5d..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkStatsSummaryLoader.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.net;
-
-import android.app.usage.NetworkStats;
-import android.app.usage.NetworkStatsManager;
-import android.content.Context;
-import android.net.NetworkTemplate;
-import android.util.Log;
-
-import androidx.loader.content.AsyncTaskLoader;
-
-/**
- * Loader for retrieving the network stats summary for all UIDs.
- */
-public class NetworkStatsSummaryLoader extends AsyncTaskLoader<NetworkStats> {
-
- private static final String TAG = "NetworkDetailLoader";
- private final NetworkStatsManager mNetworkStatsManager;
- private final long mStart;
- private final long mEnd;
- private final NetworkTemplate mNetworkTemplate;
-
- private NetworkStatsSummaryLoader(Builder builder) {
- super(builder.mContext);
- mStart = builder.mStart;
- mEnd = builder.mEnd;
- mNetworkTemplate = builder.mNetworkTemplate;
- mNetworkStatsManager = (NetworkStatsManager)
- builder.mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
- }
-
- @Override
- protected void onStartLoading() {
- super.onStartLoading();
- forceLoad();
- }
-
- @Override
- public NetworkStats loadInBackground() {
- try {
- return mNetworkStatsManager.querySummary(mNetworkTemplate, mStart, mEnd);
- } catch (RuntimeException e) {
- Log.e(TAG, "Exception querying network detail.", e);
- return null;
- }
- }
-
- @Override
- protected void onStopLoading() {
- super.onStopLoading();
- cancelLoad();
- }
-
- @Override
- protected void onReset() {
- super.onReset();
- cancelLoad();
- }
-
- public static class Builder {
- private final Context mContext;
- private long mStart;
- private long mEnd;
- private NetworkTemplate mNetworkTemplate;
-
- public Builder(Context context) {
- mContext = context;
- }
-
- public Builder setStartTime(long start) {
- mStart = start;
- return this;
- }
-
- public Builder setEndTime(long end) {
- mEnd = end;
- return this;
- }
-
- /**
- * Set {@link NetworkTemplate} for builder
- */
- public Builder setNetworkTemplate(NetworkTemplate template) {
- mNetworkTemplate = template;
- return this;
- }
-
- public NetworkStatsSummaryLoader build() {
- return new NetworkStatsSummaryLoader(this);
- }
- }
-}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d267d8056e9d..15620b7023e2 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -853,6 +853,9 @@
<!-- Permission required for accessing all content provider mime types -->
<uses-permission android:name="android.permission.GET_ANY_PROVIDER_TYPE" />
+ <!-- Permission required for CTS-in-sandbox tests -->
+ <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX" />
+
<!-- Permission required for CTS test - CtsWallpaperTestCases -->
<uses-permission android:name="android.permission.ALWAYS_UPDATE_WALLPAPER" />
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 78da5a699759..8f329b3dddd8 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -4,7 +4,6 @@ set noparent
dsandler@android.com
-aaliomer@google.com
aaronjli@google.com
achalke@google.com
acul@google.com
@@ -36,6 +35,7 @@ gallmann@google.com
gwasserman@google.com
hwwang@google.com
hyunyoungs@google.com
+ikateryna@google.com
jaggies@google.com
jamesoleary@google.com
jbolinger@google.com
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
index 03cbc169a90f..0f55f35adc4e 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -5,4 +5,11 @@ flag {
namespace: "accessibility"
description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
bug: "298467628"
+}
+
+flag {
+ name: "a11y_menu_hide_before_taking_action"
+ namespace: "accessibility"
+ description: "Hides the AccessibilityMenuService UI before taking action instead of after."
+ bug: "292020123"
} \ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
index 587395d4f636..fd9a9c634a36 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
@@ -15,7 +15,7 @@
android:layout_centerHorizontal="true"
android:scaleType="fitCenter"/>
-<TextView
+ <TextView
android:id="@+id/shortcutLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -27,6 +27,6 @@
android:lines="2"
android:textSize="@dimen/label_text_size"
android:textAlignment="center"
- android:textAppearance="@android:style/TextAppearance.DeviceDefault.Widget.Button"/>
+ android:textColor="@color/grid_item_text_color"/>
</RelativeLayout>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml
index fbf2b071ea3c..f961bdbac0aa 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-night/colors.xml
@@ -20,5 +20,6 @@
<color name="footer_icon_disabled_color">#5F6368</color>
<color name="colorControlNormal">#202124</color>
<color name="snackbar_bg_color">@android:color/white</color>
+ <color name="grid_item_text_color">@android:color/white</color>
</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml
index d81d0d511a87..9722eb04130f 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/colors.xml
@@ -21,6 +21,7 @@
<color name="footer_icon_disabled_color">#ddd</color>
<color name="colorControlNormal">@android:color/white</color>
<color name="snackbar_bg_color">#313235</color>
+ <color name="grid_item_text_color">@android:color/black</color>
<color name="colorAccessibilityMenuIcon">#3AA757</color>
</resources>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 27aade5e6bf8..008732e787f1 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -260,6 +260,27 @@ public class AccessibilityMenuService extends AccessibilityService
// Shortcuts are repeatable in a11y menu rather than unique, so use tag ID to handle.
int viewTag = (int) view.getTag();
+ // First check if this was a shortcut which should keep a11y menu visible. If so,
+ // perform the shortcut and return without hiding the UI.
+ if (viewTag == ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) {
+ adjustBrightness(BRIGHTNESS_UP_INCREMENT_GAMMA);
+ return;
+ } else if (viewTag == ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()) {
+ adjustBrightness(BRIGHTNESS_DOWN_INCREMENT_GAMMA);
+ return;
+ } else if (viewTag == ShortcutId.ID_VOLUME_UP_VALUE.ordinal()) {
+ adjustVolume(AudioManager.ADJUST_RAISE);
+ return;
+ } else if (viewTag == ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()) {
+ adjustVolume(AudioManager.ADJUST_LOWER);
+ return;
+ }
+
+ if (Flags.a11yMenuHideBeforeTakingAction()) {
+ // Hide the a11y menu UI before performing the following shortcut actions.
+ mA11yMenuLayout.hideMenu();
+ }
+
if (viewTag == ShortcutId.ID_ASSISTANT_VALUE.ordinal()) {
// Always restart the voice command activity, so that the UI is reloaded.
startActivityIfIntentIsSafe(
@@ -281,21 +302,11 @@ public class AccessibilityMenuService extends AccessibilityService
performGlobalActionInternal(GLOBAL_ACTION_NOTIFICATIONS);
} else if (viewTag == ShortcutId.ID_SCREENSHOT_VALUE.ordinal()) {
performGlobalActionInternal(GLOBAL_ACTION_TAKE_SCREENSHOT);
- } else if (viewTag == ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()) {
- adjustBrightness(BRIGHTNESS_UP_INCREMENT_GAMMA);
- return;
- } else if (viewTag == ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()) {
- adjustBrightness(BRIGHTNESS_DOWN_INCREMENT_GAMMA);
- return;
- } else if (viewTag == ShortcutId.ID_VOLUME_UP_VALUE.ordinal()) {
- adjustVolume(AudioManager.ADJUST_RAISE);
- return;
- } else if (viewTag == ShortcutId.ID_VOLUME_DOWN_VALUE.ordinal()) {
- adjustVolume(AudioManager.ADJUST_LOWER);
- return;
}
- mA11yMenuLayout.hideMenu();
+ if (!Flags.a11yMenuHideBeforeTakingAction()) {
+ mA11yMenuLayout.hideMenu();
+ }
}
/**
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 767756e17747..17c74ba7b12f 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -263,9 +263,11 @@ internal class ExpandableControllerImpl(
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
+ cujType?.let { InteractionJankMonitor.getInstance().begin(composeViewRoot, it) }
}
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ cujType?.let { InteractionJankMonitor.getInstance().end(it) }
delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
overlay.value = null
}
@@ -320,9 +322,8 @@ internal class ExpandableControllerImpl(
}
override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {
- // TODO(b/252723237): Add support for jank monitoring when animating from a
- // Composable.
- return null
+ val type = cuj?.cujType ?: return null
+ return InteractionJankMonitor.Configuration.Builder.withView(type, composeViewRoot)
}
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index 92d2bd253a83..9d62e387500f 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -163,9 +163,6 @@ object CustomizationProviderContract {
const val TABLE_NAME = "flags"
val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
- /** Flag denoting whether the Wallpaper Picker should use the new, revamped UI. */
- const val FLAG_NAME_REVAMPED_WALLPAPER_UI = "revamped_wallpaper_ui"
-
/**
* Flag denoting whether the customizable lock screen quick affordances feature is enabled.
*/
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
new file mode 100644
index 000000000000..173d57b335f0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_motion_layout.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- This file is needed when flag lockscreen.enable_landscape is on
+ Required for landscape lockscreen on small screens. -->
+<com.android.keyguard.KeyguardPasswordView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/keyguard_password_view"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:gravity="bottom">
+
+ <!-- Layout here is visually identical to the previous keyguard_password_view.
+ I.E., 'constraints here effectively the same as the previous linear layout' -->
+ <androidx.constraintlayout.motion.widget.MotionLayout
+ android:id="@+id/password_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:maxWidth="@dimen/keyguard_security_width"
+ android:layout_gravity="center_horizontal"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical"
+ androidprv:layoutDescription="@xml/keyguard_password_scene">
+
+ <!-- Guideline need to align password right of centre,
+ when on small screen landscape layout -->
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/password_center_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ androidprv:layout_constraintGuide_percent="0.5" />
+
+ <LinearLayout
+ android:id="@+id/keyguard_bouncer_message_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical"
+ androidprv:layout_constraintTop_toTopOf="parent">
+
+ <include layout="@layout/keyguard_bouncer_message_area" />
+
+ <com.android.systemui.bouncer.ui.BouncerMessageView
+ android:id="@+id/bouncer_message_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/passwordEntry_container"
+ android:layout_width="280dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:theme="?attr/passwordStyle"
+ androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintHorizontal_bias="0.5"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@+id/keyguard_bouncer_message_container"
+ androidprv:layout_constraintVertical_bias="0.7777">
+
+ <EditText
+ android:id="@+id/passwordEntry"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/keyguard_accessibility_password"
+ android:gravity="center_horizontal"
+ android:imeOptions="flagForceAscii|actionDone"
+ android:inputType="textPassword"
+ android:maxLength="500"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textCursorDrawable="@null"
+ android:textSize="16sp"
+ android:textStyle="normal" />
+
+ <ImageView
+ android:id="@+id/switch_ime_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end|center_vertical"
+ android:layout_marginBottom="12dp"
+ android:background="?android:attr/selectableItemBackground"
+ android:clickable="true"
+ android:contentDescription="@string/accessibility_ime_switch_button"
+ android:padding="8dip"
+ android:src="@drawable/ic_lockscreen_ime"
+ android:tint="?android:attr/textColorPrimary"
+ android:visibility="gone" />
+ </FrameLayout>
+
+ <include
+ android:id="@+id/keyguard_selector_fade_container"
+ layout="@layout/keyguard_eca"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="12dp"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent" />
+
+ </androidx.constraintlayout.motion.widget.MotionLayout>
+
+</com.android.keyguard.KeyguardPasswordView> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
new file mode 100644
index 000000000000..b562d7b5ee48
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pattern_motion_layout.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+
+<!-- This file is needed when flag lockscreen.enable_landscape is on
+ Required for landscape lockscreen on small screens. -->
+<com.android.keyguard.KeyguardPatternView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/keyguard_pattern_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal|bottom"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="vertical">
+
+ <!-- Layout here is visually identical to the previous keyguard_pattern_view.
+ I.E., 'constraints here effectively the same as the previous linear layout' -->
+ <androidx.constraintlayout.motion.widget.MotionLayout
+ android:id="@+id/pattern_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical"
+ android:maxWidth = "@dimen/biometric_auth_pattern_view_max_size"
+ androidprv:layoutDescription="@xml/keyguard_pattern_scene">
+
+ <!-- Guideline need to align pattern right of centre,
+ when on small screen landscape layout -->
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/pattern_center_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ androidprv:layout_constraintGuide_percent="0.5" />
+
+ <LinearLayout
+ android:id="@+id/keyguard_bouncer_message_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layoutDirection="ltr"
+ android:orientation="vertical"
+ androidprv:layout_constraintTop_toTopOf="parent">
+
+ <include layout="@layout/keyguard_bouncer_message_area" />
+
+ <com.android.systemui.bouncer.ui.BouncerMessageView
+ android:id="@+id/bouncer_message_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+
+ </LinearLayout>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/pattern_top_guideline"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ androidprv:layout_constraintGuide_percent="0" />
+
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPatternView"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:layout_marginBottom="8dp"
+ androidprv:layout_constraintVertical_bias="1.0"
+ androidprv:layout_constraintDimensionRatio="1.0"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintVertical_chainStyle="packed"
+ androidprv:layout_constraintTop_toBottomOf="@id/pattern_top_guideline"/>
+
+ <include
+ android:id="@+id/keyguard_selector_fade_container"
+ layout="@layout/keyguard_eca"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:orientation="vertical"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@+id/lockPatternView" />
+
+ </androidx.constraintlayout.motion.widget.MotionLayout>
+
+</com.android.keyguard.KeyguardPatternView> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
index 6835d59658d4..6c79d5a6095a 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
@@ -34,6 +34,7 @@
android:id="@+id/pin_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:maxWidth="@dimen/keyguard_security_width"
android:clipChildren="false"
android:clipToPadding="false"
android:layoutDirection="ltr"
diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml
new file mode 100644
index 000000000000..092e10d25c00
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/xml/keyguard_password_scene.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<MotionScene
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:motion="http://schemas.android.com/apk/res-auto"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto">
+
+ <Transition
+ motion:constraintSetStart="@id/single_constraints"
+ motion:constraintSetEnd="@+id/split_constraints"
+ motion:duration="0"
+ motion:autoTransition="none" />
+
+ <!-- No changes to default layout -->
+ <ConstraintSet android:id="@+id/single_constraints" />
+
+ <ConstraintSet android:id="@+id/split_constraints">
+
+ <Constraint
+ android:id="@+id/keyguard_bouncer_message_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintEnd_toStartOf="@+id/password_center_guideline"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+ androidprv:layout_constraintVertical_chainStyle="spread_inside" />
+ <Constraint
+ android:id="@+id/passwordEntry_container"
+ android:layout_width="280dp"
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintVertical_bias="0.5"
+ androidprv:layout_constraintHorizontal_bias="0.5"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintStart_toStartOf="@+id/password_center_guideline"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintTop_toTopOf="parent"/>
+ <Constraint
+ android:id="@+id/keyguard_selector_fade_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintEnd_toStartOf="@+id/password_center_guideline"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintTop_toBottomOf="@+id/keyguard_bouncer_message_container" />
+
+ </ConstraintSet>
+</MotionScene> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml
new file mode 100644
index 000000000000..6112411402c4
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/xml/keyguard_pattern_scene.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<MotionScene
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:motion="http://schemas.android.com/apk/res-auto"
+ xmlns:androidprv="http://schemas.android.com/apk/res-auto">
+
+ <Transition
+ motion:constraintSetStart="@id/single_constraints"
+ motion:constraintSetEnd="@+id/split_constraints"
+ motion:duration="0"
+ motion:autoTransition="none"/>
+
+ <!-- No changes to default layout -->
+ <ConstraintSet android:id="@+id/single_constraints"/>
+
+ <ConstraintSet android:id="@+id/split_constraints">
+
+ <Constraint
+ android:id="@+id/keyguard_bouncer_message_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintEnd_toStartOf="@+id/pattern_center_guideline"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ androidprv:layout_constraintTop_toTopOf="parent" />
+ <Constraint
+ android:id="@+id/lockPatternView"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ androidprv:layout_constraintDimensionRatio="1.0"
+ androidprv:layout_constraintVertical_bias="0.5"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintEnd_toEndOf="parent"
+ androidprv:layout_constraintStart_toStartOf="@+id/pattern_center_guideline"
+ androidprv:layout_constraintTop_toTopOf="parent"
+ android:layout_marginBottom="0dp" />
+ <Constraint
+ android:id="@+id/keyguard_selector_fade_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ androidprv:layout_constraintBottom_toBottomOf="parent"
+ androidprv:layout_constraintEnd_toStartOf="@+id/pattern_center_guideline"
+ androidprv:layout_constraintStart_toStartOf="parent"
+ android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+ android:layout_marginTop="@dimen/keyguard_eca_top_margin" />
+
+ </ConstraintSet>
+</MotionScene> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml
index 44af9efffbf8..2a1270c80b75 100644
--- a/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml
+++ b/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml
@@ -26,12 +26,10 @@
motion:constraintSetStart="@id/single_constraints"
motion:constraintSetEnd="@+id/split_constraints"
motion:duration="0"
- motion:autoTransition="none">
- </Transition>
+ motion:autoTransition="none"/>
- <ConstraintSet android:id="@+id/single_constraints">
- <!-- No changes to default layout -->
- </ConstraintSet>
+ <!-- No changes to default layout -->
+ <ConstraintSet android:id="@+id/single_constraints"/>
<ConstraintSet android:id="@+id/split_constraints">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index 0c2341f9480c..3cb5bc745ec7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -59,9 +59,8 @@ public class PreviewPositionHelper {
* Updates the matrix based on the provided parameters
*/
public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
- int canvasWidth, int canvasHeight, int screenWidthPx, int screenHeightPx,
- int taskbarSize, boolean isLargeScreen,
- int currentRotation, boolean isRtl) {
+ int canvasWidth, int canvasHeight, boolean isLargeScreen, int currentRotation,
+ boolean isRtl) {
boolean isRotated = false;
boolean isOrientationDifferent;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 8200e5c84186..905923039f8b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -17,6 +17,7 @@
package com.android.systemui.shared.rotation;
import static android.content.pm.PackageManager.FEATURE_PC;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
@@ -37,6 +38,8 @@ import android.content.IntentFilter;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -67,6 +70,7 @@ import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.io.PrintWriter;
import java.util.Optional;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
/**
@@ -119,6 +123,8 @@ public class RotationButtonController {
private final int mIconCwStart0ResId;
@DrawableRes
private final int mIconCwStart90ResId;
+ /** Defaults to mainExecutor if not set via {@link #setBgExecutor(Executor)}. */
+ private Executor mBgExecutor;
@DrawableRes
private int mIconResId;
@@ -178,6 +184,8 @@ public class RotationButtonController {
mAccessibilityManager = AccessibilityManager.getInstance(context);
mTaskStackListener = new TaskStackListenerImpl();
mWindowRotationProvider = windowRotationProvider;
+
+ mBgExecutor = context.getMainExecutor();
}
public void setRotationButton(RotationButton rotationButton,
@@ -193,6 +201,10 @@ public class RotationButtonController {
return mContext;
}
+ public void setBgExecutor(Executor bgExecutor) {
+ mBgExecutor = bgExecutor;
+ }
+
/**
* Called during Taskbar initialization.
*/
@@ -219,8 +231,11 @@ public class RotationButtonController {
mListenersRegistered = true;
- updateDockedState(mContext.registerReceiver(mDockedReceiver,
- new IntentFilter(Intent.ACTION_DOCK_EVENT)));
+ mBgExecutor.execute(() -> {
+ final Intent intent = mContext.registerReceiver(mDockedReceiver,
+ new IntentFilter(Intent.ACTION_DOCK_EVENT));
+ mContext.getMainExecutor().execute(() -> updateDockedState(intent));
+ });
if (registerRotationWatcher) {
try {
@@ -246,11 +261,13 @@ public class RotationButtonController {
mListenersRegistered = false;
- try {
- mContext.unregisterReceiver(mDockedReceiver);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Docked receiver already unregistered", e);
- }
+ mBgExecutor.execute(() -> {
+ try {
+ mContext.unregisterReceiver(mDockedReceiver);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Docked receiver already unregistered", e);
+ }
+ });
if (mRotationWatcherRegistered) {
try {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 8738d3300e93..abd1563ecc43 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -16,8 +16,6 @@
package com.android.keyguard;
-import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
-
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -257,10 +255,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
mFalsingCollector, mKeyguardViewController,
- mFeatureFlags);
+ mDevicePostureController, mFeatureFlags);
} else if (keyguardInputView instanceof KeyguardPINView) {
- ((KeyguardPINView) keyguardInputView).setIsLockScreenLandscapeEnabled(
- mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE));
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index bb3e759dc157..72b4ae568397 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_APPEAR;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
@@ -105,21 +106,21 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
}
void onDevicePostureChanged(@DevicePostureInt int posture) {
- if (mLastDevicePosture != posture) {
- mLastDevicePosture = posture;
+ if (mLastDevicePosture == posture) return;
+ mLastDevicePosture = posture;
- if (mIsLockScreenLandscapeEnabled) {
- boolean useSplitBouncerAfterFold =
- mLastDevicePosture == DEVICE_POSTURE_CLOSED
- && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+ if (mIsLockScreenLandscapeEnabled) {
+ boolean useSplitBouncerAfterFold =
+ mLastDevicePosture == DEVICE_POSTURE_CLOSED
+ && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE
+ && getResources().getBoolean(R.bool.update_bouncer_constraints);
- if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
- updateConstraints(useSplitBouncerAfterFold);
- }
+ if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
+ updateConstraints(useSplitBouncerAfterFold);
}
-
- updateMargins();
}
+
+ updateMargins();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 59ee0d817ef3..8d5fc0467e3b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.WindowInsets.Type.ime;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
@@ -27,10 +28,13 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FO
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.app.ActivityManager;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Insets;
@@ -46,12 +50,16 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.constraintlayout.motion.widget.MotionLayout;
import com.android.app.animation.Interpolators;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.TextViewInputDisabler;
import com.android.systemui.DejankUtils;
import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.DevicePostureController;
+
+
/**
* Displays an alphanumeric (latin-1) key entry for the user to enter
* an unlock password
@@ -68,10 +76,14 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
private TextView mPasswordEntry;
private TextViewInputDisabler mPasswordEntryDisabler;
-
private Interpolator mLinearOutSlowInInterpolator;
private Interpolator mFastOutLinearInInterpolator;
private DisappearAnimationListener mDisappearAnimationListener;
+ @Nullable private MotionLayout mContainerMotionLayout;
+ private boolean mAlreadyUsingSplitBouncer = false;
+ private boolean mIsLockScreenLandscapeEnabled = false;
+ @DevicePostureController.DevicePostureInt
+ private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
private static final int[] DISABLE_STATE_SET = {-android.R.attr.state_enabled};
private static final int[] ENABLE_STATE_SET = {android.R.attr.state_enabled};
@@ -89,6 +101,21 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
context, android.R.interpolator.fast_out_linear_in);
}
+ /**
+ * Use motion layout (new bouncer implementation) if LOCKSCREEN_ENABLE_LANDSCAPE flag is
+ * enabled
+ */
+ public void setIsLockScreenLandscapeEnabled() {
+ mIsLockScreenLandscapeEnabled = true;
+ findContainerLayout();
+ }
+
+ private void findContainerLayout() {
+ if (mIsLockScreenLandscapeEnabled) {
+ mContainerMotionLayout = findViewById(R.id.password_container);
+ }
+ }
+
@Override
protected void resetState() {
}
@@ -124,6 +151,35 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
}
}
+ void onDevicePostureChanged(@DevicePostureController.DevicePostureInt int posture) {
+ if (mLastDevicePosture == posture) return;
+ mLastDevicePosture = posture;
+
+ if (mIsLockScreenLandscapeEnabled) {
+ boolean useSplitBouncerAfterFold =
+ mLastDevicePosture == DEVICE_POSTURE_CLOSED
+ && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE
+ && getResources().getBoolean(R.bool.update_bouncer_constraints);
+
+ if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
+ updateConstraints(useSplitBouncerAfterFold);
+ }
+ }
+
+ }
+
+ @Override
+ protected void updateConstraints(boolean useSplitBouncer) {
+ mAlreadyUsingSplitBouncer = useSplitBouncer;
+ if (useSplitBouncer) {
+ mContainerMotionLayout.jumpToState(R.id.split_constraints);
+ mContainerMotionLayout.setMaxWidth(Integer.MAX_VALUE);
+ } else {
+ mContainerMotionLayout.jumpToState(R.id.single_constraints);
+ mContainerMotionLayout.setMaxWidth(getResources()
+ .getDimensionPixelSize(R.dimen.keyguard_security_width));
+ }
+ }
@Override
protected void onFinishInflate() {
@@ -131,6 +187,11 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
mPasswordEntry = findViewById(getPasswordTextViewId());
mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry);
+
+ // EditText cursor can fail screenshot tests, so disable it when testing
+ if (ActivityManager.isRunningInTestHarness()) {
+ mPasswordEntry.setCursorVisible(false);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 5dbd01407a43..ab8cd531c307 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+
import android.content.res.Resources;
import android.os.UserHandle;
import android.text.Editable;
@@ -41,6 +43,7 @@ import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.List;
@@ -49,8 +52,10 @@ public class KeyguardPasswordViewController
extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms
-
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
+ private final DevicePostureController mPostureController;
+ private final DevicePostureController.Callback mPostureCallback = posture ->
+ mView.onDevicePostureChanged(posture);
private final InputMethodManager mInputMethodManager;
private final DelayableExecutor mMainExecutor;
private final KeyguardViewController mKeyguardViewController;
@@ -106,14 +111,19 @@ public class KeyguardPasswordViewController
@Main Resources resources,
FalsingCollector falsingCollector,
KeyguardViewController keyguardViewController,
+ DevicePostureController postureController,
FeatureFlags featureFlags) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController, featureFlags);
mKeyguardSecurityCallback = keyguardSecurityCallback;
mInputMethodManager = inputMethodManager;
+ mPostureController = postureController;
mMainExecutor = mainExecutor;
mKeyguardViewController = keyguardViewController;
+ if (featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)) {
+ view.setIsLockScreenLandscapeEnabled();
+ }
mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on);
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
mSwitchImeButton = mView.findViewById(R.id.switch_ime_button);
@@ -127,6 +137,9 @@ public class KeyguardPasswordViewController
mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ mView.onDevicePostureChanged(mPostureController.getDevicePosture());
+ mPostureController.addCallback(mPostureCallback);
+
// Set selected property on so the view can send accessibility events.
mPasswordEntry.setSelected(true);
mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener);
@@ -164,6 +177,7 @@ public class KeyguardPasswordViewController
protected void onViewDetached() {
super.onViewDetached();
mPasswordEntry.setOnEditorActionListener(null);
+ mPostureController.removeCallback(mPostureCallback);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 2bdf46e1309d..56706b5a7135 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -15,9 +15,13 @@
*/
package com.android.keyguard;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -29,6 +33,7 @@ import android.view.View;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
+import androidx.constraintlayout.motion.widget.MotionLayout;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
@@ -75,7 +80,10 @@ public class KeyguardPatternView extends KeyguardInputView
BouncerKeyguardMessageArea mSecurityMessageDisplay;
private View mEcaView;
- private ConstraintLayout mContainer;
+ @Nullable private MotionLayout mContainerMotionLayout;
+ @Nullable private ConstraintLayout mContainerConstraintLayout;
+ private boolean mAlreadyUsingSplitBouncer = false;
+ private boolean mIsLockScreenLandscapeEnabled = false;
@DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
public KeyguardPatternView(Context context) {
@@ -98,16 +106,44 @@ public class KeyguardPatternView extends KeyguardInputView
mContext, android.R.interpolator.fast_out_linear_in));
}
+ /**
+ * Use motion layout (new bouncer implementation) if LOCKSCREEN_ENABLE_LANDSCAPE flag is
+ * enabled, instead of constraint layout (old bouncer implementation)
+ */
+ public void setIsLockScreenLandscapeEnabled(boolean isLockScreenLandscapeEnabled) {
+ mIsLockScreenLandscapeEnabled = isLockScreenLandscapeEnabled;
+ findContainerLayout();
+ }
+
+ private void findContainerLayout() {
+ if (mIsLockScreenLandscapeEnabled) {
+ mContainerMotionLayout = findViewById(R.id.pattern_container);
+ } else {
+ mContainerConstraintLayout = findViewById(R.id.pattern_container);
+ }
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
updateMargins();
}
void onDevicePostureChanged(@DevicePostureInt int posture) {
- if (mLastDevicePosture != posture) {
- mLastDevicePosture = posture;
- updateMargins();
+ if (mLastDevicePosture == posture) return;
+ mLastDevicePosture = posture;
+
+ if (mIsLockScreenLandscapeEnabled) {
+ boolean useSplitBouncerAfterFold =
+ mLastDevicePosture == DEVICE_POSTURE_CLOSED
+ && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE
+ && getResources().getBoolean(R.bool.update_bouncer_constraints);
+
+ if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
+ updateConstraints(useSplitBouncerAfterFold);
+ }
}
+
+ updateMargins();
}
private void updateMargins() {
@@ -115,12 +151,36 @@ public class KeyguardPatternView extends KeyguardInputView
float halfOpenPercentage =
mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
- ConstraintSet cs = new ConstraintSet();
- cs.clone(mContainer);
- cs.setGuidelinePercent(R.id.pattern_top_guideline,
- mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED
- ? halfOpenPercentage : 0.0f);
- cs.applyTo(mContainer);
+ if (mIsLockScreenLandscapeEnabled) {
+ ConstraintSet cs = mContainerMotionLayout.getConstraintSet(R.id.single_constraints);
+ cs.setGuidelinePercent(R.id.pattern_top_guideline,
+ mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
+ cs.applyTo(mContainerMotionLayout);
+ } else {
+ ConstraintSet cs = new ConstraintSet();
+ cs.clone(mContainerConstraintLayout);
+ cs.setGuidelinePercent(R.id.pattern_top_guideline,
+ mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
+ cs.applyTo(mContainerConstraintLayout);
+ }
+ }
+
+ /**
+ * Updates the keyguard view's constraints (single or split constraints).
+ * Split constraints are only used for small landscape screens.
+ * Only called when flag LANDSCAPE_ENABLE_LOCKSCREEN is enabled.
+ */
+ @Override
+ protected void updateConstraints(boolean useSplitBouncer) {
+ mAlreadyUsingSplitBouncer = useSplitBouncer;
+ if (useSplitBouncer) {
+ mContainerMotionLayout.jumpToState(R.id.split_constraints);
+ mContainerMotionLayout.setMaxWidth(Integer.MAX_VALUE);
+ } else {
+ mContainerMotionLayout.jumpToState(R.id.single_constraints);
+ mContainerMotionLayout.setMaxWidth(getResources()
+ .getDimensionPixelSize(R.dimen.biometric_auth_pattern_view_max_size));
+ }
}
@Override
@@ -130,7 +190,6 @@ public class KeyguardPatternView extends KeyguardInputView
mLockPatternView = findViewById(R.id.lockPatternView);
mEcaView = findViewById(R.id.keyguard_selector_fade_container);
- mContainer = findViewById(R.id.pattern_container);
}
@Override
@@ -209,7 +268,7 @@ public class KeyguardPatternView extends KeyguardInputView
getAnimationListener(InteractionJankMonitor.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR));
DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
- ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils;
+ ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils;
disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
() -> {
enableClipping(true);
@@ -220,7 +279,7 @@ public class KeyguardPatternView extends KeyguardInputView
if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
(long) (200 * durationMultiplier),
- - mDisappearAnimationUtils.getStartTranslation() * 3,
+ -mDisappearAnimationUtils.getStartTranslation() * 3,
false /* appearing */,
mDisappearAnimationUtils.getInterpolator(),
null /* finishRunnable */);
@@ -229,9 +288,16 @@ public class KeyguardPatternView extends KeyguardInputView
}
private void enableClipping(boolean enable) {
- setClipChildren(enable);
- mContainer.setClipToPadding(enable);
- mContainer.setClipChildren(enable);
+ if (mContainerConstraintLayout != null) {
+ setClipChildren(enable);
+ mContainerConstraintLayout.setClipToPadding(enable);
+ mContainerConstraintLayout.setClipChildren(enable);
+ }
+ if (mContainerMotionLayout != null) {
+ setClipChildren(enable);
+ mContainerMotionLayout.setClipToPadding(enable);
+ mContainerMotionLayout.setClipChildren(enable);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index a30b4479fe95..98312b11e9d6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -18,6 +18,7 @@ package com.android.keyguard;
import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
import android.content.res.ColorStateList;
import android.os.AsyncTask;
@@ -205,6 +206,8 @@ public class KeyguardPatternViewController
mLatencyTracker = latencyTracker;
mFalsingCollector = falsingCollector;
mEmergencyButtonController = emergencyButtonController;
+ view.setIsLockScreenLandscapeEnabled(
+ featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE));
mLockPatternView = mView.findViewById(R.id.lockPatternView);
mPostureController = postureController;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 574a0591bd51..0af803fc84fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+
import android.view.View;
import com.android.internal.util.LatencyTracker;
@@ -61,6 +63,7 @@ public class KeyguardPinViewController
mPostureController = postureController;
mLockPatternUtils = lockPatternUtils;
mFeatureFlags = featureFlags;
+ view.setIsLockScreenLandscapeEnabled(mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE));
mBackspaceKey = view.findViewById(R.id.delete_button);
mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser());
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 4cc90c244cf2..f18504c28609 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
import android.util.Log;
@@ -154,9 +155,9 @@ public class KeyguardSecurityViewFlipperController
private int getLayoutIdFor(SecurityMode securityMode) {
// TODO (b/297863911, b/297864907) - implement motion layout for other bouncers
switch (securityMode) {
- case Pattern: return R.layout.keyguard_pattern_view;
+ case Pattern: return R.layout.keyguard_pattern_motion_layout;
case PIN: return R.layout.keyguard_pin_motion_layout;
- case Password: return R.layout.keyguard_password_view;
+ case Password: return R.layout.keyguard_password_motion_layout;
case SimPin: return R.layout.keyguard_sim_pin_view;
case SimPuk: return R.layout.keyguard_sim_puk_view;
default:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index 97b06170e770..054bd082edb7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -25,7 +25,7 @@ constructor(
shadeExpansionCollectorJob =
scope.launch {
// wait for it to emit true once
- shadeInteractorLazy.get().anyExpanding.first { it }
+ shadeInteractorLazy.get().isAnyExpanding.first { it }
onShadeInteraction.run()
}
shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index f12b91956c80..d89332d6d686 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -49,6 +49,7 @@ import com.android.systemui.mediaprojection.taskswitcher.MediaProjectionTaskSwit
import com.android.systemui.power.PowerUI
import com.android.systemui.reardisplay.RearDisplayDialogController
import com.android.systemui.recents.Recents
+import com.android.systemui.recents.ScreenPinningRequest
import com.android.systemui.settings.dagger.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.ImmersiveModeConfirmation
@@ -366,4 +367,9 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(KeyguardDismissBinder::class)
abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenPinningRequest::class)
+ abstract fun bindScreenPinningRequest(impl: ScreenPinningRequest): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 17513589fa82..4f166858faf8 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.display.data.repository
import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED
import android.hardware.display.DisplayManager.DisplayListener
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_ADDED
import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
@@ -95,28 +96,36 @@ constructor(
@Background backgroundCoroutineDispatcher: CoroutineDispatcher
) : DisplayRepository {
// Displays are enabled only after receiving them in [onDisplayAdded]
- private val allDisplayEvents: Flow<DisplayEvent> = conflatedCallbackFlow {
- val callback =
- object : DisplayListener {
- override fun onDisplayAdded(displayId: Int) {
- trySend(DisplayEvent.Added(displayId))
- }
+ private val allDisplayEvents: Flow<DisplayEvent> =
+ conflatedCallbackFlow {
+ val callback =
+ object : DisplayListener {
+ override fun onDisplayAdded(displayId: Int) {
+ trySend(DisplayEvent.Added(displayId))
+ }
- override fun onDisplayRemoved(displayId: Int) {
- trySend(DisplayEvent.Removed(displayId))
- }
+ override fun onDisplayRemoved(displayId: Int) {
+ trySend(DisplayEvent.Removed(displayId))
+ }
- override fun onDisplayChanged(displayId: Int) {
- trySend(DisplayEvent.Changed(displayId))
- }
+ override fun onDisplayChanged(displayId: Int) {
+ trySend(DisplayEvent.Changed(displayId))
+ }
+ }
+ // Triggers an initial event when subscribed. This is needed to avoid getDisplays to
+ // be called when this class is constructed, but only when someone subscribes to
+ // this flow.
+ trySend(DisplayEvent.Changed(Display.DEFAULT_DISPLAY))
+ displayManager.registerDisplayListener(
+ callback,
+ backgroundHandler,
+ EVENT_FLAG_DISPLAY_ADDED or
+ EVENT_FLAG_DISPLAY_CHANGED or
+ EVENT_FLAG_DISPLAY_REMOVED,
+ )
+ awaitClose { displayManager.unregisterDisplayListener(callback) }
}
- displayManager.registerDisplayListener(
- callback,
- backgroundHandler,
- EVENT_FLAG_DISPLAY_ADDED or EVENT_FLAG_DISPLAY_CHANGED or EVENT_FLAG_DISPLAY_REMOVED,
- )
- awaitClose { displayManager.unregisterDisplayListener(callback) }
- }
+ .flowOn(backgroundCoroutineDispatcher)
override val displayChangeEvent: Flow<Int> =
allDisplayEvents.filter { it is DisplayEvent.Changed }.map { it.displayId }
@@ -128,7 +137,9 @@ constructor(
.stateIn(
applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = getDisplays()
+ // To avoid getting displays on this object construction, they are get after the
+ // first event. allDisplayEvents emits a changed event when we subscribe to it.
+ initialValue = emptySet()
)
private fun getDisplays(): Set<Display> =
@@ -146,12 +157,23 @@ constructor(
private val ignoredDisplayIds = MutableStateFlow<Set<Int>>(emptySet())
+ private fun getInitialConnectedDisplays(): Set<Int> =
+ displayManager
+ .getDisplays(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
+ .map { it.displayId }
+ .toSet()
+ .also {
+ if (DEBUG) {
+ Log.d(TAG, "getInitialConnectedDisplays: $it")
+ }
+ }
+
/* keeps connected displays until they are disconnected. */
private val connectedDisplayIds: StateFlow<Set<Int>> =
conflatedCallbackFlow {
+ val connectedIds = getInitialConnectedDisplays().toMutableSet()
val callback =
object : DisplayConnectionListener {
- private val connectedIds = mutableSetOf<Int>()
override fun onDisplayConnected(id: Int) {
if (DEBUG) {
Log.d(TAG, "display with id=$id connected.")
@@ -170,6 +192,7 @@ constructor(
trySend(connectedIds.toSet())
}
}
+ trySend(connectedIds.toSet())
displayManager.registerDisplayListener(
callback,
backgroundHandler,
@@ -183,6 +206,10 @@ constructor(
.stateIn(
applicationScope,
started = SharingStarted.WhileSubscribed(),
+ // The initial value is set to empty, but connected displays are gathered as soon as
+ // the flow starts being collected. This is to ensure the call to get displays (an
+ // IPC) happens in the background instead of when this object
+ // is instantiated.
initialValue = emptySet()
)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e11fcb75e190..792420d6f8cb 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -107,7 +107,7 @@ object Flags {
// TODO(b/268005230): Tracking Bug
@JvmField
- val SENSITIVE_REVEAL_ANIM = unreleasedFlag("sensitive_reveal_anim", teamfood = true)
+ val SENSITIVE_REVEAL_ANIM = releasedFlag("sensitive_reveal_anim")
// TODO(b/280783617): Tracking Bug
@Keep
@@ -182,15 +182,12 @@ object Flags {
/** Flag to control the migration of face auth to modern architecture. */
// TODO(b/262838215): Tracking bug
- @JvmField val FACE_AUTH_REFACTOR = unreleasedFlag("face_auth_refactor", teamfood = true)
+ @JvmField val FACE_AUTH_REFACTOR = releasedFlag("face_auth_refactor")
/** Flag to control the revamp of keyguard biometrics progress animation */
// TODO(b/244313043): Tracking bug
@JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp")
- // TODO(b/262780002): Tracking Bug
- @JvmField val REVAMPED_WALLPAPER_UI = releasedFlag("revamped_wallpaper_ui")
-
// flag for controlling auto pin confirmation and material u shapes in bouncer
@JvmField
val AUTO_PIN_CONFIRMATION = releasedFlag("auto_pin_confirmation", "auto_pin_confirmation")
@@ -294,6 +291,11 @@ object Flags {
// TODO(b/291767565): Tracking bug.
@JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag("migrate_keyguard_status_view")
+ /** Migrate the status bar view on keyguard from notification panel to keyguard root view. */
+ // TODO(b/299115332): Tracking Bug.
+ @JvmField val MIGRATE_KEYGUARD_STATUS_BAR_VIEW =
+ unreleasedFlag("migrate_keyguard_status_bar_view")
+
/** Enables preview loading animation in the wallpaper picker. */
// TODO(b/274443705): Tracking Bug
@JvmField
@@ -318,6 +320,11 @@ object Flags {
R.bool.flag_stop_pulsing_face_scanning_animation,
"stop_pulsing_face_scanning_animation")
+ /** Flag to use a separate view for the alternate bouncer. */
+ // TODO(b/300440924): Tracking bug
+ @JvmField
+ val ALTERNATE_BOUNCER_REFACTOR: UnreleasedFlag = unreleasedFlag("alternate_bouncer_view")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
@@ -403,7 +410,7 @@ object Flags {
// TODO(b/290676905): Tracking Bug
val NEW_SHADE_CARRIER_GROUP_MOBILE_ICONS =
- unreleasedFlag("new_shade_carrier_group_mobile_icons", teamfood = true)
+ releasedFlag("new_shade_carrier_group_mobile_icons")
// 700 - dialer/calls
// TODO(b/254512734): Tracking Bug
@@ -804,4 +811,9 @@ object Flags {
/** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
@JvmField
val BLUETOOTH_QS_TILE_DIALOG = unreleasedFlag("bluetooth_qs_tile_dialog")
+
+ // TODO(b/300995746): Tracking Bug
+ /** Enable communal hub features. */
+ @JvmField
+ val COMMUNAL_HUB = resourceBooleanFlag(R.bool.config_communalServiceEnabled, "communal_hub")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 991572003e4d..9241776191d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -764,13 +764,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
}
-
- @Override
- public void onStrongAuthStateChanged(int userId) {
- if (mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
- doKeyguardLocked(null);
- }
- }
};
ViewMediatorCallback mViewMediatorCallback = new ViewMediatorCallback() {
@@ -2193,9 +2186,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
* Enable the keyguard if the settings are appropriate.
*/
private void doKeyguardLocked(Bundle options) {
- // if another app is disabling us, don't show unless we're in lockdown mode
- if (!mExternallyEnabled
- && !mLockPatternUtils.isUserInLockdown(KeyguardUpdateMonitor.getCurrentUser())) {
+ // if another app is disabling us, don't show
+ if (!mExternallyEnabled) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
mNeedToReshowWhenReenabled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 6db21b2c1e9e..233acd90aee3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -54,13 +54,13 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
+import com.google.errorprone.annotations.CompileTimeConstant
import java.io.PrintWriter
import java.util.Arrays
import java.util.stream.Collectors
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
@@ -112,33 +112,27 @@ interface DeviceEntryFaceAuthRepository {
fun setLockedOut(isLockedOut: Boolean)
/**
- * Cancel current face authentication and prevent it from running until [resumeFaceAuth] is
- * invoked.
- */
- fun pauseFaceAuth()
-
- /**
- * Allow face auth paused using [pauseFaceAuth] to run again. The next invocation to
- * [authenticate] will run as long as other gating conditions don't stop it from running.
- */
- fun resumeFaceAuth()
-
- /**
- * Trigger face authentication.
+ * Request face authentication or detection to be run.
*
* [uiEvent] provided should be logged whenever face authentication runs. Invocation should be
* ignored if face authentication is already running. Results should be propagated through
* [authenticationStatus]
*
* Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false.
+ *
+ * Method returns immediately and the face auth request is processed as soon as possible.
*/
- suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false)
+ fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean = false)
/** Stop currently running face authentication or detection. */
fun cancel()
}
-@OptIn(ExperimentalCoroutinesApi::class)
+private data class AuthenticationRequest(
+ val uiEvent: FaceAuthUiEvent,
+ val fallbackToDetection: Boolean
+)
+
@SysUISingleton
class DeviceEntryFaceAuthRepositoryImpl
@Inject
@@ -171,6 +165,8 @@ constructor(
private var faceAcquiredInfoIgnoreList: Set<Int>
private var retryCount = 0
+ private var pendingAuthenticateRequest = MutableStateFlow<AuthenticationRequest?>(null)
+
private var cancelNotReceivedHandlerJob: Job? = null
private var halErrorRetryJob: Job? = null
@@ -193,15 +189,6 @@ constructor(
override val isAuthRunning: StateFlow<Boolean>
get() = _isAuthRunning
- private val faceAuthPaused = MutableStateFlow(false)
- override fun pauseFaceAuth() {
- faceAuthPaused.value = true
- }
-
- override fun resumeFaceAuth() {
- faceAuthPaused.value = false
- }
-
private val keyguardSessionId: InstanceId?
get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
@@ -213,6 +200,8 @@ constructor(
override val isAuthenticated: Flow<Boolean>
get() = _isAuthenticated
+ private var cancellationInProgress = MutableStateFlow(false)
+
override val isBypassEnabled: Flow<Boolean> =
keyguardBypassController?.let {
conflatedCallbackFlow {
@@ -302,6 +291,7 @@ constructor(
observeFaceDetectGatingChecks()
observeFaceAuthResettingConditions()
listenForSchedulingWatchdog()
+ processPendingAuthRequests()
} else {
canRunFaceAuth = MutableStateFlow(false).asStateFlow()
canRunDetection = MutableStateFlow(false).asStateFlow()
@@ -338,6 +328,7 @@ constructor(
)
.onEach { anyOfThemIsTrue ->
if (anyOfThemIsTrue) {
+ clearPendingAuthRequest("Resetting auth status")
_isAuthenticated.value = false
retryCount = 0
halErrorRetryJob?.cancel()
@@ -346,6 +337,15 @@ constructor(
.launchIn(applicationScope)
}
+ private fun clearPendingAuthRequest(@CompileTimeConstant loggingContext: String) {
+ faceAuthLogger.clearingPendingAuthRequest(
+ loggingContext,
+ pendingAuthenticateRequest.value?.uiEvent,
+ pendingAuthenticateRequest.value?.fallbackToDetection
+ )
+ pendingAuthenticateRequest.value = null
+ }
+
private fun observeFaceDetectGatingChecks() {
canRunDetection
.onEach {
@@ -378,7 +378,6 @@ constructor(
biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
"isFaceAuthEnrolledAndEnabled"
),
- Pair(faceAuthPaused.isFalse(), "faceAuthIsNotPaused"),
Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
Pair(
keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
@@ -402,7 +401,13 @@ constructor(
biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
"userHasNotLockedDownDevice"
),
- Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing")
+ Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing"),
+ Pair(
+ userRepository.selectedUser
+ .map { it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS }
+ .isFalse(),
+ "userSwitchingInProgress"
+ )
)
}
@@ -440,9 +445,6 @@ constructor(
}
_authenticationStatus.value = errorStatus
_isAuthenticated.value = false
- if (errorStatus.isCancellationError()) {
- handleFaceCancellationError()
- }
if (errorStatus.isHardwareError()) {
faceAuthLogger.hardwareError(errorStatus)
handleFaceHardwareError()
@@ -471,16 +473,6 @@ constructor(
}
}
- private fun handleFaceCancellationError() {
- applicationScope.launch {
- faceAuthRequestedWhileCancellation?.let {
- faceAuthLogger.launchingQueuedFaceAuthRequest(it)
- authenticate(it)
- }
- faceAuthRequestedWhileCancellation = null
- }
- }
-
private fun handleFaceHardwareError() {
if (retryCount < HAL_ERROR_RETRY_MAX) {
retryCount++
@@ -490,7 +482,7 @@ constructor(
delay(HAL_ERROR_RETRY_TIMEOUT)
if (retryCount < HAL_ERROR_RETRY_MAX) {
faceAuthLogger.attemptingRetryAfterHardwareError(retryCount)
- authenticate(
+ requestAuthenticate(
FaceAuthUiEvent.FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE,
fallbackToDetection = false
)
@@ -501,7 +493,7 @@ constructor(
private fun onFaceAuthRequestCompleted() {
cancelNotReceivedHandlerJob?.cancel()
- cancellationInProgress = false
+ cancellationInProgress.value = false
_isAuthRunning.value = false
authCancellationSignal = null
}
@@ -512,24 +504,60 @@ constructor(
_detectionStatus.value = FaceDetectionStatus(sensorId, userId, isStrong)
}
- private var cancellationInProgress = false
- private var faceAuthRequestedWhileCancellation: FaceAuthUiEvent? = null
+ override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+ if (pendingAuthenticateRequest.value != null) {
+ faceAuthLogger.ignoredFaceAuthTrigger(
+ pendingAuthenticateRequest.value?.uiEvent,
+ "Previously queued trigger skipped due to new request"
+ )
+ }
+ faceAuthLogger.queueingRequest(uiEvent, fallbackToDetection)
+ pendingAuthenticateRequest.value = AuthenticationRequest(uiEvent, fallbackToDetection)
+ }
- override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+ private fun processPendingAuthRequests() {
+ combine(
+ pendingAuthenticateRequest,
+ canRunFaceAuth,
+ canRunDetection,
+ cancellationInProgress,
+ ) { pending, canRunAuth, canRunDetect, cancelInProgress ->
+ if (
+ pending != null &&
+ !(canRunAuth || (canRunDetect && pending.fallbackToDetection)) ||
+ cancelInProgress
+ ) {
+ faceAuthLogger.notProcessingRequestYet(
+ pending?.uiEvent,
+ canRunAuth,
+ canRunDetect,
+ cancelInProgress
+ )
+ return@combine null
+ } else {
+ return@combine pending
+ }
+ }
+ .onEach {
+ it?.let {
+ faceAuthLogger.processingRequest(it.uiEvent, it.fallbackToDetection)
+ clearPendingAuthRequest("Authenticate was invoked")
+ authenticate(it.uiEvent, it.fallbackToDetection)
+ }
+ }
+ .flowOn(mainDispatcher)
+ .launchIn(applicationScope)
+ }
+
+ private suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
if (_isAuthRunning.value) {
faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running")
return
}
- if (cancellationInProgress) {
- faceAuthLogger.queuingRequestWhileCancelling(
- faceAuthRequestedWhileCancellation,
- uiEvent
- )
- faceAuthRequestedWhileCancellation = uiEvent
+ if (cancellationInProgress.value) {
+ faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "cancellation in progress")
return
- } else {
- faceAuthRequestedWhileCancellation = null
}
if (canRunFaceAuth.value) {
@@ -553,12 +581,19 @@ constructor(
FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
)
}
- } else if (fallbackToDetection && canRunDetection.value) {
- faceAuthLogger.ignoredFaceAuthTrigger(
- uiEvent,
- "face auth gating check is false, falling back to detection."
- )
- detect()
+ } else if (canRunDetection.value) {
+ if (fallbackToDetection) {
+ faceAuthLogger.ignoredFaceAuthTrigger(
+ uiEvent,
+ "face auth gating check is false, falling back to detection."
+ )
+ detect()
+ } else {
+ faceAuthLogger.ignoredFaceAuthTrigger(
+ uiEvent = uiEvent,
+ "face auth gating check is false and fallback to detection is not requested"
+ )
+ }
} else {
faceAuthLogger.ignoredFaceAuthTrigger(
uiEvent,
@@ -608,13 +643,13 @@ constructor(
faceAuthLogger.cancelSignalNotReceived(
_isAuthRunning.value,
_isLockedOut.value,
- cancellationInProgress,
- faceAuthRequestedWhileCancellation
+ cancellationInProgress.value,
+ pendingAuthenticateRequest.value?.uiEvent
)
_authenticationStatus.value = ErrorFaceAuthenticationStatus.cancelNotReceivedError()
onFaceAuthRequestCompleted()
}
- cancellationInProgress = true
+ cancellationInProgress.value = true
_isAuthRunning.value = false
}
@@ -647,9 +682,7 @@ constructor(
" supportsFaceDetection: " +
"${faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection}"
)
- pw.println(
- " faceAuthRequestedWhileCancellation: ${faceAuthRequestedWhileCancellation?.reason}"
- )
+ pw.println(" _pendingAuthenticateRequest: ${pendingAuthenticateRequest.value}")
pw.println(" authCancellationSignal: $authCancellationSignal")
pw.println(" detectCancellationSignal: $detectCancellationSignal")
pw.println(" faceAcquiredInfoIgnoreList: $faceAcquiredInfoIgnoreList")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
index 46135fa89451..c8cb9e6aa0dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -56,9 +56,6 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA
get() = emptyFlow()
override fun setLockedOut(isLockedOut: Boolean) = Unit
- override fun pauseFaceAuth() = Unit
-
- override fun resumeFaceAuth() = Unit
/**
* Trigger face authentication.
@@ -69,7 +66,7 @@ class NoopDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceA
*
* Run only face detection when [fallbackToDetection] is true and [canRunFaceAuth] is false.
*/
- override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {}
+ override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) = Unit
/** Stop currently running face authentication or detection. */
override fun cancel() {}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
index ca430da0ffce..a257f529357f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
@@ -172,7 +172,6 @@ constructor(
private fun isFeatureEnabled(): Boolean {
return featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED) &&
- featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI) &&
appContext.resources.getBoolean(R.bool.long_press_keyguard_customize_lockscreen_enabled)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 9c796f846994..7f43cbc69fa2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -380,10 +380,6 @@ constructor(
suspend fun getPickerFlags(): List<KeyguardPickerFlag> {
return listOf(
KeyguardPickerFlag(
- name = Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI,
- value = featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI),
- ),
- KeyguardPickerFlag(
name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED,
value =
!isFeatureDisabledByDevicePolicy() &&
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index ccc2080d8fee..f0df3a2e6a6f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -52,8 +52,6 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
/**
@@ -144,14 +142,11 @@ constructor(
.onEach { (previous, curr) ->
val wasSwitching = previous.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
val isSwitching = curr.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
- if (!wasSwitching && isSwitching) {
- repository.pauseFaceAuth()
- } else if (wasSwitching && !isSwitching) {
+ if (wasSwitching && !isSwitching) {
val lockoutMode = facePropertyRepository.getLockoutMode(curr.userInfo.id)
repository.setLockedOut(
lockoutMode == LockoutMode.PERMANENT || lockoutMode == LockoutMode.TIMED
)
- repository.resumeFaceAuth()
yield()
runFaceAuth(
FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING,
@@ -232,12 +227,8 @@ constructor(
)
} else {
faceAuthenticationStatusOverride.value = null
- applicationScope.launch {
- withContext(mainDispatcher) {
- faceAuthenticationLogger.authRequested(uiEvent)
- repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
- }
- }
+ faceAuthenticationLogger.authRequested(uiEvent)
+ repository.requestAuthenticate(uiEvent, fallbackToDetection = fallbackToDetect)
}
} else {
faceAuthenticationLogger.ignoredFaceAuthTrigger(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 15bb90915d94..43bbf69db883 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -29,6 +29,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSect
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import javax.inject.Inject
@@ -49,6 +50,7 @@ constructor(
defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
defaultStatusViewSection: DefaultStatusViewSection,
+ defaultStatusBarSection: DefaultStatusBarSection,
defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
splitShadeGuidelines: SplitShadeGuidelines,
aodNotificationIconsSection: AodNotificationIconsSection,
@@ -64,6 +66,7 @@ constructor(
defaultAmbientIndicationAreaSection,
defaultSettingsPopupMenuSection,
defaultStatusViewSection,
+ defaultStatusBarSection,
defaultNotificationStackScrollLayoutSection,
splitShadeGuidelines,
aodNotificationIconsSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index 6534dcf5c86b..a9885fc8defe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -26,6 +26,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAr
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import javax.inject.Inject
@@ -41,6 +42,7 @@ constructor(
defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
defaultStatusViewSection: DefaultStatusViewSection,
+ defaultStatusBarSection: DefaultStatusBarSection,
splitShadeGuidelines: SplitShadeGuidelines,
defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection,
aodNotificationIconsSection: AodNotificationIconsSection,
@@ -55,6 +57,7 @@ constructor(
defaultSettingsPopupMenuSection,
alignShortcutsToUdfpsSection,
defaultStatusViewSection,
+ defaultStatusBarSection,
defaultNotificationStackScrollLayoutSection,
splitShadeGuidelines,
aodNotificationIconsSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt
new file mode 100644
index 000000000000..d6c69b3c442f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusBarSection.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import com.android.keyguard.dagger.KeyguardStatusBarViewComponent
+import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.shade.ShadeViewStateProvider
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView
+import com.android.systemui.util.Utils
+import javax.inject.Inject
+
+/** A section for the status bar displayed at the top of the lockscreen. */
+class DefaultStatusBarSection
+@Inject
+constructor(
+ private val context: Context,
+ private val featureFlags: FeatureFlags,
+ private val notificationPanelView: NotificationPanelView,
+ private val keyguardStatusBarViewComponentFactory: KeyguardStatusBarViewComponent.Factory,
+) : KeyguardSection() {
+
+ private val statusBarViewId = R.id.keyguard_header
+
+ override fun addViews(constraintLayout: ConstraintLayout) {
+ if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW)) {
+ return
+ }
+
+ notificationPanelView.findViewById<View>(statusBarViewId)?.let {
+ (it.parent as ViewGroup).removeView(it)
+ }
+
+ val view =
+ LayoutInflater.from(constraintLayout.context)
+ .inflate(R.layout.keyguard_status_bar, constraintLayout, false)
+ as KeyguardStatusBarView
+
+ constraintLayout.addView(view)
+ }
+
+ override fun bindData(constraintLayout: ConstraintLayout) {
+ if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW)) {
+ return
+ }
+
+ val statusBarView =
+ constraintLayout.findViewById<KeyguardStatusBarView>(statusBarViewId) ?: return
+
+ val provider =
+ object : ShadeViewStateProvider {
+ override val lockscreenShadeDragProgress: Float = 0f
+ override val panelViewExpandedHeight: Float = 0f
+ override fun shouldHeadsUpBeVisible(): Boolean {
+ return false
+ }
+ }
+ val statusBarViewComponent =
+ keyguardStatusBarViewComponentFactory.build(statusBarView, provider)
+ val controller = statusBarViewComponent.keyguardStatusBarViewController
+ controller.init()
+ }
+
+ override fun applyConstraints(constraintSet: ConstraintSet) {
+ constraintSet.apply {
+ constrainHeight(statusBarViewId, Utils.getStatusBarHeaderHeightKeyguard(context))
+ connect(statusBarViewId, TOP, PARENT_ID, TOP)
+ connect(statusBarViewId, START, PARENT_ID, START)
+ connect(statusBarViewId, END, PARENT_ID, END)
+ }
+ }
+
+ override fun removeViews(constraintLayout: ConstraintLayout) {
+ constraintLayout.removeView(statusBarViewId)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
index c7b0cb995acc..1cb10bd762a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
@@ -91,7 +91,7 @@ constructor(
it.requireViewById<ViewGroup>(R.id.status_view_media_container)
)
keyguardViewConfigurator.get().keyguardStatusViewController = controller
- notificationPanelViewController.get().updateStatusBarViewController()
+ notificationPanelViewController.get().updateStatusViewController()
}
}
}
@@ -100,6 +100,8 @@ constructor(
constraintSet.apply {
constrainWidth(statusViewId, MATCH_CONSTRAINT)
constrainHeight(statusViewId, WRAP_CONTENT)
+ // TODO(b/296122465): Constrain to the top of [DefaultStatusBarSection] and remove the
+ // extra margin below.
connect(statusViewId, TOP, PARENT_ID, TOP)
connect(statusViewId, START, PARENT_ID, START)
connect(statusViewId, END, PARENT_ID, END)
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 66067b11a18c..8143f99c4d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -29,36 +29,18 @@ class FaceAuthenticationLogger
constructor(
@FaceAuthLog private val logBuffer: LogBuffer,
) {
- fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent, ignoredReason: String) {
+ fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent?, ignoredReason: String) {
logBuffer.log(
TAG,
DEBUG,
{
- str1 = uiEvent.reason
+ str1 = "${uiEvent?.reason}"
str2 = ignoredReason
},
{ "Ignoring trigger because $str2, Trigger reason: $str1" }
)
}
- fun queuingRequestWhileCancelling(
- alreadyQueuedRequest: FaceAuthUiEvent?,
- newRequest: FaceAuthUiEvent
- ) {
- logBuffer.log(
- TAG,
- DEBUG,
- {
- str1 = alreadyQueuedRequest?.reason
- str2 = newRequest.reason
- },
- {
- "Face auth requested while previous request is being cancelled, " +
- "already queued request: $str1 queueing the new request: $str2"
- }
- )
- }
-
fun authenticating(uiEvent: FaceAuthUiEvent) {
logBuffer.log(TAG, DEBUG, { str1 = uiEvent.reason }, { "Running authenticate for $str1" })
}
@@ -161,15 +143,6 @@ constructor(
)
}
- fun launchingQueuedFaceAuthRequest(faceAuthRequestedWhileCancellation: FaceAuthUiEvent?) {
- logBuffer.log(
- TAG,
- DEBUG,
- { str1 = "${faceAuthRequestedWhileCancellation?.reason}" },
- { "Received cancellation error and starting queued face auth request: $str1" }
- )
- }
-
fun faceAuthSuccess(result: FaceManager.AuthenticationResult) {
logBuffer.log(
TAG,
@@ -182,31 +155,10 @@ constructor(
)
}
- fun observedConditionChanged(newValue: Boolean, context: String) {
- logBuffer.log(
- TAG,
- DEBUG,
- {
- bool1 = newValue
- str1 = context
- },
- { "Observed condition changed: $str1, new value: $bool1" }
- )
- }
-
fun canFaceAuthRunChanged(canRun: Boolean) {
logBuffer.log(TAG, DEBUG, { bool1 = canRun }, { "canFaceAuthRun value changed to $bool1" })
}
- fun canRunDetectionChanged(canRunDetection: Boolean) {
- logBuffer.log(
- TAG,
- DEBUG,
- { bool1 = canRunDetection },
- { "canRunDetection value changed to $bool1" }
- )
- }
-
fun cancellingFaceAuth() {
logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false")
}
@@ -236,7 +188,7 @@ constructor(
logBuffer.log(
TAG,
DEBUG,
- { str1 = "$uiEvent" },
+ { str1 = uiEvent.reason },
{ "Requesting face auth for trigger: $str1" }
)
}
@@ -269,4 +221,77 @@ constructor(
fun faceLockedOut(@CompileTimeConstant reason: String) {
logBuffer.log(TAG, DEBUG, "Face auth has been locked out: $reason")
}
+
+ fun queueingRequest(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = "$uiEvent"
+ bool1 = fallbackToDetection
+ },
+ { "Queueing $str1 request for face auth, fallbackToDetection: $bool1" }
+ )
+ }
+
+ fun notProcessingRequestYet(
+ uiEvent: FaceAuthUiEvent?,
+ canRunAuth: Boolean,
+ canRunDetect: Boolean,
+ cancelInProgress: Boolean
+ ) {
+ uiEvent?.let {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = uiEvent.reason
+ bool1 = canRunAuth
+ bool2 = canRunDetect
+ bool3 = cancelInProgress
+ },
+ {
+ "Waiting to process request: reason: $str1, " +
+ "canRunAuth: $bool1, " +
+ "canRunDetect: $bool2, " +
+ "cancelInProgress: $bool3"
+ }
+ )
+ }
+ }
+
+ fun processingRequest(uiEvent: FaceAuthUiEvent?, fallbackToDetection: Boolean) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = "${uiEvent?.reason}"
+ bool1 = fallbackToDetection
+ },
+ { "Processing face auth request: $str1, fallbackToDetect: $bool1" }
+ )
+ }
+
+ fun clearingPendingAuthRequest(
+ @CompileTimeConstant loggingContext: String,
+ uiEvent: FaceAuthUiEvent?,
+ fallbackToDetection: Boolean?
+ ) {
+ uiEvent?.let {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = uiEvent.reason
+ str2 = "$fallbackToDetection"
+ str3 = loggingContext
+ },
+ {
+ "Clearing pending auth: $str1, " +
+ "fallbackToDetection: $str2, " +
+ "reason: $str3"
+ }
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 60fd10492628..cf64a838c2bf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -274,7 +274,9 @@ class MediaProjectionAppSelectorActivity(
recentsViewController.hasRecentTasks
}
- override fun shouldShowContentPreviewWhenEmpty() = shouldShowContentPreview()
+ override fun shouldShowStickyContentPreviewWhenEmpty() = shouldShowContentPreview()
+
+ override fun shouldShowServiceTargets() = false
private fun hasWorkProfile() = mMultiProfilePagerAdapter.count > 1
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 5d732fb8ace9..cbb7e1d0ef83 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
@@ -17,6 +17,7 @@
package com.android.systemui.mediaprojection.appselector.view
import android.app.ActivityOptions
+import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
import android.app.IActivityTaskManager
import android.graphics.Rect
import android.os.Binder
@@ -129,6 +130,9 @@ constructor(
view.width,
view.height
)
+ activityOptions.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
activityOptions.launchCookie = launchCookie
activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
index 9b9d561b5180..0c7705467e10 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -28,7 +28,6 @@ import android.view.View
import android.view.WindowManager
import androidx.core.content.getSystemService
import androidx.core.content.res.use
-import com.android.internal.R as AndroidR
import com.android.systemui.R
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.shared.recents.model.ThumbnailData
@@ -147,25 +146,14 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
previewRect.set(0, 0, thumbnailData.thumbnail.width, thumbnailData.thumbnail.height)
val currentRotation: Int = display.rotation
- val displayWidthPx = windowMetrics.bounds.width()
- val displayHeightPx = windowMetrics.bounds.height()
val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
val isLargeScreen = isLargeScreen(context)
- val taskbarSize =
- if (isLargeScreen) {
- resources.getDimensionPixelSize(AndroidR.dimen.taskbar_frame_height)
- } else {
- 0
- }
previewPositionHelper.updateThumbnailMatrix(
previewRect,
thumbnailData,
measuredWidth,
measuredHeight,
- displayWidthPx,
- displayHeightPx,
- taskbarSize,
isLargeScreen,
currentRotation,
isRtl
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 146b5f57630e..79e7b710c56e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -359,6 +359,7 @@ public class NavigationBarView extends FrameLayout {
public void setBackgroundExecutor(Executor bgExecutor) {
mBgExecutor = bgExecutor;
+ mRotationButtonController.setBgExecutor(bgExecutor);
}
public void setDisplayTracker(DisplayTracker displayTracker) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
new file mode 100644
index 000000000000..e9f907c4d8e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt
@@ -0,0 +1,30 @@
+package com.android.systemui.qs.tiles.base.actions
+
+import android.content.Intent
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/**
+ * Provides a shortcut to start an activity from [QSTileUserActionInteractor]. It supports keyguard
+ * dismissing and tile from-view animations.
+ */
+@SysUISingleton
+class QSTileIntentUserActionHandler
+@Inject
+constructor(private val activityStarter: ActivityStarter) {
+
+ fun handle(userAction: QSTileUserAction, intent: Intent) {
+ val animationController: ActivityLaunchAnimator.Controller? =
+ userAction.view?.let {
+ ActivityLaunchAnimator.Controller.fromView(
+ it,
+ InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
+ )
+ }
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
new file mode 100644
index 000000000000..d6c9705a6aa1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt
@@ -0,0 +1,14 @@
+package com.android.systemui.qs.tiles.base.interactor
+
+import androidx.annotation.WorkerThread
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+
+interface QSTileDataToStateMapper<DATA_TYPE> {
+
+ /**
+ * Maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View layer. It's called
+ * on a background thread, so it's safe to perform long running operations there.
+ */
+ @WorkerThread fun map(config: QSTileConfig, data: DATA_TYPE): QSTileState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
new file mode 100644
index 000000000000..c2a75fac60f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt
@@ -0,0 +1,185 @@
+package com.android.systemui.qs.tiles.base.viewmodel
+
+import androidx.annotation.CallSuper
+import androidx.annotation.VisibleForTesting
+import com.android.internal.util.Preconditions
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.util.kotlin.sample
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Provides a hassle-free way to implement new tiles according to current System UI architecture
+ * standards. THis ViewModel is cheap to instantiate and does nothing until it's moved to
+ * [QSTileLifecycle.ALIVE] state.
+ */
+abstract class BaseQSTileViewModel<DATA_TYPE>
+@VisibleForTesting
+constructor(
+ final override val config: QSTileConfig,
+ private val userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
+ private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
+ private val mapper: QSTileDataToStateMapper<DATA_TYPE>,
+ private val backgroundDispatcher: CoroutineDispatcher,
+ private val tileScope: CoroutineScope,
+) : QSTileViewModel {
+
+ /**
+ * @param config contains all the static information (like TileSpec) about the tile.
+ * @param userActionInteractor encapsulates user input processing logic. Use it to start
+ * activities, show dialogs or otherwise update the tile state.
+ * @param tileDataInteractor provides [DATA_TYPE] and its availability.
+ * @param backgroundDispatcher is used to run the internal [DATA_TYPE] processing and call
+ * interactors methods. This should likely to be @Background CoroutineDispatcher.
+ * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View layer.
+ * It's called in [backgroundDispatcher], so it's safe to perform long running operations
+ * there.
+ */
+ constructor(
+ config: QSTileConfig,
+ userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>,
+ tileDataInteractor: QSTileDataInteractor<DATA_TYPE>,
+ mapper: QSTileDataToStateMapper<DATA_TYPE>,
+ backgroundDispatcher: CoroutineDispatcher,
+ ) : this(
+ config,
+ userActionInteractor,
+ tileDataInteractor,
+ mapper,
+ backgroundDispatcher,
+ CoroutineScope(SupervisorJob())
+ )
+
+ private val userInputs: MutableSharedFlow<QSTileUserAction> =
+ MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ private val userIds: MutableSharedFlow<Int> =
+ MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ private val forceUpdates: MutableSharedFlow<Unit> =
+ MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+
+ private lateinit var tileData: SharedFlow<DATA_TYPE>
+
+ override lateinit var state: SharedFlow<QSTileState>
+ override val isAvailable: StateFlow<Boolean> =
+ tileDataInteractor
+ .availability()
+ .flowOn(backgroundDispatcher)
+ .stateIn(
+ tileScope,
+ SharingStarted.WhileSubscribed(),
+ false,
+ )
+
+ private var currentLifeState: QSTileLifecycle = QSTileLifecycle.DEAD
+
+ @CallSuper
+ override fun forceUpdate() {
+ Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
+ forceUpdates.tryEmit(Unit)
+ }
+
+ @CallSuper
+ override fun onUserIdChanged(userId: Int) {
+ Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
+ userIds.tryEmit(userId)
+ }
+
+ @CallSuper
+ override fun onActionPerformed(userAction: QSTileUserAction) {
+ Preconditions.checkState(tileData.replayCache.isNotEmpty())
+ Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
+ userInputs.tryEmit(userAction)
+ }
+
+ @CallSuper
+ override fun onLifecycle(lifecycle: QSTileLifecycle) {
+ when (lifecycle) {
+ QSTileLifecycle.ALIVE -> {
+ Preconditions.checkState(currentLifeState == QSTileLifecycle.DEAD)
+ tileData = createTileDataFlow()
+ state =
+ tileData
+ // TODO(b/299908705): log data and corresponding tile state
+ .map { mapper.map(config, it) }
+ .flowOn(backgroundDispatcher)
+ .shareIn(
+ tileScope,
+ SharingStarted.WhileSubscribed(),
+ replay = 1,
+ )
+ }
+ QSTileLifecycle.DEAD -> {
+ Preconditions.checkState(currentLifeState == QSTileLifecycle.ALIVE)
+ tileScope.coroutineContext.cancelChildren()
+ }
+ }
+ currentLifeState = lifecycle
+ }
+
+ private fun createTileDataFlow(): SharedFlow<DATA_TYPE> =
+ userIds
+ .flatMapLatest { userId ->
+ merge(
+ userInputFlow(),
+ forceUpdates.map { StateUpdateTrigger.ForceUpdate },
+ )
+ .onStart { emit(StateUpdateTrigger.InitialRequest) }
+ .map { trigger -> QSTileDataRequest(userId, trigger) }
+ }
+ .flatMapLatest { request ->
+ // 1) get an updated data source
+ // 2) process user input, possibly triggering new data to be emitted
+ // This handles the case when the data isn't buffered in the interactor
+ // TODO(b/299908705): Log events that trigger data flow to update
+ val dataFlow = tileDataInteractor.tileData(request)
+ if (request.trigger is StateUpdateTrigger.UserAction<*>) {
+ userActionInteractor.handleInput(
+ request.trigger.action,
+ request.trigger.tileData as DATA_TYPE,
+ )
+ }
+ dataFlow
+ }
+ .flowOn(backgroundDispatcher)
+ .shareIn(
+ tileScope,
+ SharingStarted.WhileSubscribed(),
+ replay = 1, // we only care about the most recent value
+ )
+
+ private fun userInputFlow(): Flow<StateUpdateTrigger> {
+ data class StateWithData<T>(val state: QSTileState, val data: T)
+
+ // Skip the input until there is some data
+ return userInputs.sample(
+ state.combine(tileData) { state, data -> StateWithData(state, data) }
+ ) { input, stateWithData ->
+ StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
index 39db7038bfa2..1d5c1bcada1f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt
@@ -1,6 +1,6 @@
package com.android.systemui.qs.tiles.viewmodel
enum class QSTileLifecycle {
- ON_CREATE,
- ON_DESTROY,
+ ALIVE,
+ DEAD,
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index 49077f3f310d..d66d0a195e02 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -1,28 +1,35 @@
package com.android.systemui.qs.tiles.viewmodel
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
/**
* Represents tiles behaviour logic. This ViewModel is a connection between tile view and data
- * layers.
+ * layers. All direct inheritors must be added to the [QSTileViewModelInterfaceComplianceTest] class
+ * to pass compliance tests.
+ *
+ * All methods of this view model should be considered running on the main thread. This means no
+ * synchronous long running operations are permitted in any method.
*/
interface QSTileViewModel {
/**
- * State of the tile to be shown by the view. Favor reactive consumption over the
- * [StateFlow.value], because there is no guarantee that current value would be available at any
- * time.
+ * State of the tile to be shown by the view. It's guaranteed that it's only accessed between
+ * [QSTileLifecycle.ALIVE] and [QSTileLifecycle.DEAD].
*/
- val state: StateFlow<QSTileState>
+ val state: SharedFlow<QSTileState>
val config: QSTileConfig
- val isAvailable: Flow<Boolean>
+ /**
+ * Specifies whether this device currently supports this tile. This might be called outside of
+ * [QSTileLifecycle.ALIVE] and [QSTileLifecycle.DEAD] bounds (for example in Edit Mode).
+ */
+ val isAvailable: StateFlow<Boolean>
/**
* Handles ViewModel lifecycle. Implementations should be inactive outside of
- * [QSTileLifecycle.ON_CREATE] and [QSTileLifecycle.ON_DESTROY] bounds.
+ * [QSTileLifecycle.ALIVE] and [QSTileLifecycle.DEAD] bounds.
*/
fun onLifecycle(lifecycle: QSTileLifecycle)
@@ -33,9 +40,17 @@ interface QSTileViewModel {
*/
fun onUserIdChanged(userId: Int)
- /** Triggers emit of the new [QSTileState] in [state]. */
+ /** Triggers the emission of the new [QSTileState] in a [state]. */
fun forceUpdate()
/** Notifies underlying logic about user input. */
fun onActionPerformed(userAction: QSTileUserAction)
}
+
+/**
+ * Returns the immediate state of the tile or null if the state haven't been collected yet. Favor
+ * reactive consumption over the [currentState], because there is no guarantee that current value
+ * would be available at any time.
+ */
+val QSTileViewModel.currentState: QSTileState?
+ get() = state.replayCache.lastOrNull()
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index d23bedace06f..cef52e73d711 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -151,6 +151,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private SysUiState mSysUiState;
private final Handler mHandler;
private final Lazy<NavigationBarController> mNavBarControllerLazy;
+ private final ScreenPinningRequest mScreenPinningRequest;
private final NotificationShadeWindowController mStatusBarWinController;
private final Provider<SceneInteractor> mSceneInteractor;
@@ -185,9 +186,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
@Override
public void startScreenPinning(int taskId) {
verifyCallerAndClearCallingIdentityPostMain("startScreenPinning", () ->
- mCentralSurfacesOptionalLazy.get().ifPresent(
- statusBar -> statusBar.showScreenPinningRequest(taskId,
- false /* allowCancel */)));
+ mScreenPinningRequest.showPrompt(taskId, false /* allowCancel */));
}
@Override
@@ -572,6 +571,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
Lazy<NavigationBarController> navBarControllerLazy,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Lazy<ShadeViewController> shadeViewControllerLazy,
+ ScreenPinningRequest screenPinningRequest,
NavigationModeController navModeController,
NotificationShadeWindowController statusBarWinController,
SysUiState sysUiState,
@@ -601,6 +601,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
mShadeViewControllerLazy = shadeViewControllerLazy;
mHandler = new Handler();
mNavBarControllerLazy = navBarControllerLazy;
+ mScreenPinningRequest = screenPinningRequest;
mStatusBarWinController = statusBarWinController;
mSceneInteractor = sceneInteractor;
mUserTracker = userTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 346acf958e51..77fcd6813583 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -53,8 +53,10 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
+import com.android.systemui.CoreStartable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.UserTracker;
@@ -69,8 +71,9 @@ import javax.inject.Inject;
import dagger.Lazy;
+@SysUISingleton
public class ScreenPinningRequest implements View.OnClickListener,
- NavigationModeController.ModeChangedListener {
+ NavigationModeController.ModeChangedListener, CoreStartable {
private static final String TAG = "ScreenPinningRequest";
private final Context mContext;
@@ -113,6 +116,9 @@ public class ScreenPinningRequest implements View.OnClickListener,
mUserTracker = userTracker;
}
+ @Override
+ public void start() {}
+
public void clearPrompt() {
if (mRequestWindow != null) {
mWindowManager.removeView(mRequestWindow);
@@ -144,7 +150,8 @@ public class ScreenPinningRequest implements View.OnClickListener,
mNavBarMode = mode;
}
- public void onConfigurationChanged() {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
if (mRequestWindow != null) {
mRequestWindow.onConfigurationChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index c5bc2fb8ea90..03c3f7a8ddf3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -150,16 +150,18 @@ constructor(
}
/**
- * Returns a [RequestCallback] that calls [RequestCallback.onFinish] only when all callbacks for
- * id created have finished.
+ * Returns a [RequestCallback] that wraps [originalCallback].
*
- * If any callback created calls [reportError], then following [onFinish] are not considered.
+ * Each [RequestCallback] created with [createCallbackForId] is expected to be used with either
+ * [reportError] or [onFinish]. Once they are both called:
+ * - If any finished with an error, [reportError] of [originalCallback] is called
+ * - Otherwise, [onFinish] is called.
*/
private class MultiResultCallbackWrapper(
private val originalCallback: RequestCallback,
) {
private val idsPending = mutableSetOf<Int>()
- private var errorReported = false
+ private val idsWithErrors = mutableSetOf<Int>()
/**
* Creates a callback for [id].
@@ -172,25 +174,34 @@ constructor(
idsPending += id
return object : RequestCallback {
override fun reportError() {
- Log.d(TAG, "ReportError id=$id")
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TAG, id)
- Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, "reportError id=$id")
- originalCallback.reportError()
- errorReported = true
+ endTrace("reportError id=$id")
+ idsWithErrors += id
+ idsPending -= id
+ reportToOriginalIfNeeded()
}
override fun onFinish() {
- Log.d(TAG, "onFinish id=$id")
- if (errorReported) return
+ endTrace("onFinish id=$id")
idsPending -= id
- Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, "onFinish id=$id")
- if (idsPending.isEmpty()) {
- Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TAG, id)
- originalCallback.onFinish()
- }
+ reportToOriginalIfNeeded()
+ }
+
+ private fun endTrace(reason: String) {
+ Log.d(TAG, "Finished waiting for id=$id. $reason")
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TAG, id)
+ Trace.instantForTrack(Trace.TRACE_TAG_APP, TAG, reason)
}
}
}
+
+ private fun reportToOriginalIfNeeded() {
+ if (idsPending.isNotEmpty()) return
+ if (idsWithErrors.isEmpty()) {
+ originalCallback.onFinish()
+ } else {
+ originalCallback.reportError()
+ }
+ }
}
private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 1e8542fe8f0c..32df5121b0b8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -99,7 +99,11 @@ public class TakeScreenshotService extends Service {
/** Informs about coarse grained state of the Controller. */
public interface RequestCallback {
- /** Respond to the current request indicating the screenshot request failed. */
+ /**
+ * Respond to the current request indicating the screenshot request failed.
+ * <p>
+ * After this, the service will be disconnected and all visible UI is removed.
+ */
void reportError();
/** The controller has completed handling this request UI has been removed */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 15a097239704..d2e80fc7cf65 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -205,7 +205,6 @@ import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
-import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
@@ -381,7 +380,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private int mMaxAllowedKeyguardNotifications;
private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
- private KeyguardStatusBarView mKeyguardStatusBar;
private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
private KeyguardStatusViewController mKeyguardStatusViewController;
private final LockIconViewController mLockIconViewController;
@@ -1035,7 +1033,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@VisibleForTesting
void onFinishInflate() {
loadDimens();
- mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
FrameLayout userAvatarContainer = null;
KeyguardUserSwitcherView keyguardUserSwitcherView = null;
@@ -1053,7 +1050,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mKeyguardStatusBarViewController =
mKeyguardStatusBarViewComponentFactory.build(
- mKeyguardStatusBar,
+ mView.findViewById(R.id.keyguard_header),
mShadeViewStateProvider)
.getKeyguardStatusBarViewController();
mKeyguardStatusBarViewController.init();
@@ -1226,7 +1223,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private void updateViewControllers(
FrameLayout userAvatarView,
KeyguardUserSwitcherView keyguardUserSwitcherView) {
- updateStatusBarViewController();
+ updateStatusViewController();
if (mKeyguardUserSwitcherController != null) {
// Try to close the switcher so that callbacks are triggered if necessary.
// Otherwise, NPV can get into a state where some of the views are still hidden
@@ -1257,7 +1254,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
/** Updates the StatusBarViewController and updates any that depend on it. */
- public void updateStatusBarViewController() {
+ public void updateStatusViewController() {
// Re-associate the KeyguardStatusViewController
if (mKeyguardStatusViewController != null) {
mKeyguardStatusViewController.onDestroy();
@@ -1842,6 +1839,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
/** Returns extra space available to show the shelf on lockscreen */
@VisibleForTesting
float getVerticalSpaceForLockscreenShelf() {
+ if (mSplitShadeEnabled) {
+ return 0f;
+ }
final float lockIconPadding = getLockIconPadding();
final float noShelfOverlapBottomPadding =
@@ -3730,8 +3730,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
expand = true;
mShadeLog.logEndMotionEvent("endMotionEvent: cancel while on keyguard",
forceCancel, expand);
- } else if (mCentralSurfaces.isBouncerShowingOverDream()) {
- expand = false;
} else {
// If we get a cancel, put the shade back to the state it was in when the
// gesture started
@@ -4891,10 +4889,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
return false;
}
- // Do not allow panel expansion if bouncer is scrimmed or showing over a dream,
+ // Do not allow panel expansion if bouncer is scrimmed,
// otherwise user would be able to pull down QS or expand the shade.
- if (mCentralSurfaces.isBouncerShowingScrimmed()
- || mCentralSurfaces.isBouncerShowingOverDream()) {
+ if (mCentralSurfaces.isBouncerShowingScrimmed()) {
mShadeLog.logMotionEvent(event,
"onTouch: ignore touch, bouncer scrimmed or showing over dream");
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 95a072c0d4c9..b77b9e488d7e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -35,15 +35,20 @@ import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.isActive
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.isActive
/** Business logic for shade interactions. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -119,18 +124,41 @@ constructor(
repository.qsExpansion
}
- /** The amount [0-1] either QS or the shade has been opened */
+ /** The amount [0-1] either QS or the shade has been opened. */
val anyExpansion: StateFlow<Float> =
combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) }
.stateIn(scope, SharingStarted.Eagerly, 0f)
/** Whether either the shade or QS is expanding from a fully collapsed state. */
- val anyExpanding =
+ val isAnyExpanding =
anyExpansion
.pairwise(1f)
.map { (prev, curr) -> curr > 0f && curr < 1f && prev < 1f }
.distinctUntilChanged()
+ /**
+ * Whether the user is expanding or collapsing the shade with user input. This will be true even
+ * if the user's input gesture has ended but a transition they initiated is animating.
+ */
+ val isUserInteractingWithShade: Flow<Boolean> =
+ userInteractingFlow(repository.legacyShadeTracking, repository.legacyShadeExpansion)
+
+ /**
+ * Whether the user is expanding or collapsing quick settings with user input. This will be true
+ * even if the user's input gesture has ended but a transition they initiated is still
+ * animating.
+ */
+ val isUserInteractingWithQs: Flow<Boolean> =
+ userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
+
+ /**
+ * Whether the user is expanding or collapsing either the shade or quick settings with user
+ * input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended
+ * but a transition they initiated is still animating.
+ */
+ val isUserInteracting: Flow<Boolean> =
+ combine(isUserInteractingWithShade, isUserInteractingWithShade) { shade, qs -> shade || qs }
+
/** Emits true if the shade can be expanded from QQS to QS and false otherwise. */
val isExpandToQsEnabled: Flow<Boolean> =
combine(
@@ -169,4 +197,30 @@ constructor(
}
}
.distinctUntilChanged()
+
+ /**
+ * Return a flow for whether a user is interacting with an expandable shade component using
+ * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that
+ * [expansion.first] checks the current value of the flow.
+ */
+ private fun userInteractingFlow(
+ tracking: Flow<Boolean>,
+ expansion: StateFlow<Float>
+ ): Flow<Boolean> {
+ return flow {
+ // initial value is false
+ emit(false)
+ while (currentCoroutineContext().isActive) {
+ // wait for tracking to become true
+ tracking.first { it }
+ emit(true)
+ // wait for tracking to become false
+ tracking.first { !it }
+ // wait for expansion to complete in either direction
+ expansion.first { it <= 0f || it >= 1f }
+ // interaction complete
+ emit(false)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index e20614178885..70ccc4f3ae43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -31,6 +31,7 @@ import android.view.ViewGroup;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -38,6 +39,7 @@ import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationClicker;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.icon.IconManager;
+import com.android.systemui.statusbar.notification.row.BigPictureIconManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
@@ -151,6 +153,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
component.getExpandableNotificationRowController();
rowController.init(entry);
entry.setRowController(rowController);
+ maybeSetBigPictureIconManager(row, component);
bindRow(entry, row);
updateRow(entry, row);
inflateContentViews(entry, params, row, callback);
@@ -165,6 +168,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
return;
}
mLogger.logReleasingViews(entry);
+ cancelRunningJobs(entry.getRow());
final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED);
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED);
@@ -172,6 +176,23 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
mRowContentBindStage.requestRebind(entry, null);
}
+ private void maybeSetBigPictureIconManager(ExpandableNotificationRow row,
+ ExpandableNotificationRowComponent component) {
+ if (mFeatureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
+ row.setBigPictureIconManager(component.getBigPictureIconManager());
+ }
+ }
+
+ private void cancelRunningJobs(ExpandableNotificationRow row) {
+ if (row == null) {
+ return;
+ }
+ BigPictureIconManager iconManager = row.getBigPictureIconManager();
+ if (iconManager != null) {
+ iconManager.cancelJobs();
+ }
+ }
+
/**
* Bind row to various controllers and managers. This is only called when the row is first
* created.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
new file mode 100644
index 000000000000..88dbb4cf1342
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.annotation.WorkerThread
+import android.app.ActivityManager
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.util.Dumpable
+import android.util.Log
+import android.util.Size
+import com.android.internal.R
+import com.android.internal.widget.NotificationDrawableConsumer
+import com.android.internal.widget.NotificationIconManager
+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.graphics.ImageLoader
+import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.Empty
+import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.FullImage
+import com.android.systemui.statusbar.notification.row.BigPictureIconManager.DrawableState.PlaceHolder
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlin.math.min
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+private const val TAG = "BigPicImageLoader"
+private const val DEBUG = false
+private const val FREE_IMAGE_DELAY_MS = 3000L
+
+/**
+ * A helper class for [com.android.internal.widget.BigPictureNotificationImageView] to lazy-load
+ * images from SysUI. It replaces the placeholder image with the fully loaded one, and vica versa.
+ *
+ * TODO(b/283082473) move the logs to a [com.android.systemui.log.LogBuffer]
+ */
+@SuppressWarnings("DumpableNotRegistered")
+class BigPictureIconManager
+@Inject
+constructor(
+ private val context: Context,
+ private val imageLoader: ImageLoader,
+ @Application private val scope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val bgDispatcher: CoroutineDispatcher
+) : NotificationIconManager, Dumpable {
+
+ private var lastLoadingJob: Job? = null
+ private var drawableConsumer: NotificationDrawableConsumer? = null
+ private var displayedState: DrawableState = Empty(null)
+ private var viewShown = false
+
+ private var maxWidth = getMaxWidth()
+ private var maxHeight = getMaxHeight()
+
+ /**
+ * Called when the displayed state changes of the view.
+ *
+ * @param shown true if the view is shown, and the image needs to be displayed.
+ */
+ fun onViewShown(shown: Boolean) {
+ log("onViewShown:$shown")
+
+ if (this.viewShown != shown) {
+ this.viewShown = shown
+
+ val state = displayedState
+
+ this.lastLoadingJob?.cancel()
+ this.lastLoadingJob =
+ when {
+ state is Empty && shown -> state.icon?.let(::startLoadingJob)
+ state is PlaceHolder && shown -> startLoadingJob(state.icon)
+ state is FullImage && !shown ->
+ startFreeImageJob(state.icon, state.drawableSize)
+ else -> null
+ }
+ }
+ }
+
+ /**
+ * Update the maximum width and height allowed for bitmaps, ex. after a configuration change.
+ */
+ fun updateMaxImageSizes() {
+ log("updateMaxImageSizes")
+ maxWidth = getMaxWidth()
+ maxHeight = getMaxHeight()
+ }
+
+ /** Cancels all currently running jobs. */
+ fun cancelJobs() {
+ lastLoadingJob?.cancel()
+ }
+
+ @WorkerThread
+ override fun updateIcon(drawableConsumer: NotificationDrawableConsumer, icon: Icon?): Runnable {
+ if (this.drawableConsumer != null && this.drawableConsumer != drawableConsumer) {
+ Log.wtf(TAG, "A consumer is already set for this iconManager.")
+ return Runnable {}
+ }
+
+ if (displayedState.iconSameAs(icon)) {
+ // We're already handling this icon, nothing to do here.
+ log("skipping updateIcon for consumer:$drawableConsumer with icon:$icon")
+ return Runnable {}
+ }
+
+ this.drawableConsumer = drawableConsumer
+ this.displayedState = Empty(icon)
+ this.lastLoadingJob?.cancel()
+
+ val drawable = loadImageOrPlaceHolderSync(icon)
+
+ log("icon updated")
+
+ return Runnable { drawableConsumer.setImageDrawable(drawable) }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>?) {
+ pw.println("BigPictureIconManager ${getDebugString()}")
+ }
+
+ @WorkerThread
+ private fun loadImageOrPlaceHolderSync(icon: Icon?): Drawable? {
+ icon ?: return null
+
+ if (viewShown) {
+ return loadImageSync(icon)
+ }
+
+ return loadPlaceHolderSync(icon) ?: loadImageSync(icon)
+ }
+
+ @WorkerThread
+ private fun loadImageSync(icon: Icon): Drawable? {
+ return imageLoader.loadDrawableSync(icon, context, maxWidth, maxHeight)?.also { drawable ->
+ checkPlaceHolderSizeForDrawable(this.displayedState, drawable)
+ this.displayedState = FullImage(icon, drawable.intrinsicSize)
+ }
+ }
+
+ private fun checkPlaceHolderSizeForDrawable(
+ displayedState: DrawableState,
+ newDrawable: Drawable
+ ) {
+ if (displayedState is PlaceHolder) {
+ val (oldWidth, oldHeight) = displayedState.drawableSize
+ val (newWidth, newHeight) = newDrawable.intrinsicSize
+
+ if (oldWidth != newWidth || oldHeight != newHeight) {
+ Log.e(
+ TAG,
+ "Mismatch in dimensions, when replacing PlaceHolder " +
+ "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight."
+ )
+ }
+ }
+ }
+
+ @WorkerThread
+ private fun loadPlaceHolderSync(icon: Icon): Drawable? {
+ return imageLoader
+ .loadSizeSync(icon, context)
+ ?.resizeToMax(maxWidth, maxHeight) // match the dimensions of the fully loaded drawable
+ ?.let { size -> createPlaceHolder(size) }
+ ?.also { drawable -> this.displayedState = PlaceHolder(icon, drawable.intrinsicSize) }
+ }
+
+ private fun startLoadingJob(icon: Icon): Job =
+ scope.launch {
+ val drawable = withContext(bgDispatcher) { loadImageSync(icon) }
+ withContext(mainDispatcher) { drawableConsumer?.setImageDrawable(drawable) }
+ log("image loaded")
+ }
+
+ private fun startFreeImageJob(icon: Icon, drawableSize: Size): Job =
+ scope.launch {
+ delay(FREE_IMAGE_DELAY_MS)
+ val drawable = createPlaceHolder(drawableSize)
+ displayedState = PlaceHolder(icon, drawable.intrinsicSize)
+ withContext(mainDispatcher) { drawableConsumer?.setImageDrawable(drawable) }
+ log("placeholder loaded")
+ }
+
+ private fun createPlaceHolder(size: Size): Drawable {
+ return PlaceHolderDrawable(width = size.width, height = size.height)
+ }
+
+ private fun isLowRam(): Boolean {
+ return ActivityManager.isLowRamDeviceStatic()
+ }
+
+ private fun getMaxWidth() =
+ context.resources.getDimensionPixelSize(
+ if (isLowRam()) {
+ R.dimen.notification_big_picture_max_width_low_ram
+ } else {
+ R.dimen.notification_big_picture_max_width
+ }
+ )
+
+ private fun getMaxHeight() =
+ context.resources.getDimensionPixelSize(
+ if (isLowRam()) {
+ R.dimen.notification_big_picture_max_height_low_ram
+ } else {
+ R.dimen.notification_big_picture_max_height
+ }
+ )
+
+ private fun log(msg: String) {
+ if (DEBUG) {
+ Log.d(TAG, "$msg state=${getDebugString()}")
+ }
+ }
+
+ private fun getDebugString() =
+ "{ state:$displayedState, hasConsumer:${drawableConsumer != null}, viewShown:$viewShown}"
+
+ private sealed class DrawableState(open val icon: Icon?) {
+ data class Empty(override val icon: Icon?) : DrawableState(icon)
+ data class PlaceHolder(override val icon: Icon, val drawableSize: Size) :
+ DrawableState(icon)
+ data class FullImage(override val icon: Icon, val drawableSize: Size) : DrawableState(icon)
+
+ fun iconSameAs(other: Icon?): Boolean {
+ val displayedIcon = icon
+ return when {
+ displayedIcon == null && other == null -> true
+ displayedIcon != null && other != null -> displayedIcon.sameAs(other)
+ else -> false
+ }
+ }
+ }
+}
+
+/**
+ * @return an image size that conforms to the maxWidth / maxHeight parameters. It can be the same
+ * instance, if the provided size was already small enough.
+ */
+private fun Size.resizeToMax(maxWidth: Int, maxHeight: Int): Size {
+ if (width <= maxWidth && height <= maxHeight) {
+ return this
+ }
+
+ // Calculate the scale factor for both dimensions
+ val wScale =
+ if (maxWidth <= 0) {
+ 1.0f
+ } else {
+ maxWidth.toFloat() / width.toFloat()
+ }
+
+ val hScale =
+ if (maxHeight <= 0) {
+ 1.0f
+ } else {
+ maxHeight.toFloat() / height.toFloat()
+ }
+
+ // Scale down to the smaller scale factor
+ val scale = min(wScale, hScale)
+ if (scale < 1.0f) {
+ val targetWidth = (width * scale).toInt()
+ val targetHeight = (height * scale).toInt()
+
+ return Size(targetWidth, targetHeight)
+ }
+
+ return this
+}
+
+private val Drawable.intrinsicSize
+ get() = Size(/*width=*/ intrinsicWidth, /*height=*/ intrinsicHeight)
+
+private operator fun Size.component1() = width
+
+private operator fun Size.component2() = height
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureLayoutInflaterFactory.kt
new file mode 100644
index 000000000000..e22666574e0d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureLayoutInflaterFactory.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import com.android.internal.widget.BigPictureNotificationImageView
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
+import javax.inject.Inject
+
+class BigPictureLayoutInflaterFactory @Inject constructor() : NotifRemoteViewsFactory {
+
+ override fun instantiate(
+ row: ExpandableNotificationRow,
+ @InflationFlag layoutType: Int,
+ parent: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? {
+ // Currently the [BigPictureIconManager] only handles one view per notification.
+ // Exclude other layout types for now, to make sure that we set the same iconManager
+ // on only one [BigPictureNotificationImageView].
+ if (layoutType != FLAG_CONTENT_VIEW_EXPANDED) {
+ return null
+ }
+
+ return when (name) {
+ BigPictureNotificationImageView::class.java.name ->
+ BigPictureNotificationImageView(context, attrs).also { view ->
+ view.setIconManager(row.bigPictureIconManager)
+ }
+ else -> null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index c02382dcde94..7fa955bc75eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -376,6 +376,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private float mTranslationWhenRemoved;
private boolean mWasChildInGroupWhenRemoved;
private NotificationInlineImageResolver mImageResolver;
+ private BigPictureIconManager mBigPictureIconManager;
@Nullable
private OnExpansionChangedListener mExpansionChangedListener;
@Nullable
@@ -1355,6 +1356,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mImageResolver != null) {
mImageResolver.updateMaxImageSizes();
}
+ if (mBigPictureIconManager != null) {
+ mBigPictureIconManager.updateMaxImageSizes();
+ }
}
public void onUiModeChanged() {
@@ -1794,6 +1798,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mImageResolver;
}
+ public BigPictureIconManager getBigPictureIconManager() {
+ return mBigPictureIconManager;
+ }
+
+ public void setBigPictureIconManager(
+ BigPictureIconManager bigPictureIconManager) {
+ mBigPictureIconManager = bigPictureIconManager;
+ }
+
+
/**
* Resets this view so it can be re-used for an updated notification.
*/
@@ -3687,6 +3701,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
pw.println("no viewState!!!");
}
pw.println(getRoundableState().debugString());
+ if (mBigPictureIconManager != null) {
+ mBigPictureIconManager.dump(pw, args);
+ }
dumpBackgroundView(pw, args);
int transientViewCount = mChildrenContainer == null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 867e08b2e743..0239afc08ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -60,12 +60,16 @@ public abstract class NotificationRowModule {
@Named(NOTIF_REMOTEVIEWS_FACTORIES)
static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
FeatureFlags featureFlags,
- PrecomputedTextViewFactory precomputedTextViewFactory
+ PrecomputedTextViewFactory precomputedTextViewFactory,
+ BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory
) {
final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
if (featureFlags.isEnabled(Flags.PRECOMPUTED_TEXT)) {
replacementFactories.add(precomputedTextViewFactory);
}
+ if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
+ replacementFactories.add(bigPictureLayoutInflaterFactory);
+ }
return replacementFactories;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PlaceHolderDrawable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PlaceHolderDrawable.kt
new file mode 100644
index 000000000000..40aa27fc4592
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PlaceHolderDrawable.kt
@@ -0,0 +1,33 @@
+package com.android.systemui.statusbar.notification.row
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+import android.graphics.drawable.Drawable
+
+class PlaceHolderDrawable(private val width: Int, private val height: Int) : Drawable() {
+
+ companion object {
+ fun createFrom(other: Drawable): PlaceHolderDrawable {
+ return PlaceHolderDrawable(other.intrinsicWidth, other.intrinsicHeight)
+ }
+ }
+
+ override fun getIntrinsicWidth(): Int {
+ return width
+ }
+
+ override fun getIntrinsicHeight(): Int {
+ return height
+ }
+
+ override fun draw(canvas: Canvas) {}
+ override fun setAlpha(alpha: Int) {}
+ override fun setColorFilter(colorFilter: ColorFilter?) {}
+
+ @Suppress("DeprecatedCallableAddReplaceWith")
+ @Deprecated("Deprecated in android.graphics.drawable.Drawable")
+ override fun getOpacity(): Int {
+ return PixelFormat.TRANSPARENT
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
index 3588621b12ad..0a6a2c84b0c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/ExpandableNotificationRowComponent.java
@@ -23,6 +23,7 @@ import android.service.notification.StatusBarNotification;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.BigPictureIconManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -68,6 +69,12 @@ public interface ExpandableNotificationRowComponent {
ExpandableNotificationRowController getExpandableNotificationRowController();
/**
+ * Creates a BigPictureIconManager.
+ */
+ @NotificationRowScope
+ BigPictureIconManager getBigPictureIconManager();
+
+ /**
* Dagger Module that extracts interesting properties from an ExpandableNotificationRow.
*/
@Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
index 175ba15eebae..acd6cc69b553 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapper.java
@@ -28,6 +28,7 @@ import android.view.View;
import com.android.internal.R;
import com.android.internal.widget.BigPictureNotificationImageView;
import com.android.systemui.statusbar.notification.ImageTransformState;
+import com.android.systemui.statusbar.notification.row.BigPictureIconManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
@@ -66,6 +67,17 @@ public class NotificationBigPictureTemplateViewWrapper extends NotificationTempl
}
}
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+
+ BigPictureIconManager imageManager = mRow.getBigPictureIconManager();
+ if (imageManager != null) {
+ // TODO(b/283082473) call it a bit earlier for true, as soon as the row starts to expand
+ imageManager.onViewShown(visible);
+ }
+ }
+
/**
* Starts or stops the animations in any drawables contained in this BigPicture Notification.
*
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 cfa481eaf7ef..7d575685dcae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -269,8 +269,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
boolean isScreenFullyOff();
- void showScreenPinningRequest(int taskId, boolean allowCancel);
-
@Nullable
Intent getEmergencyActionIntent();
@@ -301,8 +299,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
boolean isBouncerShowingScrimmed();
- boolean isBouncerShowingOverDream();
-
void updateNotificationPanelTouchState();
int getRotation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 28bb58108916..ebcfb8adb08d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -59,6 +59,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.QuickSettingsController;
@@ -84,6 +85,7 @@ import javax.inject.Inject;
public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callbacks {
private final CentralSurfaces mCentralSurfaces;
private final Context mContext;
+ private final ScreenPinningRequest mScreenPinningRequest;
private final com.android.systemui.shade.ShadeController mShadeController;
private final CommandQueue mCommandQueue;
private final ShadeViewController mShadeViewController;
@@ -126,6 +128,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
QuickSettingsController quickSettingsController,
Context context,
@Main Resources resources,
+ ScreenPinningRequest screenPinningRequest,
ShadeController shadeController,
CommandQueue commandQueue,
ShadeViewController shadeViewController,
@@ -155,6 +158,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
mCentralSurfaces = centralSurfaces;
mQsController = quickSettingsController;
mContext = context;
+ mScreenPinningRequest = screenPinningRequest;
mShadeController = shadeController;
mCommandQueue = commandQueue;
mShadeViewController = shadeViewController;
@@ -516,7 +520,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
return;
}
// Show screen pinning request, since this comes from an app, show 'no thanks', button.
- mCentralSurfaces.showScreenPinningRequest(taskId, true);
+ mScreenPinningRequest.showPrompt(taskId, true);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index ff380db99c16..5e505f733c93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -71,7 +71,6 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces {
override fun isOverviewEnabled() = false
override fun setBouncerShowing(bouncerShowing: Boolean) {}
override fun isScreenFullyOff() = false
- override fun showScreenPinningRequest(taskId: Int, allowCancel: Boolean) {}
override fun getEmergencyActionIntent(): Intent? = null
override fun isCameraAllowedByAdmin() = false
override fun isGoingToSleep() = false
@@ -84,7 +83,6 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces {
override fun awakenDreams() {}
override fun isBouncerShowing() = false
override fun isBouncerShowingScrimmed() = false
- override fun isBouncerShowingOverDream() = false
override fun updateNotificationPanelTouchState() {}
override fun getRotation() = 0
override fun setBarStateForTest(state: Int) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 490c469261d9..8d35d39bceea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -168,7 +168,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
-import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.UserTracker;
@@ -388,7 +387,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
*/
protected int mState; // TODO: remove this. Just use StatusBarStateController
protected boolean mBouncerShowing;
- private boolean mBouncerShowingOverDream;
private final PhoneStatusBarPolicy mIconPolicy;
@@ -507,8 +505,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
? new GestureRecorder("/sdcard/statusbar_gestures.dat")
: null;
- private final ScreenPinningRequest mScreenPinningRequest;
-
private final MetricsLogger mMetricsLogger;
// ensure quick settings is disabled until the current user makes it through the setup wizard
@@ -692,7 +688,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
DozeServiceHost dozeServiceHost,
BackActionInteractor backActionInteractor,
PowerManager powerManager,
- ScreenPinningRequest screenPinningRequest,
DozeScrimController dozeScrimController,
VolumeComponent volumeComponent,
CommandQueue commandQueue,
@@ -799,7 +794,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mDozeParameters = dozeParameters;
mScrimController = scrimController;
mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
- mScreenPinningRequest = screenPinningRequest;
mDozeScrimController = dozeScrimController;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
mAuthRippleController = authRippleController;
@@ -2460,10 +2454,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
*/
@Override
public boolean shouldKeyguardHideImmediately() {
- final boolean isScrimmedBouncer =
- mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
- final boolean isBouncerOverDream = isBouncerShowingOverDream();
- return (isScrimmedBouncer || isBouncerOverDream);
+ return mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
}
private void showBouncerOrLockScreenIfKeyguard() {
@@ -2815,11 +2806,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_OFF;
}
- @Override
- public void showScreenPinningRequest(int taskId, boolean allowCancel) {
- mScreenPinningRequest.showPrompt(taskId, allowCancel);
- }
-
@Nullable
@Override
public Intent getEmergencyActionIntent() {
@@ -3160,11 +3146,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return isBouncerShowing() && mStatusBarKeyguardViewManager.primaryBouncerNeedsScrimming();
}
- @Override
- public boolean isBouncerShowingOverDream() {
- return mBouncerShowingOverDream;
- }
-
// End Extra BaseStatusBarMethods.
boolean isTransientShown() {
@@ -3251,8 +3232,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
if (DEBUG) {
Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
}
-
- mScreenPinningRequest.onConfigurationChanged();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 47ab316bb239..5de0d15ee7d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -62,6 +62,7 @@ constructor(
private var keyguardIndicationArea: View? = null
private var binding: KeyguardBottomAreaViewBinder.Binding? = null
private var lockIconViewController: LockIconViewController? = null
+ private var isLockscreenLandscapeEnabled: Boolean = false
/** Initializes the view. */
@Deprecated("Deprecated as part of b/278057014")
@@ -115,15 +116,26 @@ constructor(
}
}
+ fun setIsLockscreenLandscapeEnabled(isLockscreenLandscapeEnabled: Boolean) {
+ this.isLockscreenLandscapeEnabled = isLockscreenLandscapeEnabled
+ }
+
override fun onFinishInflate() {
super.onFinishInflate()
ambientIndicationArea = findViewById(R.id.ambient_indication_container)
+ keyguardIndicationArea = findViewById(R.id.keyguard_indication_area)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
binding?.onConfigurationChanged()
+ if (isLockscreenLandscapeEnabled) {
+ updateIndicationAreaBottomMargin()
+ }
+ }
+
+ private fun updateIndicationAreaBottomMargin() {
keyguardIndicationArea?.let {
val params = it.layoutParams as FrameLayout.LayoutParams
params.bottomMargin =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
index 3942dae7e0c3..0bad47ecb2ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
@@ -16,11 +16,20 @@
package com.android.systemui.statusbar.phone
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
import com.android.systemui.util.ViewController
import javax.inject.Inject
-class KeyguardBottomAreaViewController @Inject constructor(view: KeyguardBottomAreaView) :
+class KeyguardBottomAreaViewController
+ @Inject constructor(view: KeyguardBottomAreaView, featureFlags: FeatureFlagsClassic) :
ViewController<KeyguardBottomAreaView> (view) {
+
+ init {
+ view.setIsLockscreenLandscapeEnabled(
+ featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE))
+ }
+
override fun onViewAttached() {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index be336e59f534..16413d2ef33a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -45,6 +45,8 @@ import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeViewStateProvider;
@@ -69,6 +71,8 @@ import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -76,8 +80,6 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
-import kotlin.Unit;
-
/** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
private static final String TAG = "KeyguardStatusBarViewController";
@@ -111,6 +113,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private final BiometricUnlockController mBiometricUnlockController;
private final SysuiStatusBarStateController mStatusBarStateController;
private final StatusBarContentInsetsProvider mInsetsProvider;
+ private final FeatureFlags mFeatureFlags;
private final UserManager mUserManager;
private final StatusBarUserChipViewModel mStatusBarUserChipViewModel;
private final SecureSettings mSecureSettings;
@@ -283,6 +286,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
BiometricUnlockController biometricUnlockController,
SysuiStatusBarStateController statusBarStateController,
StatusBarContentInsetsProvider statusBarContentInsetsProvider,
+ FeatureFlags featureFlags,
UserManager userManager,
StatusBarUserChipViewModel userChipViewModel,
SecureSettings secureSettings,
@@ -308,6 +312,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mBiometricUnlockController = biometricUnlockController;
mStatusBarStateController = statusBarStateController;
mInsetsProvider = statusBarContentInsetsProvider;
+ mFeatureFlags = featureFlags;
mUserManager = userManager;
mStatusBarUserChipViewModel = userChipViewModel;
mSecureSettings = secureSettings;
@@ -367,7 +372,19 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mView.findViewById(R.id.statusIcons), StatusBarLocation.KEYGUARD);
mTintedIconManager.setBlockList(getBlockedIcons());
mStatusBarIconController.addIconGroup(mTintedIconManager);
+ } else {
+ // In the old implementation, the keyguard status bar view is never detached and
+ // re-attached, so only calling #addIconGroup when the IconManager is first created was
+ // safe and correct.
+ // In the new scene framework implementation, the keyguard status bar view *is* detached
+ // whenever the shade is opened on top of lockscreen, and then re-attached when the
+ // shade is closed. So, we need to re-add the IconManager each time we're re-attached to
+ // get icon updates.
+ if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW)) {
+ mStatusBarIconController.addIconGroup(mTintedIconManager);
+ }
}
+
mSystemIconsContainer = mView.findViewById(R.id.system_icons);
StatusOverlayHoverListener hoverListener = mStatusOverlayHoverListenerFactory
.createDarkAwareListener(mSystemIconsContainer, mView.darkChangeFlow());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index e337215089ce..8d86d729d958 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -22,7 +22,6 @@ import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
-import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -472,17 +471,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Show the keyguard views whenever we've told WM that the lockscreen is visible.
mShadeViewController.postToView(() ->
collectFlow(
getViewRootImpl().getView(),
- combineFlows(
- mKeyguardTransitionInteractor.getFinishedKeyguardState(),
- mWmLockscreenVisibilityInteractor.get()
- .getUsingKeyguardGoingAwayAnimation(),
- (finishedState, animating) ->
- KeyguardInteractor.Companion.isKeyguardVisibleInState(
- finishedState)
- || animating),
+ mWmLockscreenVisibilityInteractor.get().getLockscreenVisibility(),
this::consumeShowStatusBarKeyguardView));
}
}
@@ -635,8 +628,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
if (needsFullscreenBouncer() && !mDozing) {
// The keyguard might be showing (already). So we need to hide it.
- mCentralSurfaces.hideKeyguard();
- mPrimaryBouncerInteractor.show(true);
+ if (!primaryBouncerIsShowing()) {
+ mCentralSurfaces.hideKeyguard();
+ mPrimaryBouncerInteractor.show(true);
+ } else {
+ Log.e(TAG, "Attempted to show the sim bouncer when it is already showing.");
+ }
} else {
mCentralSurfaces.showKeyguard();
if (hideBouncerWhenShowing) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index dabdcc5fc0f7..39cdfa382bff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -169,6 +169,7 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable {
@Override
public void addCallback(@NonNull Callback callback) {
synchronized (mCallbacksLock) {
+ Log.d(TAG, "Added callback " + callback.getClass());
mCallbacks.add(callback);
}
}
@@ -176,6 +177,7 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable {
@Override
public void removeCallback(@NonNull Callback callback) {
synchronized (mCallbacksLock) {
+ Log.d(TAG, "Removed callback " + callback.getClass());
mCallbacks.remove(callback);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index e1b608ffb1d6..ee67348c9b93 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.whenever
import org.junit.Before
@@ -67,6 +68,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var mKeyguardMessageAreaController:
KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+ @Mock private lateinit var postureController: DevicePostureController
private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
@@ -89,6 +91,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
`when`(keyguardPasswordView.resources).thenReturn(context.resources)
val fakeFeatureFlags = FakeFeatureFlags()
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
+ fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
keyguardPasswordViewController =
KeyguardPasswordViewController(
keyguardPasswordView,
@@ -104,6 +107,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
mContext.resources,
falsingCollector,
keyguardViewController,
+ postureController,
fakeFeatureFlags
)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 93048a5787b2..0ef9f4533015 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -94,9 +94,10 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
.thenReturn(mKeyguardMessageAreaController)
fakeFeatureFlags = FakeFeatureFlags()
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false)
+ fakeFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
mKeyguardPatternView =
View.inflate(mContext, R.layout.keyguard_pattern_view, null) as KeyguardPatternView
-
+ mKeyguardPatternView.setIsLockScreenLandscapeEnabled(false)
mKeyguardPatternViewController =
KeyguardPatternViewController(
mKeyguardPatternView,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 33d40976a2e4..a9f044ccd144 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -123,7 +123,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
private fun constructPinViewController(
mKeyguardPinView: KeyguardPINView
): KeyguardPinViewController {
- mKeyguardPinView.setIsLockScreenLandscapeEnabled(false)
return KeyguardPinViewController(
mKeyguardPinView,
keyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 7c1861e42d6d..decc457dc452 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -60,6 +60,7 @@ import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.UserSwitcherController
@@ -140,6 +141,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
@Mock private lateinit var userInteractor: UserInteractor
@Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
@Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var postureController: DevicePostureController
@Captor
private lateinit var swipeListenerArgumentCaptor:
@@ -197,6 +199,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+ featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
keyguardPasswordViewController =
KeyguardPasswordViewController(
@@ -213,6 +216,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
mock(),
null,
keyguardViewController,
+ postureController,
featureFlags
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index 3a0883b3a575..511562f2aec0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -21,6 +21,7 @@ import android.os.Looper
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.Display
+import android.view.Display.TYPE_EXTERNAL
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.FlowValue
@@ -62,6 +63,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
@Before
fun setup() {
setDisplays(emptyList())
+ setAllDisplaysIncludingDisabled()
displayRepository =
DisplayRepositoryImpl(
displayManager,
@@ -70,6 +72,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
UnconfinedTestDispatcher()
)
verify(displayManager, never()).registerDisplayListener(any(), any())
+ verify(displayManager, never()).getDisplays(any())
}
@Test
@@ -351,6 +354,22 @@ class DisplayRepositoryTest : SysuiTestCase() {
}
@Test
+ fun initialState_onePendingDisplayOnBoot_notNull() =
+ testScope.runTest {
+ // 1 is not enabled, but just connected. It should be seen as pending
+ setAllDisplaysIncludingDisabled(0, 1)
+ setDisplays(0) // 0 is enabled.
+ verify(displayManager, never()).getDisplays(any())
+
+ val pendingDisplay by collectLastValue(displayRepository.pendingDisplay)
+
+ verify(displayManager).getDisplays(any())
+
+ assertThat(pendingDisplay).isNotNull()
+ assertThat(pendingDisplay!!.id).isEqualTo(1)
+ }
+
+ @Test
fun onPendingDisplay_internalDisplay_ignored() =
testScope.runTest {
val pendingDisplay by lastPendingDisplay()
@@ -365,7 +384,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
testScope.runTest {
val pendingDisplay by lastPendingDisplay()
- sendOnDisplayConnected(1, Display.TYPE_EXTERNAL)
+ sendOnDisplayConnected(1, TYPE_EXTERNAL)
sendOnDisplayConnected(2, Display.TYPE_INTERNAL)
assertThat(pendingDisplay!!.id).isEqualTo(1)
@@ -416,7 +435,7 @@ class DisplayRepositoryTest : SysuiTestCase() {
whenever(displayManager.getDisplay(eq(id))).thenReturn(null)
}
- private fun sendOnDisplayConnected(id: Int, displayType: Int = Display.TYPE_EXTERNAL) {
+ private fun sendOnDisplayConnected(id: Int, displayType: Int = TYPE_EXTERNAL) {
val mockDisplay = display(id = id, type = displayType)
whenever(displayManager.getDisplay(eq(id))).thenReturn(mockDisplay)
connectedDisplayListener.value.onDisplayConnected(id)
@@ -424,15 +443,25 @@ class DisplayRepositoryTest : SysuiTestCase() {
private fun setDisplays(displays: List<Display>) {
whenever(displayManager.displays).thenReturn(displays.toTypedArray())
+ displays.forEach { display ->
+ whenever(displayManager.getDisplay(eq(display.displayId))).thenReturn(display)
+ }
}
- private fun setDisplays(vararg ids: Int) {
- setDisplays(ids.map { display(it) })
+ private fun setAllDisplaysIncludingDisabled(vararg ids: Int) {
+ val displays = ids.map { display(type = TYPE_EXTERNAL, id = it) }.toTypedArray()
+ whenever(
+ displayManager.getDisplays(
+ eq(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
+ )
+ )
+ .thenReturn(displays)
+ displays.forEach { display ->
+ whenever(displayManager.getDisplay(eq(display.displayId))).thenReturn(display)
+ }
}
- private fun display(id: Int): Display {
- return mock<Display>().also { mockDisplay ->
- whenever(mockDisplay.displayId).thenReturn(id)
- }
+ private fun setDisplays(vararg ids: Int) {
+ setDisplays(ids.map { display(type = TYPE_EXTERNAL, id = it) })
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index f62137c62c65..f3c9432cb4bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -172,7 +172,6 @@ class CustomizationProviderTest : SysuiTestCase() {
val featureFlags =
FakeFeatureFlags().apply {
set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
- set(Flags.REVAMPED_WALLPAPER_UI, true)
set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true)
set(Flags.FACE_AUTH_REFACTOR, true)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index f78d051eca61..a45b0bd4cca8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -186,8 +186,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock AuthController mAuthController;
private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
private @Mock ShadeWindowLogger mShadeWindowLogger;
- private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
- mKeyguardUpdateMonitorCallbackCaptor;
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
mKeyguardStateControllerCallback;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
@@ -294,25 +292,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
}
@Test
- @TestableLooper.RunWithLooper(setAsMainLooper = true)
- public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() {
- // GIVEN keyguard is not enabled and isn't showing
- mViewMediator.onSystemReady();
- mViewMediator.setKeyguardEnabled(false);
- TestableLooper.get(this).processAllMessages();
- captureKeyguardUpdateMonitorCallback();
- assertFalse(mViewMediator.isShowingAndNotOccluded());
-
- // WHEN lockdown occurs
- when(mLockPatternUtils.isUserInLockdown(anyInt())).thenReturn(true);
- mKeyguardUpdateMonitorCallbackCaptor.getValue().onStrongAuthStateChanged(0);
-
- // THEN keyguard is shown
- TestableLooper.get(this).processAllMessages();
- assertTrue(mViewMediator.isShowingAndNotOccluded());
- }
-
- @Test
public void testOnGoingToSleep_UpdatesKeyguardGoingAway() {
mViewMediator.onStartedGoingToSleep(OFF_BECAUSE_OF_USER);
verify(mUpdateMonitor).dispatchKeyguardGoingAway(false);
@@ -1120,10 +1099,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
}
- private void captureKeyguardUpdateMonitorCallback() {
- verify(mUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallbackCaptor.capture());
- }
-
private void captureKeyguardStateControllerCallback() {
verify(mKeyguardStateController).addCallback(mKeyguardStateControllerCallback.capture());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index a76c88579dca..6b194f243b2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -36,6 +36,7 @@ import com.android.internal.logging.InstanceId.fakeInstanceId
import com.android.internal.logging.UiEventLogger
import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED
import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
@@ -285,7 +286,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
allPreconditionsToRunFaceAuthAreTrue()
FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER.extraInfo = 10
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
faceAuthenticateIsCalled()
uiEventIsLogged(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
@@ -318,12 +319,12 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
faceAuthenticateIsCalled()
clearInvocations(faceManager)
clearInvocations(uiEventLogger)
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
verifyNoMoreInteractions(faceManager)
verifyNoMoreInteractions(uiEventLogger)
}
@@ -335,7 +336,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
verify(faceManager).addLockoutResetCallback(faceLockoutResetCallback.capture())
allPreconditionsToRunFaceAuthAreTrue()
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
faceAuthenticateIsCalled()
authenticationCallback.value.onAuthenticationError(
@@ -389,7 +390,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
faceAuthenticateIsCalled()
var wasAuthCancelled = false
@@ -443,7 +444,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
faceAuthenticateIsCalled()
// Enter cancelling state
@@ -451,7 +452,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
clearInvocations(faceManager)
// Auth is while cancelling.
- underTest.authenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN)
// Auth is not started
verifyNoMoreInteractions(faceManager)
@@ -474,14 +475,14 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
faceAuthenticateIsCalled()
clearInvocations(faceManager)
underTest.cancel()
advanceTimeBy(DeviceEntryFaceAuthRepositoryImpl.DEFAULT_CANCEL_SIGNAL_TIMEOUT + 1)
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
faceAuthenticateIsCalled()
}
@@ -492,7 +493,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
allPreconditionsToRunFaceAuthAreTrue()
val emittedValues by collectValues(underTest.authenticationStatus)
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
underTest.cancel()
advanceTimeBy(100)
underTest.cancel()
@@ -519,7 +520,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
faceAuthenticateIsCalled()
authenticationCallback.value.onAuthenticationHelp(9, "help msg")
@@ -562,8 +563,26 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
- fun authenticateDoesNotRunIfFaceAuthIsCurrentlyPaused() =
- testScope.runTest { testGatingCheckForFaceAuth { underTest.pauseFaceAuth() } }
+ fun authenticateDoesNotRunIfUserSwitchingIsCurrentlyInProgress() =
+ testScope.runTest {
+ testGatingCheckForFaceAuth {
+ fakeUserRepository.setSelectedUserInfo(
+ primaryUser,
+ SelectionStatus.SELECTION_IN_PROGRESS
+ )
+ }
+ }
+
+ @Test
+ fun detectDoesNotRunIfUserSwitchingIsCurrentlyInProgress() =
+ testScope.runTest {
+ testGatingCheckForDetect {
+ fakeUserRepository.setSelectedUserInfo(
+ userInfo = primaryUser,
+ selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS
+ )
+ }
+ }
@Test
fun authenticateDoesNotRunIfKeyguardIsNotShowing() =
@@ -582,12 +601,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
testScope.runTest { testGatingCheckForFaceAuth { underTest.setLockedOut(true) } }
@Test
- fun authenticateDoesNotRunWhenUserIsCurrentlyTrusted() =
- testScope.runTest {
- testGatingCheckForFaceAuth { trustRepository.setCurrentUserTrusted(true) }
- }
-
- @Test
fun authenticateDoesNotRunWhenKeyguardIsGoingAway() =
testScope.runTest {
testGatingCheckForFaceAuth { keyguardRepository.setKeyguardGoingAway(true) }
@@ -608,14 +621,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
- fun authenticateDoesNotRunWhenFaceAuthIsNotCurrentlyAllowedToRun() =
- testScope.runTest {
- testGatingCheckForFaceAuth {
- biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
- }
- }
-
- @Test
fun authenticateDoesNotRunWhenSecureCameraIsActive() =
testScope.runTest {
testGatingCheckForFaceAuth {
@@ -672,7 +677,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
// Flip one precondition to false.
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
assertThat(canFaceAuthRun()).isFalse()
- underTest.authenticate(
+ underTest.requestAuthenticate(
FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
fallbackToDetection = true
)
@@ -693,7 +698,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
trustRepository.setCurrentUserTrusted(true)
assertThat(canFaceAuthRun()).isFalse()
- underTest.authenticate(
+ underTest.requestAuthenticate(
FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
fallbackToDetection = true
)
@@ -884,10 +889,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
- fun detectDoesNotRunWhenUserSwitchingInProgress() =
- testScope.runTest { testGatingCheckForDetect { underTest.pauseFaceAuth() } }
-
- @Test
fun detectDoesNotRunWhenKeyguardGoingAway() =
testScope.runTest {
testGatingCheckForDetect { keyguardRepository.setKeyguardGoingAway(true) }
@@ -1075,6 +1076,28 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthenticateIsCalled()
}
+ @Test
+ fun queuedAuthOnlyRequestShouldNotBeProcessedIfOnlyDetectionCanBeRun() =
+ testScope.runTest {
+ initCollectors()
+ allPreconditionsToRunFaceAuthAreTrue()
+
+ // This will prevent auth from running but not detection
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(false)
+
+ runCurrent()
+ assertThat(canFaceAuthRun()).isFalse()
+
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, false)
+ runCurrent()
+
+ faceDetectIsNotCalled()
+ faceAuthenticateIsNotCalled()
+
+ biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
+ faceAuthenticateIsCalled()
+ }
+
private suspend fun TestScope.testGatingCheckForFaceAuth(
gatingCheckModifier: suspend () -> Unit
) {
@@ -1087,10 +1110,18 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
// gating check doesn't allow face auth to run.
assertThat(underTest.canRunFaceAuth.value).isFalse()
+ // request face auth just before gating conditions become true, this ensures any race
+ // conditions won't prevent face auth from running
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)
+ faceAuthenticateIsNotCalled()
+
// flip the gating check back on.
allPreconditionsToRunFaceAuthAreTrue()
+ assertThat(underTest.canRunFaceAuth.value).isTrue()
- triggerFaceAuth(false)
+ faceAuthenticateIsCalled()
+ assertThat(authRunning()).isTrue()
+ cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
// Flip gating check off
gatingCheckModifier()
@@ -1101,13 +1132,17 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
clearInvocations(faceManager)
// Try auth again
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER)
+
+ runCurrent()
// Auth can't run again
faceAuthenticateIsNotCalled()
}
- private suspend fun TestScope.testGatingCheckForDetect(gatingCheckModifier: () -> Unit) {
+ private suspend fun TestScope.testGatingCheckForDetect(
+ gatingCheckModifier: suspend () -> Unit
+ ) {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
@@ -1118,7 +1153,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
assertThat(canFaceAuthRun()).isFalse()
// Trigger authenticate with detection fallback
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true)
+ underTest.requestAuthenticate(
+ FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
+ fallbackToDetection = true
+ )
+ runCurrent()
faceAuthenticateIsNotCalled()
faceDetectIsCalled()
@@ -1133,15 +1172,21 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
clearInvocations(faceManager)
// Try to run detect again
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetection = true)
+ underTest.requestAuthenticate(
+ FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER,
+ fallbackToDetection = true
+ )
// Detect won't run because preconditions are not true anymore.
faceDetectIsNotCalled()
}
- private suspend fun triggerFaceAuth(fallbackToDetect: Boolean) {
+ private fun TestScope.triggerFaceAuth(fallbackToDetect: Boolean) {
assertThat(canFaceAuthRun()).isTrue()
- underTest.authenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetect)
+ underTest.requestAuthenticate(FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, fallbackToDetect)
+
+ runCurrent()
+
faceAuthenticateIsCalled()
assertThat(authRunning()).isTrue()
cancellationSignal.value.setOnCancelListener { wasAuthCancelled = true }
@@ -1150,7 +1195,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() {
verify(faceManager, atLeastOnce())
.addLockoutResetCallback(faceLockoutResetCallback.capture())
- underTest.resumeFaceAuth()
trustRepository.setCurrentUserTrusted(false)
keyguardRepository.setKeyguardGoingAway(false)
keyguardRepository.setWakefulnessModel(
@@ -1164,7 +1208,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
biometricSettingsRepository.setIsFaceAuthSupportedInCurrentPosture(true)
biometricSettingsRepository.setIsFaceAuthCurrentlyAllowed(true)
biometricSettingsRepository.setIsUserInLockdown(false)
- fakeUserRepository.setSelectedUserInfo(primaryUser)
+ fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE)
faceLockoutResetCallback.value.onLockoutReset(0)
bouncerRepository.setAlternateVisible(true)
keyguardRepository.setKeyguardShowing(true)
@@ -1187,7 +1231,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
private fun successResult() = FaceManager.AuthenticationResult(null, null, primaryUserId, false)
- private fun faceDetectIsCalled() {
+ private fun TestScope.faceDetectIsCalled() {
+ runCurrent()
+
verify(faceManager)
.detectFace(
cancellationSignal.capture(),
@@ -1196,7 +1242,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
)
}
- private fun faceAuthenticateIsCalled() {
+ private fun TestScope.faceAuthenticateIsCalled() {
+ runCurrent()
+
verify(faceManager)
.authenticate(
isNull(),
@@ -1207,7 +1255,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
)
}
- private fun faceAuthenticateIsNotCalled() {
+ private fun TestScope.faceAuthenticateIsNotCalled() {
+ runCurrent()
+
verify(faceManager, never())
.authenticate(
isNull(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index da70a9ff036f..2ed9de26dfa3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -224,23 +224,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
}
@Test
- fun faceAuthIsPausedWhenUserSwitchingIsInProgress() =
- testScope.runTest {
- underTest.start()
-
- fakeUserRepository.setSelectedUserInfo(primaryUser, SelectionStatus.SELECTION_COMPLETE)
- runCurrent()
- fakeUserRepository.setSelectedUserInfo(
- secondaryUser,
- SelectionStatus.SELECTION_IN_PROGRESS
- )
- runCurrent()
-
- assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue()
- }
-
- @Test
- fun faceAuthIsUnpausedWhenUserSwitchingIsInComplete() =
+ fun faceAuthLockedOutStateIsUpdatedAfterUserSwitch() =
testScope.runTest {
underTest.start()
@@ -251,7 +235,6 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
SelectionStatus.SELECTION_IN_PROGRESS
)
runCurrent()
- assertThat(faceAuthRepository.isFaceAuthPaused()).isTrue()
bouncerRepository.setPrimaryShow(true)
// New user is not locked out.
@@ -262,7 +245,6 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
)
runCurrent()
- assertThat(faceAuthRepository.isFaceAuthPaused()).isFalse()
assertThat(faceAuthRepository.isLockedOut.value).isFalse()
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index 0050d64d7505..0bbeeff8eee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -302,7 +302,6 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() {
featureFlags =
FakeFeatureFlags().apply {
set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled)
- set(Flags.REVAMPED_WALLPAPER_UI, isRevampedWppFeatureEnabled)
set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled)
},
broadcastDispatcher = fakeBroadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 681fce822342..d6b621e55dd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSect
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotificationStackScrollLayoutSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.util.mockito.whenever
@@ -60,6 +61,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() {
private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection
@Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection
@Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection
+ @Mock private lateinit var defaultStatusBarViewSection: DefaultStatusBarSection
@Mock private lateinit var defaultNSSLSection: DefaultNotificationStackScrollLayoutSection
@Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines
@Mock private lateinit var aodNotificationIconsSection: AodNotificationIconsSection
@@ -78,6 +80,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() {
defaultAmbientIndicationAreaSection,
defaultSettingsPopupMenuSection,
defaultStatusViewSection,
+ defaultStatusBarViewSection,
defaultNSSLSection,
splitShadeGuidelines,
aodNotificationIconsSection,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
new file mode 100644
index 000000000000..47b4244e0910
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
@@ -0,0 +1,41 @@
+package com.android.systemui.qs.tiles.base
+
+import android.content.Intent
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserActionHandler
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
+
+ @Mock private lateinit var activityStarted: ActivityStarter
+
+ lateinit var underTest: QSTileIntentUserActionHandler
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = QSTileIntentUserActionHandler(activityStarted)
+ }
+
+ @Test
+ fun testPassesIntentToStarter() {
+ val intent = Intent("test.ACTION")
+
+ underTest.handle(QSTileUserAction.Click(context, null), intent)
+
+ verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
new file mode 100644
index 000000000000..643866e3cade
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt
@@ -0,0 +1,90 @@
+package com.android.systemui.qs.tiles.viewmodel
+
+import android.graphics.drawable.Icon
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.MediumTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor
+import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger
+import com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.StandardTestDispatcher
+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
+
+// TODO(b/299909368): Add more tests
+@MediumTest
+@RoboPilotTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() {
+
+ private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>()
+ private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>()
+
+ private val testCoroutineDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testCoroutineDispatcher)
+
+ private lateinit var underTest: QSTileViewModel
+
+ @Before
+ fun setup() {
+ underTest = createViewModel(testScope)
+ }
+
+ @Test
+ fun testDoesntListenStateUntilCreated() =
+ testScope.runTest {
+ assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
+
+ underTest.onLifecycle(QSTileLifecycle.ALIVE)
+ underTest.onUserIdChanged(1)
+
+ assertThat(fakeQSTileDataInteractor.dataRequests).isEmpty()
+
+ underTest.state.launchIn(backgroundScope)
+ runCurrent()
+
+ assertThat(fakeQSTileDataInteractor.dataRequests).isNotEmpty()
+ assertThat(fakeQSTileDataInteractor.dataRequests.first())
+ .isEqualTo(QSTileDataRequest(1, StateUpdateTrigger.InitialRequest))
+ }
+
+ private fun createViewModel(
+ scope: TestScope,
+ config: QSTileConfig = TEST_QS_TILE_CONFIG,
+ ): QSTileViewModel =
+ object :
+ BaseQSTileViewModel<Any>(
+ config,
+ fakeQSTileUserActionInteractor,
+ fakeQSTileDataInteractor,
+ object : QSTileDataToStateMapper<Any> {
+ override fun map(config: QSTileConfig, data: Any): QSTileState {
+ return QSTileState(config.tileIcon, config.tileLabel)
+ }
+ },
+ testCoroutineDispatcher,
+ tileScope = scope.backgroundScope,
+ ) {}
+
+ private companion object {
+ val TEST_QS_TILE_CONFIG =
+ QSTileConfig(
+ TileSpec.create("default"),
+ Icon.createWithContentUri(""),
+ "",
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index e353a53af752..18fa0bec000c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -96,6 +96,7 @@ class OverviewProxyServiceTest : SysuiTestCase() {
@Mock private lateinit var navBarController: NavigationBarController
@Mock private lateinit var centralSurfaces: CentralSurfaces
@Mock private lateinit var shadeViewController: ShadeViewController
+ @Mock private lateinit var screenPinningRequest: ScreenPinningRequest
@Mock private lateinit var navModeController: NavigationModeController
@Mock private lateinit var statusBarWinController: NotificationShadeWindowController
@Mock private lateinit var userTracker: UserTracker
@@ -136,6 +137,7 @@ class OverviewProxyServiceTest : SysuiTestCase() {
{ navBarController },
{ Optional.of(centralSurfaces) },
{ shadeViewController },
+ screenPinningRequest,
navModeController,
statusBarWinController,
sysUiState,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index cfdf66ef2488..a105c15b630a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -180,7 +180,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
}
@Test
- fun executeScreenshots_doesNotReportFinishedIfOneFinishesOtherFails() =
+ fun executeScreenshots_oneFinishesOtherFails_reportFailsOnlyAtTheEnd() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
val onSaved = { _: Uri -> }
@@ -207,7 +207,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
}
@Test
- fun executeScreenshots_doesNotReportFinishedAfterOneFails() =
+ fun executeScreenshots_allDisplaysFail_reportsFail() =
testScope.runTest {
setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
val onSaved = { _: Uri -> }
@@ -224,11 +224,12 @@ class TakeScreenshotExecutorTest : SysuiTestCase() {
capturer0.value.reportError()
verify(callback, never()).onFinish()
- verify(callback).reportError()
+ verify(callback, never()).reportError()
- capturer1.value.onFinish()
+ capturer1.value.reportError()
verify(callback, never()).onFinish()
+ verify(callback).reportError()
screenshotExecutor.onDestroy()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 638c2665cb20..6dadd4c3dca9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -197,6 +197,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
@Test
public void getVerticalSpaceForLockscreenShelf_useLockIconBottomPadding_returnsShelfHeight() {
+ enableSplitShade(/* enabled= */ false);
setBottomPadding(/* stackScrollLayoutBottom= */ 100,
/* lockIconPadding= */ 20,
/* indicationPadding= */ 0,
@@ -209,6 +210,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
@Test
public void getVerticalSpaceForLockscreenShelf_useIndicationBottomPadding_returnsZero() {
+ enableSplitShade(/* enabled= */ false);
setBottomPadding(/* stackScrollLayoutBottom= */ 100,
/* lockIconPadding= */ 0,
/* indicationPadding= */ 30,
@@ -221,6 +223,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
@Test
public void getVerticalSpaceForLockscreenShelf_useAmbientBottomPadding_returnsZero() {
+ enableSplitShade(/* enabled= */ false);
setBottomPadding(/* stackScrollLayoutBottom= */ 100,
/* lockIconPadding= */ 0,
/* indicationPadding= */ 0,
@@ -233,6 +236,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
@Test
public void getVerticalSpaceForLockscreenShelf_useLockIconPadding_returnsLessThanShelfHeight() {
+ enableSplitShade(/* enabled= */ false);
setBottomPadding(/* stackScrollLayoutBottom= */ 100,
/* lockIconPadding= */ 10,
/* indicationPadding= */ 8,
@@ -244,6 +248,19 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
}
@Test
+ public void getVerticalSpaceForLockscreenShelf_splitShade() {
+ enableSplitShade(/* enabled= */ true);
+ setBottomPadding(/* stackScrollLayoutBottom= */ 100,
+ /* lockIconPadding= */ 10,
+ /* indicationPadding= */ 8,
+ /* ambientPadding= */ 0);
+
+ when(mNotificationShelfController.getIntrinsicHeight()).thenReturn(5);
+ assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf())
+ .isEqualTo(0);
+ }
+
+ @Test
public void testSetPanelScrimMinFractionWhenHeadsUpIsDragged() {
mNotificationPanelViewController.setHeadsUpDraggingStartingHeight(
mNotificationPanelViewController.getMaxPanelHeight() / 2);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index 9275ccb6d473..d4b69fad6097 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -402,6 +402,7 @@ class ShadeInteractorTest : SysuiTestCase() {
assertThat(actual).isEqualTo(0.8f)
}
+ @Test
fun shadeExpansionWhenInSplitShadeAndQsExpanded() =
testScope.runTest {
val actual by collectLastValue(underTest.shadeExpansion)
@@ -410,27 +411,31 @@ class ShadeInteractorTest : SysuiTestCase() {
keyguardRepository.setStatusBarState(StatusBarState.SHADE)
overrideResource(R.bool.config_use_split_notification_shade, true)
configurationRepository.onAnyConfigurationChange()
- runCurrent()
shadeRepository.setQsExpansion(.5f)
shadeRepository.setLegacyShadeExpansion(.7f)
+ runCurrent()
// THEN legacy shade expansion is passed through
assertThat(actual).isEqualTo(.7f)
}
+ @Test
fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() =
testScope.runTest {
val actual by collectLastValue(underTest.shadeExpansion)
// WHEN split shade is not enabled and QS is expanded
keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ overrideResource(R.bool.config_use_split_notification_shade, false)
shadeRepository.setQsExpansion(.5f)
shadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
// THEN shade expansion is zero
assertThat(actual).isEqualTo(0f)
}
+ @Test
fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() =
testScope.runTest {
val actual by collectLastValue(underTest.shadeExpansion)
@@ -471,7 +476,7 @@ class ShadeInteractorTest : SysuiTestCase() {
@Test
fun expanding_shadeDraggedDown_expandingTrue() =
testScope.runTest() {
- val actual by collectLastValue(underTest.anyExpanding)
+ val actual by collectLastValue(underTest.isAnyExpanding)
// GIVEN shade and QS collapsed
shadeRepository.setLegacyShadeExpansion(0f)
@@ -489,7 +494,7 @@ class ShadeInteractorTest : SysuiTestCase() {
@Test
fun expanding_qsDraggedDown_expandingTrue() =
testScope.runTest() {
- val actual by collectLastValue(underTest.anyExpanding)
+ val actual by collectLastValue(underTest.isAnyExpanding)
// GIVEN shade and QS collapsed
shadeRepository.setLegacyShadeExpansion(0f)
@@ -507,7 +512,7 @@ class ShadeInteractorTest : SysuiTestCase() {
@Test
fun expanding_shadeDraggedUpAndDown() =
testScope.runTest() {
- val actual by collectLastValue(underTest.anyExpanding)
+ val actual by collectLastValue(underTest.isAnyExpanding)
// WHEN shade starts collapsed then partially expanded
shadeRepository.setLegacyShadeExpansion(0f)
@@ -532,7 +537,7 @@ class ShadeInteractorTest : SysuiTestCase() {
// THEN anyExpanding is still true
assertThat(actual).isTrue()
- // WHEN shade fully shadeExpanded
+ // WHEN shade fully expanded
shadeRepository.setLegacyShadeExpansion(1f)
runCurrent()
@@ -550,7 +555,7 @@ class ShadeInteractorTest : SysuiTestCase() {
@Test
fun expanding_shadeDraggedDownThenUp_expandingFalse() =
testScope.runTest() {
- val actual by collectLastValue(underTest.anyExpanding)
+ val actual by collectLastValue(underTest.isAnyExpanding)
// GIVEN shade starts collapsed
shadeRepository.setLegacyShadeExpansion(0f)
@@ -708,4 +713,227 @@ class ShadeInteractorTest : SysuiTestCase() {
// THEN expansion is still 0
assertThat(expansionAmount).isEqualTo(0f)
}
+
+ @Test
+ fun userInteractingWithShade_shadeDraggedUpAndDown() =
+ testScope.runTest() {
+ val actual by collectLastValue(underTest.isUserInteractingWithShade)
+ // GIVEN shade collapsed and not tracking input
+ shadeRepository.setLegacyShadeExpansion(0f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+
+ // WHEN shade tracking starts
+ shadeRepository.setLegacyShadeTracking(true)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade dragged down halfway
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade fully expanded but tracking is not stopped
+ shadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade fully collapsed but tracking is not stopped
+ shadeRepository.setLegacyShadeExpansion(0f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade dragged halfway and tracking is stopped
+ shadeRepository.setLegacyShadeExpansion(.6f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade completes expansion stopped
+ shadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun userInteractingWithShade_shadeExpanded() =
+ testScope.runTest() {
+ val actual by collectLastValue(underTest.isUserInteractingWithShade)
+ // GIVEN shade collapsed and not tracking input
+ shadeRepository.setLegacyShadeExpansion(0f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+
+ // WHEN shade tracking starts
+ shadeRepository.setLegacyShadeTracking(true)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade dragged down halfway
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade fully expanded and tracking is stopped
+ shadeRepository.setLegacyShadeExpansion(1f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun userInteractingWithShade_shadePartiallyExpanded() =
+ testScope.runTest() {
+ val actual by collectLastValue(underTest.isUserInteractingWithShade)
+ // GIVEN shade collapsed and not tracking input
+ shadeRepository.setLegacyShadeExpansion(0f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+
+ // WHEN shade tracking starts
+ shadeRepository.setLegacyShadeTracking(true)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade partially expanded
+ shadeRepository.setLegacyShadeExpansion(.4f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN tracking is stopped
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade goes back to collapsed
+ shadeRepository.setLegacyShadeExpansion(0f)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun userInteractingWithShade_shadeCollapsed() =
+ testScope.runTest() {
+ val actual by collectLastValue(underTest.isUserInteractingWithShade)
+ // GIVEN shade expanded and not tracking input
+ shadeRepository.setLegacyShadeExpansion(1f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+
+ // WHEN shade tracking starts
+ shadeRepository.setLegacyShadeTracking(true)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade dragged up halfway
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN shade fully collapsed and tracking is stopped
+ shadeRepository.setLegacyShadeExpansion(0f)
+ shadeRepository.setLegacyShadeTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ fun userInteractingWithQs_qsDraggedUpAndDown() =
+ testScope.runTest() {
+ val actual by collectLastValue(underTest.isUserInteractingWithQs)
+ // GIVEN qs collapsed and not tracking input
+ shadeRepository.setQsExpansion(0f)
+ shadeRepository.setLegacyQsTracking(false)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+
+ // WHEN qs tracking starts
+ shadeRepository.setLegacyQsTracking(true)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN qs dragged down halfway
+ shadeRepository.setQsExpansion(.5f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN qs fully expanded but tracking is not stopped
+ shadeRepository.setQsExpansion(1f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN qs fully collapsed but tracking is not stopped
+ shadeRepository.setQsExpansion(0f)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN qs dragged halfway and tracking is stopped
+ shadeRepository.setQsExpansion(.6f)
+ shadeRepository.setLegacyQsTracking(false)
+ runCurrent()
+
+ // THEN user is interacting
+ assertThat(actual).isTrue()
+
+ // WHEN qs completes expansion stopped
+ shadeRepository.setQsExpansion(1f)
+ runCurrent()
+
+ // THEN user is not interacting
+ assertThat(actual).isFalse()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
new file mode 100644
index 000000000000..c650e01263bc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.graphics.BitmapFactory
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.net.Uri
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.NotificationDrawableConsumer
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+
+private const val FREE_IMAGE_DELAY_MS = 4000L
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class BigPictureIconManagerTest : SysuiTestCase() {
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val testableResources = context.orCreateTestableResources
+ private val imageLoader: ImageLoader = ImageLoader(context, testDispatcher)
+ private var mockConsumer: NotificationDrawableConsumer = mock()
+ private val drawableCaptor = argumentCaptor<Drawable>()
+
+ private lateinit var iconManager: BigPictureIconManager
+
+ private val expectedDrawable by lazy {
+ context.resources.getDrawable(R.drawable.dessert_zombiegingerbread, context.theme)
+ }
+ private val supportedIcon by lazy {
+ Icon.createWithContentUri(
+ Uri.parse(
+ "android.resource://${context.packageName}/${R.drawable.dessert_zombiegingerbread}"
+ )
+ )
+ }
+ private val unsupportedIcon by lazy {
+ Icon.createWithBitmap(
+ BitmapFactory.decodeResource(context.resources, R.drawable.dessert_zombiegingerbread)
+ )
+ }
+ private val invalidIcon by lazy { Icon.createWithContentUri(Uri.parse("this.is/broken")) }
+
+ @Before
+ fun setUp() {
+ iconManager =
+ BigPictureIconManager(
+ context,
+ imageLoader,
+ scope = testScope,
+ mainDispatcher = testDispatcher,
+ bgDispatcher = testDispatcher
+ )
+ }
+
+ @Test
+ fun onIconUpdated_supportedType_placeholderLoaded() =
+ testScope.runTest {
+ // WHEN update with a supported icon
+ iconManager.updateIcon(mockConsumer, supportedIcon).run()
+
+ // THEN consumer is updated with a placeholder
+ verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+ assertIsPlaceHolder(drawableCaptor.value)
+ assertSize(drawableCaptor.value)
+ }
+
+ @Test
+ fun onIconUpdated_notSupportedType_fullImageLoaded() =
+ testScope.runTest {
+ // WHEN update with an unsupported icon
+ iconManager.updateIcon(mockConsumer, unsupportedIcon).run()
+
+ // THEN consumer is updated with the full image
+ verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+ assertIsFullImage(drawableCaptor.value)
+ assertSize(drawableCaptor.value)
+ }
+
+ @Test
+ fun onIconUpdated_invalidIcon_drawableIsNull() =
+ testScope.runTest {
+ // WHEN update with an invalid icon
+ iconManager.updateIcon(mockConsumer, invalidIcon).run()
+
+ // THEN consumer is updated with null
+ verify(mockConsumer).setImageDrawable(null)
+ }
+
+ @Test
+ fun onIconUpdated_consumerAlreadySet_nothingHappens() =
+ testScope.runTest {
+ // GIVEN a consumer is set
+ val otherConsumer: NotificationDrawableConsumer = mock()
+ iconManager.updateIcon(mockConsumer, supportedIcon).run()
+ reset(mockConsumer)
+
+ // WHEN a new consumer is set
+ iconManager.updateIcon(otherConsumer, unsupportedIcon).run()
+
+ // THEN nothing happens
+ verifyZeroInteractions(mockConsumer, otherConsumer)
+ }
+
+ @Test
+ fun onIconUpdated_iconAlreadySet_loadsNewIcon() =
+ testScope.runTest {
+ // GIVEN an icon is set
+ iconManager.updateIcon(mockConsumer, supportedIcon).run()
+ reset(mockConsumer)
+
+ // WHEN a new icon is set
+ iconManager.updateIcon(mockConsumer, unsupportedIcon).run()
+
+ // THEN consumer is updated with the new image
+ verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+ assertIsFullImage(drawableCaptor.value)
+ assertSize(drawableCaptor.value)
+ }
+
+ @Test
+ fun onIconUpdated_supportedTypeButTooWide_resizedPlaceholderLoaded() =
+ testScope.runTest {
+ // GIVEN the max width is smaller than our image
+ testableResources.addOverride(
+ com.android.internal.R.dimen.notification_big_picture_max_width,
+ 20
+ )
+ iconManager.updateMaxImageSizes()
+
+ // WHEN update with a supported icon
+ iconManager.updateIcon(mockConsumer, supportedIcon).run()
+
+ // THEN consumer is updated with the resized placeholder
+ verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+ assertIsPlaceHolder(drawableCaptor.value)
+ assertSize(drawableCaptor.value, expectedWidth = 20, expectedHeight = 20)
+ }
+
+ @Test
+ fun onIconUpdated_supportedTypeButTooHigh_resizedPlaceholderLoaded() =
+ testScope.runTest {
+ // GIVEN the max height is smaller than our image
+ testableResources.addOverride(
+ com.android.internal.R.dimen.notification_big_picture_max_height,
+ 20
+ )
+ iconManager.updateMaxImageSizes()
+
+ // WHEN update with a supported icon
+ iconManager.updateIcon(mockConsumer, supportedIcon).run()
+
+ // THEN consumer is updated with the resized placeholder
+ verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+ assertIsPlaceHolder(drawableCaptor.value)
+ assertSize(drawableCaptor.value, expectedWidth = 20, expectedHeight = 20)
+ }
+
+ @Test
+ fun onViewShown_placeholderShowing_fullImageLoaded() =
+ testScope.runTest {
+ // GIVEN placeholder is showing
+ iconManager.updateIcon(mockConsumer, supportedIcon).run()
+ reset(mockConsumer)
+
+ // WHEN the view is shown
+ iconManager.onViewShown(true)
+ runCurrent()
+
+ // THEN full image is set
+ verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+ assertIsFullImage(drawableCaptor.value)
+ assertSize(drawableCaptor.value)
+ }
+
+ @Test
+ fun onViewHidden_fullImageShowing_placeHolderSet() =
+ testScope.runTest {
+ // GIVEN full image is showing and the view is shown
+ iconManager.updateIcon(mockConsumer, supportedIcon).run()
+ iconManager.onViewShown(true)
+ runCurrent()
+ reset(mockConsumer)
+
+ // WHEN the view goes off the screen
+ iconManager.onViewShown(false)
+ // AND we wait a bit
+ advanceTimeBy(FREE_IMAGE_DELAY_MS)
+ runCurrent()
+
+ // THEN placeholder is set
+ verify(mockConsumer).setImageDrawable(drawableCaptor.capture())
+ assertIsPlaceHolder(drawableCaptor.value)
+ assertSize(drawableCaptor.value)
+ }
+
+ @Test
+ fun onViewShownToggled_viewShown_nothingHappens() =
+ testScope.runTest {
+ // GIVEN full image is showing and the view is shown
+ iconManager.updateIcon(mockConsumer, supportedIcon).run()
+ iconManager.onViewShown(true)
+ runCurrent()
+ reset(mockConsumer)
+
+ // WHEN the onViewShown is toggled
+ iconManager.onViewShown(false)
+ runCurrent()
+ iconManager.onViewShown(true)
+ // AND we wait a bit
+ advanceTimeBy(FREE_IMAGE_DELAY_MS)
+ runCurrent()
+
+ // THEN nothing happens
+ verifyZeroInteractions(mockConsumer)
+ }
+
+ // nice to have tests
+
+ @Test
+ fun onViewShown_fullImageLoaded_nothingHappens() =
+ testScope.runTest {
+ // GIVEN full image is showing
+ iconManager.updateIcon(mockConsumer, unsupportedIcon).run()
+ reset(mockConsumer)
+
+ // WHEN the view is shown
+ iconManager.onViewShown(true)
+ runCurrent()
+
+ // THEN nothing happens
+ verifyZeroInteractions(mockConsumer)
+ }
+
+ @Test
+ fun onViewHidden_placeholderShowing_nothingHappens() =
+ testScope.runTest {
+ // GIVEN placeholder image is showing
+ iconManager.updateIcon(mockConsumer, unsupportedIcon).run()
+ reset(mockConsumer)
+
+ // WHEN the view is hidden
+ iconManager.onViewShown(false)
+ // AND we wait a bit
+ advanceTimeBy(FREE_IMAGE_DELAY_MS)
+ runCurrent()
+
+ // THEN nothing happens
+ verifyZeroInteractions(mockConsumer)
+ }
+
+ @Test
+ fun onViewShown_alreadyShowing_nothingHappens() =
+ testScope.runTest {
+ // GIVEN full image is showing and the view is shown
+ iconManager.updateIcon(mockConsumer, supportedIcon).run()
+ iconManager.onViewShown(true)
+ runCurrent()
+ reset(mockConsumer)
+
+ // WHEN view shown called again
+ iconManager.onViewShown(true)
+ runCurrent()
+
+ verifyZeroInteractions(mockConsumer)
+ }
+
+ @Test
+ fun onViewHidden_alreadyHidden_nothingHappens() =
+ testScope.runTest {
+ // GIVEN placeholder image is showing and the view is hidden
+ iconManager.updateIcon(mockConsumer, unsupportedIcon).run()
+ iconManager.onViewShown(false)
+ advanceTimeBy(FREE_IMAGE_DELAY_MS)
+ runCurrent()
+ reset(mockConsumer)
+
+ // WHEN the view is hidden again
+ iconManager.onViewShown(false)
+ // AND we wait a bit
+ advanceTimeBy(FREE_IMAGE_DELAY_MS)
+ runCurrent()
+
+ // THEN nothing happens
+ verifyZeroInteractions(mockConsumer)
+ }
+
+ @Test
+ fun cancelJobs_freeImageJobRunning_jobCancelled() =
+ testScope.runTest {
+ // GIVEN full image is showing
+ iconManager.updateIcon(mockConsumer, supportedIcon).run()
+ iconManager.onViewShown(true)
+ runCurrent()
+ reset(mockConsumer)
+ // AND the view has just gone off the screen
+ iconManager.onViewShown(false)
+
+ // WHEN cancelJobs is called
+ iconManager.cancelJobs()
+ // AND we wait a bit
+ advanceTimeBy(FREE_IMAGE_DELAY_MS)
+ runCurrent()
+
+ // THEN no more updates are happening
+ verifyZeroInteractions(mockConsumer)
+ }
+
+ private fun assertIsPlaceHolder(drawable: Drawable) {
+ assertThat(drawable).isInstanceOf(PlaceHolderDrawable::class.java)
+ }
+
+ private fun assertIsFullImage(drawable: Drawable) {
+ assertThat(drawable).isInstanceOf(BitmapDrawable::class.java)
+ }
+
+ private fun assertSize(
+ drawable: Drawable,
+ expectedWidth: Int = expectedDrawable.intrinsicWidth,
+ expectedHeight: Int = expectedDrawable.intrinsicHeight
+ ) {
+ assertThat(drawable.intrinsicWidth).isEqualTo(expectedWidth)
+ assertThat(drawable.intrinsicHeight).isEqualTo(expectedHeight)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 3ad3c15f158a..c71c0c579e3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -50,6 +50,7 @@ import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.CameraLauncher;
import com.android.systemui.shade.QuickSettingsController;
@@ -79,6 +80,7 @@ import java.util.Optional;
public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@Mock private CentralSurfaces mCentralSurfaces;
+ @Mock private ScreenPinningRequest mScreenPinningRequest;
@Mock private ShadeController mShadeController;
@Mock private CommandQueue mCommandQueue;
@Mock private QuickSettingsController mQuickSettingsController;
@@ -116,6 +118,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
mQuickSettingsController,
mContext,
mContext.getResources(),
+ mScreenPinningRequest,
mShadeController,
mCommandQueue,
mShadeViewController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index bd3fb9f1cae3..6b944aed9368 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -116,7 +116,6 @@ import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
-import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.brightness.BrightnessSliderController;
@@ -285,7 +284,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private PluginManager mPluginManager;
@Mock private ViewMediatorCallback mViewMediatorCallback;
@Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
- @Mock private ScreenPinningRequest mScreenPinningRequest;
@Mock private PluginDependencyProvider mPluginDependencyProvider;
@Mock private ExtensionController mExtensionController;
@Mock private UserInfoControllerImpl mUserInfoControllerImpl;
@@ -508,7 +506,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mDozeServiceHost,
mBackActionInteractor,
mPowerManager,
- mScreenPinningRequest,
mDozeScrimController,
mVolumeComponent,
mCommandQueue,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index d100c687d802..79f8dbd687ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -30,6 +30,8 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -51,6 +53,8 @@ import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.battery.BatteryMeterViewController;
+import com.android.systemui.flags.FakeFeatureFlagsClassic;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeViewStateProvider;
import com.android.systemui.statusbar.CommandQueue;
@@ -79,6 +83,7 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
+ private final FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
@Mock
private CarrierTextController mCarrierTextController;
@Mock
@@ -131,6 +136,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
@Before
public void setup() throws Exception {
+ mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, false);
mShadeViewStateProvider = new TestShadeViewStateProvider();
MockitoAnnotations.initMocks(this);
@@ -166,6 +172,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
mBiometricUnlockController,
mStatusBarStateController,
mStatusBarContentInsetsProvider,
+ mFeatureFlags,
mUserManager,
mStatusBarUserChipViewModel,
mSecureSettings,
@@ -228,6 +235,30 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
}
@Test
+ public void onViewReAttached_flagOff_iconManagerNotReRegistered() {
+ mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, false);
+ mController.onViewAttached();
+ mController.onViewDetached();
+ reset(mStatusBarIconController);
+
+ mController.onViewAttached();
+
+ verify(mStatusBarIconController, never()).addIconGroup(any());
+ }
+
+ @Test
+ public void onViewReAttached_flagOn_iconManagerReRegistered() {
+ mFeatureFlags.set(Flags.MIGRATE_KEYGUARD_STATUS_BAR_VIEW, true);
+ mController.onViewAttached();
+ mController.onViewDetached();
+ reset(mStatusBarIconController);
+
+ mController.onViewAttached();
+
+ verify(mStatusBarIconController).addIconGroup(any());
+ }
+
+ @Test
public void setBatteryListening_true_callbackAdded() {
mController.setBatteryListening(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index ba4e8d325abc..bac857910329 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -976,4 +976,23 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
verify(mKeyguardMessageAreaController).setIsVisible(eq(false));
verify(mKeyguardMessageAreaController).setMessage(eq(""));
}
+
+ @Test
+ public void testShowBouncerOrKeyguard_needsFullScreen() {
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.SimPin);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+ verify(mCentralSurfaces).hideKeyguard();
+ verify(mPrimaryBouncerInteractor).show(true);
+ }
+
+ @Test
+ public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() {
+ when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+ KeyguardSecurityModel.SecurityMode.SimPin);
+ when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false);
+ verify(mCentralSurfaces, never()).hideKeyguard();
+ verify(mPrimaryBouncerInteractor, never()).show(true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 7f3d4b7f9f76..66c5aaa3ed07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -132,12 +132,6 @@ public class ZenModeControllerImplTest extends SysuiTestCase {
}
@Test
- public void testAddNullCallback() {
- mController.addCallback(null);
- mController.fireConfigChanged(null);
- }
-
- @Test
public void testModeChange() {
List<Integer> states = List.of(
Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index 2b13dcabb640..322fb284dad7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -56,20 +56,7 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
_isLockedOut.value = isLockedOut
}
- private val faceAuthPaused = MutableStateFlow(false)
- override fun pauseFaceAuth() {
- faceAuthPaused.value = true
- }
-
- override fun resumeFaceAuth() {
- faceAuthPaused.value = false
- }
-
- fun isFaceAuthPaused(): Boolean {
- return faceAuthPaused.value
- }
-
- override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+ override fun requestAuthenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
_runningAuthRequest.value = uiEvent to fallbackToDetection
_isAuthRunning.value = true
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
new file mode 100644
index 000000000000..13437c925367
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt
@@ -0,0 +1,64 @@
+package com.android.systemui.qs.tiles.base.interactor
+
+import javax.annotation.CheckReturnValue
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+
+class FakeQSTileDataInteractor<T>(
+ private val dataFlow: MutableSharedFlow<FakeData<T>> =
+ MutableSharedFlow(replay = Int.MAX_VALUE),
+ private val availabilityFlow: MutableSharedFlow<Boolean> =
+ MutableSharedFlow(replay = Int.MAX_VALUE),
+) : QSTileDataInteractor<T> {
+
+ private val mutableDataRequests = mutableListOf<QSTileDataRequest>()
+ val dataRequests: List<QSTileDataRequest> = mutableDataRequests
+
+ private val mutableAvailabilityRequests = mutableListOf<Unit>()
+ val availabilityRequests: List<Unit> = mutableAvailabilityRequests
+
+ @CheckReturnValue
+ fun emitData(data: T): FilterEmit =
+ object : FilterEmit {
+ override fun forRequest(request: QSTileDataRequest): Boolean =
+ dataFlow.tryEmit(FakeData(data, DataFilter.ForRequest(request)))
+ override fun forAnyRequest(): Boolean = dataFlow.tryEmit(FakeData(data, DataFilter.Any))
+ }
+
+ fun tryEmitAvailability(isAvailable: Boolean): Boolean = availabilityFlow.tryEmit(isAvailable)
+ suspend fun emitAvailability(isAvailable: Boolean) = availabilityFlow.emit(isAvailable)
+
+ override fun tileData(qsTileDataRequest: QSTileDataRequest): Flow<T> {
+ mutableDataRequests.add(qsTileDataRequest)
+ return dataFlow
+ .filter {
+ when (it.filter) {
+ is DataFilter.Any -> true
+ is DataFilter.ForRequest -> it.filter.request == qsTileDataRequest
+ }
+ }
+ .map { it.data }
+ }
+
+ override fun availability(): Flow<Boolean> {
+ mutableAvailabilityRequests.add(Unit)
+ return availabilityFlow
+ }
+
+ interface FilterEmit {
+ fun forRequest(request: QSTileDataRequest): Boolean
+ fun forAnyRequest(): Boolean
+ }
+
+ class FakeData<T>(
+ val data: T,
+ val filter: DataFilter,
+ )
+
+ sealed class DataFilter {
+ object Any : DataFilter()
+ class ForRequest(val request: QSTileDataRequest) : DataFilter()
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
new file mode 100644
index 000000000000..4e0266e25716
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt
@@ -0,0 +1,21 @@
+package com.android.systemui.qs.tiles.base.interactor
+
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+
+class FakeQSTileUserActionInteractor<T> : QSTileUserActionInteractor<T> {
+
+ private val mutex: Mutex = Mutex()
+ private val mutableInputs: MutableList<FakeInput<T>> = mutableListOf()
+
+ val inputs: List<FakeInput<T>> = mutableInputs
+
+ fun lastInput(): FakeInput<T>? = inputs.lastOrNull()
+
+ override suspend fun handleInput(userAction: QSTileUserAction, currentData: T) {
+ mutex.withLock { mutableInputs.add(FakeInput(userAction, currentData)) }
+ }
+
+ data class FakeInput<T>(val userAction: QSTileUserAction, val data: T)
+}
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 1e44de61bdd7..92e00ee0a477 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -1 +1,8 @@
-package: "android.service.autofill" \ No newline at end of file
+package: "android.service.autofill"
+
+flag {
+ name: "autofill_credman_integration"
+ namespace: "autofill"
+ description: "Guards Autofill Framework against Autofill-Credman integration"
+ bug: "296907283"
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/SmartStorageMaintIdler.java b/services/core/java/com/android/server/SmartStorageMaintIdler.java
index 899692611086..44f1e76f7a22 100644
--- a/services/core/java/com/android/server/SmartStorageMaintIdler.java
+++ b/services/core/java/com/android/server/SmartStorageMaintIdler.java
@@ -25,6 +25,7 @@ import android.content.Context;
import android.util.Slog;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
public class SmartStorageMaintIdler extends JobService {
private static final String TAG = "SmartStorageMaintIdler";
@@ -34,15 +35,15 @@ public class SmartStorageMaintIdler extends JobService {
private static final int SMART_MAINT_JOB_ID = 2808;
- private boolean mStarted;
+ private final AtomicBoolean mStarted = new AtomicBoolean(false);
private JobParameters mJobParams;
private final Runnable mFinishCallback = new Runnable() {
@Override
public void run() {
Slog.i(TAG, "Got smart storage maintenance service completion callback");
- if (mStarted) {
+ if (mStarted.get()) {
jobFinished(mJobParams, false);
- mStarted = false;
+ mStarted.set(false);
}
// ... and try again in a next period
scheduleSmartIdlePass(SmartStorageMaintIdler.this,
@@ -52,18 +53,26 @@ public class SmartStorageMaintIdler extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
- mJobParams = params;
- StorageManagerService ms = StorageManagerService.sSelf;
- if (ms != null) {
- mStarted = true;
- ms.runSmartIdleMaint(mFinishCallback);
+ final StorageManagerService ms = StorageManagerService.sSelf;
+ if (mStarted.compareAndSet(false, true)) {
+ new Thread() {
+ public void run() {
+ mJobParams = params;
+ if (ms != null) {
+ ms.runSmartIdleMaint(mFinishCallback);
+ } else {
+ mStarted.set(false);
+ }
+ }
+ }.start();
+ return ms != null;
}
- return ms != null;
+ return false;
}
@Override
public boolean onStopJob(JobParameters params) {
- mStarted = false;
+ mStarted.set(false);
return false;
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index a78764456d8b..25ca509cb949 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -2842,7 +2842,7 @@ class StorageManagerService extends IStorageManager.Stub
return true;
}
- void runSmartIdleMaint(Runnable callback) {
+ synchronized void runSmartIdleMaint(Runnable callback) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
try {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b5911f69b156..50be1288ded9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -315,6 +315,7 @@ import android.net.ConnectivityManager;
import android.net.Proxy;
import android.net.Uri;
import android.os.AppZygote;
+import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.BinderProxy;
@@ -4070,21 +4071,6 @@ public class ActivityManagerService extends IActivityManager.Stub
profile.addPss(mi.getTotalPss(),
mi.getTotalUss(), mi.getTotalRss(), false,
ProcessStats.ADD_PSS_EXTERNAL_SLOW, duration);
- proc.getPkgList().forEachPackageProcessStats(holder -> {
- final ProcessState state = holder.state;
- FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
- proc.info.uid,
- state != null ? state.getName() : proc.processName,
- state != null ? state.getPackage() : proc.info.packageName,
- mi.getTotalPss(),
- mi.getTotalUss(),
- mi.getTotalRss(),
- ProcessStats.ADD_PSS_EXTERNAL_SLOW,
- duration,
- holder.appVersion,
- profile.getCurrentHostingComponentTypes(),
- profile.getHistoricalHostingComponentTypes());
- });
}
}
}
@@ -4131,20 +4117,6 @@ public class ActivityManagerService extends IActivityManager.Stub
// Record this for posterity if the process has been stable.
profile.addPss(pi, tmpUss[0], tmpUss[2], false,
ProcessStats.ADD_PSS_EXTERNAL, duration);
- proc.getPkgList().forEachPackageProcessStats(holder -> {
- FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
- proc.info.uid,
- holder.state.getName(),
- holder.state.getPackage(),
- pi,
- tmpUss[0],
- tmpUss[2],
- ProcessStats.ADD_PSS_EXTERNAL,
- duration,
- holder.appVersion,
- profile.getCurrentHostingComponentTypes(),
- profile.getHistoricalHostingComponentTypes());
- });
}
}
}
@@ -9851,6 +9823,10 @@ public class ActivityManagerService extends IActivityManager.Stub
PriorityDump.dump(mPriorityDumper, fd, pw, args);
}
+ private static final String TICK =
+ "---------------------------------------"
+ + "----------------------------------------";
+
private void dumpEverything(FileDescriptor fd, PrintWriter pw, String[] args, int opti,
boolean dumpAll, String dumpPackage, int displayIdFilter, boolean dumpClient,
boolean dumpNormalPriority, int dumpAppId, boolean dumpProxies) {
@@ -9906,6 +9882,11 @@ public class ActivityManagerService extends IActivityManager.Stub
sdumper.dumpLocked();
}
}
+
+ // No need to hold the lock.
+ pw.println(TICK);
+ AnrTimer.dump(pw, false);
+
// We drop the lock here because we can't call dumpWithClient() with the lock held;
// if the caller wants a consistent state for the !dumpClient case, it can call this
// method with the lock held.
@@ -10351,6 +10332,8 @@ public class ActivityManagerService extends IActivityManager.Stub
mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
mOomAdjuster.dumpCacheOomRankerSettings(pw);
}
+ } else if ("timers".equals(cmd)) {
+ AnrTimer.dump(pw, true);
} else if ("services".equals(cmd) || "s".equals(cmd)) {
if (dumpClient) {
ActiveServices.ServiceDumper dumper;
@@ -12077,17 +12060,6 @@ public class ActivityManagerService extends IActivityManager.Stub
// Record this for posterity if the process has been stable.
r.mProfile.addPss(myTotalPss, myTotalUss, myTotalRss, true,
reportType, endTime - startTime);
- r.getPkgList().forEachPackageProcessStats(holder -> {
- FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
- r.info.uid,
- holder.state.getName(),
- holder.state.getPackage(),
- myTotalPss, myTotalUss, myTotalRss, reportType,
- endTime-startTime,
- holder.appVersion,
- r.mProfile.getCurrentHostingComponentTypes(),
- r.mProfile.getHistoricalHostingComponentTypes());
- });
}
}
@@ -12723,16 +12695,6 @@ public class ActivityManagerService extends IActivityManager.Stub
// Record this for posterity if the process has been stable.
r.mProfile.addPss(myTotalPss, myTotalUss, myTotalRss, true,
reportType, endTime - startTime);
- r.getPkgList().forEachPackageProcessStats(holder -> {
- FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
- r.info.uid,
- holder.state.getName(),
- holder.state.getPackage(),
- myTotalPss, myTotalUss, myTotalRss, reportType, endTime-startTime,
- holder.appVersion,
- r.mProfile.getCurrentHostingComponentTypes(),
- r.mProfile.getHistoricalHostingComponentTypes());
- });
}
}
@@ -15134,6 +15096,16 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ // STOPSHIP(b/298884211): Remove this logging
+ if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
+ final int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ if (level < 0) {
+ Slog.wtf(BroadcastQueue.TAG, "Unexpected broadcast: " + intent
+ + "; callingUid: " + callingUid + ", callingPid: " + callingPid,
+ new Throwable());
+ }
+ }
+
int[] users;
if (userId == UserHandle.USER_ALL) {
// Caller wants broadcast to go to all started users.
@@ -15866,7 +15838,15 @@ public class ActivityManagerService extends IActivityManager.Stub
activeInstr.mWatcher = watcher;
activeInstr.mUiAutomationConnection = uiAutomationConnection;
activeInstr.mResultClass = className;
- activeInstr.mHasBackgroundActivityStartsPermission = false;
+ activeInstr.mHasBackgroundActivityStartsPermission =
+ isSdkInSandbox
+ // TODO(b/261864298): consider using START_ACTIVITIES_FROM_BACKGROUND.
+ && checkPermission(
+ android.Manifest.permission
+ .START_ACTIVITIES_FROM_SDK_SANDBOX,
+ Binder.getCallingPid(),
+ Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED;
activeInstr.mHasBackgroundForegroundServiceStartsPermission = false;
// Instrumenting sdk sandbox without a restart is not supported
activeInstr.mNoRestart = false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 942d35a1d842..a057f32386f6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -4095,6 +4095,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" lru: raw LRU process list");
pw.println(" binder-proxies: stats on binder objects and IPCs");
pw.println(" settings: currently applied config settings");
+ pw.println(" timers: the current ANR timer state");
pw.println(" service [COMP_SPEC]: service client-side state");
pw.println(" package [PACKAGE_NAME]: all state related to given package");
pw.println(" all: dump all activities");
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index 7d984434284c..e0a224629174 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -20,6 +20,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import android.content.pm.ApplicationInfo;
+import android.os.Process;
import android.os.SystemClock;
import android.os.Trace;
import android.util.ArraySet;
@@ -267,6 +268,7 @@ class AnrHelper {
private class AnrRecord {
final ProcessRecord mApp;
final int mPid;
+ final int mUid;
final String mActivityShortComponentName;
final String mParentShortComponentName;
final TimeoutRecord mTimeoutRecord;
@@ -283,6 +285,7 @@ class AnrHelper {
Future<File> firstPidFilePromise) {
mApp = anrProcess;
mPid = anrProcess.mPid;
+ mUid = anrProcess.uid;
mActivityShortComponentName = activityShortComponentName;
mParentShortComponentName = parentShortComponentName;
mTimeoutRecord = timeoutRecord;
diff --git a/services/core/java/com/android/server/am/AnrTimer.java b/services/core/java/com/android/server/am/AnrTimer.java
new file mode 100644
index 000000000000..cd6f009a4106
--- /dev/null
+++ b/services/core/java/com/android/server/am/AnrTimer.java
@@ -0,0 +1,834 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.text.TextUtils.formatSimple;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.Keep;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ProcessCpuTracker;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class managers AnrTimers. An AnrTimer is a substitute for a delayed Message. In legacy
+ * mode, the timer just sends a delayed message. In modern mode, the timer is implemented in
+ * native code; on expiration, the message is sent without delay.
+ *
+ * <p>There are four external operations on a timer:
+ * <ul>
+ *
+ * <li>{@link #start} starts a timer. The timer is started with an object that the message
+ * argument. The timer is also given the pid and uid of the target. A timer that is started must
+ * be canceled, accepted, or discarded.
+ *
+ * <li>{@link #cancel} stops a timer and removes any in-flight expiration messages.
+ *
+ * <li>{@link #accept} acknowledges that the timer has expired, and that an ANR should be
+ * generated. This clears bookkeeping information for the timer.
+ *
+ * <li>{@link #discard} acknowledges that the timer has expired but, for other reasons, no ANR
+ * will be generated. This clears bookkeeping information for the timer.
+ *
+ *</li></p>
+ *
+ * <p>There is one internal operation on a timer: {@link #expire}. A timer may have automatic
+ * extensions enabled. If so, the extension is computed and if the extension is non-zero, the timer
+ * is restarted with the extension timeout. If extensions are disabled or if the extension is zero,
+ * the client process is notified of the expiration.
+ *
+ * @hide
+ */
+abstract class AnrTimer<V> {
+
+ /**
+ * The log tag.
+ */
+ final static String TAG = "AnrTimer";
+
+ /**
+ * The trace track for these events. There is a single track for all AnrTimer instances. The
+ * tracks give a sense of handler latency: the time between timer expiration and ANR
+ * collection.
+ */
+ private final static String TRACK = "AnrTimer";
+
+ /**
+ * Enable debug messages.
+ */
+ private static boolean DEBUG = false;
+
+ /**
+ * The trace tag.
+ */
+ private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+ /**
+ * Enable tracing from the time a timer expires until it is accepted or discarded. This is
+ * used to diagnose long latencies in the client.
+ */
+ private static final boolean ENABLE_TRACING = false;
+
+ /**
+ * The status of an ANR timer. TIMER_INVALID status is returned when an error is detected.
+ */
+ private static final int TIMER_INVALID = 0;
+ private static final int TIMER_RUNNING = 1;
+ private static final int TIMER_EXPIRED = 2;
+
+ @IntDef(prefix = { "TIMER_" }, value = {
+ TIMER_INVALID, TIMER_RUNNING, TIMER_EXPIRED
+ })
+ private @interface TimerStatus {}
+
+ /**
+ * A static list of all known AnrTimer instances, used for dumping and testing.
+ */
+ @GuardedBy("sAnrTimerList")
+ private static final ArrayList<WeakReference<AnrTimer>> sAnrTimerList = new ArrayList<>();
+
+ /**
+ * An error is defined by its issue, the operation that detected the error, the tag of the
+ * affected service, a short stack of the bad call, and the stringified arg associated with
+ * the error.
+ */
+ private static final class Error {
+ /** The issue is the kind of error that was detected. This is a free-form string. */
+ final String issue;
+ /** The operation that detected the error: start, cancel, accept, or discard. */
+ final String operation;
+ /** The argument (stringified) passed in to the operation. */
+ final String arg;
+ /** The tag of the associated AnrTimer. */
+ final String tag;
+ /** A partial stack that localizes the caller of the operation. */
+ final StackTraceElement[] stack;
+
+ Error(@NonNull String issue, @NonNull String operation, @NonNull String tag,
+ @NonNull StackTraceElement[] stack, @NonNull String arg) {
+ this.issue = issue;
+ this.operation = operation;
+ this.tag = tag;
+ this.stack = stack;
+ this.arg = arg;
+ }
+ }
+
+ /**
+ * A list of errors detected during processing. Errors correspond to "timer not found"
+ * conditions. The stack trace identifies the source of the call. The list is
+ * first-in/first-out, and the size is limited to MAX_SAVED_ERROR_COUNT.
+ */
+ @GuardedBy("sErrors")
+ private static final ArrayList<Error> sErrors = new ArrayList<>();
+
+ /**
+ * The maximum number of errors that are saved in the sErrors list.
+ */
+ private static final int MAX_SAVED_ERROR_COUNT = 20;
+
+ /**
+ * A record of a single anr timer. The pid and uid are retained for reference but they do not
+ * participate in the equality tests. A {@link Timer} is bound to its parent {@link AnrTimer}
+ * through the owner field. Access to timer fields is guarded by the mLock of the owner.
+ */
+ private static class Timer {
+ /** The AnrTimer that is managing this Timer. */
+ final AnrTimer owner;
+
+ /** The argument that uniquely identifies the Timer in the context of its current owner. */
+ final Object arg;
+ /** The pid of the process being tracked by this Timer. */
+ final int pid;
+ /** The uid of the process being tracked by this Timer as reported by the kernel. */
+ final int uid;
+ /** The original timeout. */
+ final long timeoutMs;
+
+ /** The status of the Timer. */
+ @GuardedBy("owner.mLock")
+ @TimerStatus
+ int status;
+
+ /** The absolute time the timer was startd */
+ final long startedMs;
+
+ /** Fields used by the native timer service. */
+
+ /** The timer ID: used to exchange information with the native service. */
+ int timerId;
+
+ /** Fields used by the legacy timer service. */
+
+ /**
+ * The process's cpu delay time when the timer starts . It is meaningful only if
+ * extendable is true. The cpu delay is cumulative, so the incremental delay that occurs
+ * during a timer is the delay at the end of the timer minus this value. Units are in
+ * milliseconds.
+ */
+ @GuardedBy("owner.mLock")
+ long initialCpuDelayMs;
+
+ /** True if the timer has been extended. */
+ @GuardedBy("owner.mLock")
+ boolean extended;
+
+ /**
+ * Fetch a new Timer. This is private. Clients should get a new timer using the obtain()
+ * method.
+ */
+ private Timer(int pid, int uid, @Nullable Object arg, long timeoutMs,
+ @NonNull AnrTimer service) {
+ this.arg = arg;
+ this.pid = pid;
+ this.uid = uid;
+ this.timerId = 0;
+ this.timeoutMs = timeoutMs;
+ this.startedMs = now();
+ this.owner = service;
+ this.initialCpuDelayMs = 0;
+ this.extended = false;
+ this.status = TIMER_INVALID;
+ }
+
+ /** Get a timer. This implementation constructs a new timer. */
+ static Timer obtain(int pid, int uid, @Nullable Object arg, long timeout,
+ @NonNull AnrTimer service) {
+ return new Timer(pid, uid, arg, timeout, service);
+ }
+
+ /** Release a timer. This implementation simply drops the timer. */
+ void release() {
+ }
+
+ /** Return the age of the timer. This is used for debugging. */
+ long age() {
+ return now() - startedMs;
+ }
+
+ /**
+ * The hash code is generated from the owner and the argument. By definition, the
+ * combination must be unique for the lifetime of an in-use Timer.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(owner, arg);
+ }
+
+ /**
+ * The equality check compares the owner and the argument. By definition, the combination
+ * must be unique for the lifetime of an in-use Timer.
+ */
+ @Override
+ public boolean equals(Object r) {
+ if (r instanceof Timer) {
+ Timer t = (Timer) r;
+ return Objects.equals(owner, t.owner) && Objects.equals(arg, t.arg);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ final int myStatus;
+ synchronized (owner.mLock) {
+ myStatus = status;
+ }
+ return "timerId=" + timerId + " pid=" + pid + " uid=" + uid
+ + " " + statusString(myStatus) + " " + owner.mLabel;
+ }
+ }
+
+ /** A lock for the AnrTimer instance. */
+ private final Object mLock = new Object();
+
+ /**
+ * The map from client argument to the associated timer.
+ */
+ @GuardedBy("mLock")
+ private final ArrayMap<V, Timer> mTimerMap = new ArrayMap<>();
+
+ /** The highwater mark of started, but not closed, timers. */
+ @GuardedBy("mLock")
+ private int mMaxStarted = 0;
+
+ /**
+ * The total number of timers started.
+ */
+ @GuardedBy("mLock")
+ private int mTotalStarted = 0;
+
+ /**
+ * The total number of errors detected.
+ */
+ @GuardedBy("mLock")
+ private int mTotalErrors = 0;
+
+ /**
+ * The total number of timers that have expired.
+ */
+ @GuardedBy("mLock")
+ private int mTotalExpired = 0;
+
+ /**
+ * A TimerService that generates a timeout event <n> milliseconds in the future. See the
+ * class documentation for an explanation of the operations.
+ */
+ private abstract class TimerService {
+ /** Start a timer. The timeout must be initialized. */
+ abstract boolean start(@NonNull Timer timer);
+
+ abstract void cancel(@NonNull Timer timer);
+
+ abstract void accept(@NonNull Timer timer);
+
+ abstract void discard(@NonNull Timer timer);
+ }
+
+ /**
+ * A class to assist testing. All methods are null by default but can be overridden as
+ * necessary for a test.
+ */
+ @VisibleForTesting
+ static class Injector {
+ /**
+ * Return a handler for the given Callback.
+ */
+ Handler getHandler(@NonNull Handler.Callback callback) {
+ return null;
+ };
+
+ /**
+ * Return a CpuTracker.
+ */
+ CpuTracker getTracker() {
+ return null;
+ }
+ }
+
+ /**
+ * A helper class to measure CPU delays. Given a process ID, this class will return the
+ * cumulative CPU delay for the PID, since process inception. This class is defined to assist
+ * testing.
+ */
+ @VisibleForTesting
+ static class CpuTracker {
+ /**
+ * The parameter to ProcessCpuTracker indicates that statistics should be collected on a
+ * single process and not on the collection of threads associated with that process.
+ */
+ private final ProcessCpuTracker mCpu = new ProcessCpuTracker(false);
+
+ /** A simple wrapper to fetch the delay. This method can be overridden for testing. */
+ long delay(int pid) {
+ return mCpu.getCpuDelayTimeForPid(pid);
+ }
+ }
+
+ /**
+ * The "user-space" implementation of the timer service. This service uses its own message
+ * handler to create timeouts.
+ */
+ private class HandlerTimerService extends TimerService {
+ /** The lock for this handler */
+ private final Object mLock = new Object();
+
+ /** The message handler for scheduling future events. */
+ private final Handler mHandler;
+
+ /** The interface to fetch process statistics that might extend an ANR timeout. */
+ private final CpuTracker mCpu;
+
+ /** Create a HandlerTimerService based on the input handler. */
+ HandlerTimerService(@NonNull Handler handler) {
+ mHandler = new Handler(handler.getLooper(), this::expires);
+ mCpu = new CpuTracker();
+ }
+
+ /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */
+ @VisibleForTesting
+ HandlerTimerService(@NonNull Injector injector) {
+ mHandler = injector.getHandler(this::expires);
+ mCpu = injector.getTracker();
+ }
+
+ /** Post a message with the specified timeout. The timer is not modified. */
+ private void post(@NonNull Timer t, long timeoutMillis) {
+ final Message msg = mHandler.obtainMessage();
+ msg.obj = t;
+ mHandler.sendMessageDelayed(msg, timeoutMillis);
+ }
+
+ /**
+ * The local expiration handler first attempts to compute a timer extension. If the timer
+ * should be extended, it is rescheduled in the future (granting more time to the
+ * associated process). If the timer should not be extended then the timeout is delivered
+ * to the client.
+ *
+ * A process is extended to account for the time the process was swapped out and was not
+ * runnable through no fault of its own. A timer can only be extended once and only if
+ * the AnrTimer permits extensions. Finally, a timer will never be extended by more than
+ * the original timeout, so the total timeout will never be more than twice the originally
+ * configured timeout.
+ */
+ private boolean expires(Message msg) {
+ Timer t = (Timer) msg.obj;
+ synchronized (mLock) {
+ long extension = 0;
+ if (mExtend && !t.extended) {
+ extension = mCpu.delay(t.pid) - t.initialCpuDelayMs;
+ if (extension < 0) extension = 0;
+ if (extension > t.timeoutMs) extension = t.timeoutMs;
+ t.extended = true;
+ }
+ if (extension > 0) {
+ post(t, extension);
+ } else {
+ onExpiredLocked(t, now());
+ }
+ }
+ return true;
+ }
+
+ @GuardedBy("mLock")
+ @Override
+ boolean start(@NonNull Timer t) {
+ if (mExtend) {
+ t.initialCpuDelayMs = mCpu.delay(t.pid);
+ }
+ post(t, t.timeoutMs);
+ return true;
+ }
+
+ @Override
+ void cancel(@NonNull Timer t) {
+ mHandler.removeMessages(0, t);
+ }
+
+ @Override
+ void accept(@NonNull Timer t) {
+ // Nothing to do.
+ }
+
+ @Override
+ void discard(@NonNull Timer t) {
+ // Nothing to do.
+ }
+
+ /** The string identifies this subclass of AnrTimerService as being based on handlers. */
+ @Override
+ public String toString() {
+ return "handler";
+ }
+ }
+
+ /**
+ * The handler for messages sent from this instance.
+ */
+ private final Handler mHandler;
+
+ /**
+ * The message type for messages sent from this interface.
+ */
+ private final int mWhat;
+
+ /**
+ * A label that identifies the AnrTimer associated with a Timer in log messages.
+ */
+ private final String mLabel;
+
+ /**
+ * Whether this timer instance supports extending timeouts.
+ */
+ private final boolean mExtend;
+
+ /**
+ * The timer service to use for this AnrTimer.
+ */
+ private final TimerService mTimerService;
+
+ /**
+ * Whether or not canceling a non-existent timer is an error. Clients often cancel freely
+ * preemptively, without knowing if the timer was ever started. Keeping this variable true
+ * means that such behavior is not an error.
+ */
+ private final boolean mLenientCancel = true;
+
+ /**
+ * The common constructor. A null injector results in a normal, production timer.
+ */
+ @VisibleForTesting
+ AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend,
+ @Nullable Injector injector) {
+ mHandler = handler;
+ mWhat = what;
+ mLabel = label;
+ mExtend = extend;
+ if (injector == null) {
+ mTimerService = new HandlerTimerService(handler);
+ } else {
+ mTimerService = new HandlerTimerService(injector);
+ }
+ synchronized (sAnrTimerList) {
+ sAnrTimerList.add(new WeakReference(this));
+ }
+ Log.i(TAG, formatSimple("created %s label: \"%s\"", mTimerService.toString(), label));
+ }
+
+ /**
+ * Create one timer instance for production. The client can ask for extensible timeouts.
+ */
+ AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
+ this(handler, what, label, extend, null);
+ }
+
+ /**
+ * Create one timer instance for production. There are no extensible timeouts.
+ */
+ AnrTimer(@NonNull Handler handler, int what, @NonNull String label) {
+ this(handler, what, label, false, null);
+ }
+
+ /**
+ * Start a trace on the timer. The trace is laid down in the AnrTimerTrack.
+ */
+ private void traceBegin(Timer t, String what) {
+ if (ENABLE_TRACING) {
+ final String label = formatSimple("%s(%d,%d,%s)", what, t.pid, t.uid, mLabel);
+ final int cookie = t.hashCode();
+ Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie);
+ }
+ }
+
+ /**
+ * End a trace on the timer.
+ */
+ private void traceEnd(Timer t) {
+ if (ENABLE_TRACING) {
+ final int cookie = t.hashCode();
+ Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie);
+ }
+ }
+
+ /**
+ * Return the string representation for a timer status.
+ */
+ private static String statusString(int s) {
+ switch (s) {
+ case TIMER_INVALID: return "invalid";
+ case TIMER_RUNNING: return "running";
+ case TIMER_EXPIRED: return "expired";
+ }
+ return formatSimple("unknown: %d", s);
+ }
+
+ /**
+ * Delete the timer associated with arg from the maps and return it. Return null if the timer
+ * was not found.
+ */
+ @GuardedBy("mLock")
+ private Timer removeLocked(V arg) {
+ Timer timer = mTimerMap.remove(arg);
+ return timer;
+ }
+
+ /**
+ * Return the number of timers currently running.
+ */
+ @VisibleForTesting
+ static int sizeOfTimerList() {
+ synchronized (sAnrTimerList) {
+ int totalTimers = 0;
+ for (int i = 0; i < sAnrTimerList.size(); i++) {
+ AnrTimer client = sAnrTimerList.get(i).get();
+ if (client != null) totalTimers += client.mTimerMap.size();
+ }
+ return totalTimers;
+ }
+ }
+
+ /**
+ * Clear out all existing timers. This will lead to unexpected behavior if used carelessly.
+ * It is available only for testing. It returns the number of times that were actually
+ * erased.
+ */
+ @VisibleForTesting
+ static int resetTimerListForHermeticTest() {
+ synchronized (sAnrTimerList) {
+ int mapLen = 0;
+ for (int i = 0; i < sAnrTimerList.size(); i++) {
+ AnrTimer client = sAnrTimerList.get(i).get();
+ if (client != null) {
+ mapLen += client.mTimerMap.size();
+ client.mTimerMap.clear();
+ }
+ }
+ if (mapLen > 0) {
+ Log.w(TAG, formatSimple("erasing timer list: clearing %d timers", mapLen));
+ }
+ return mapLen;
+ }
+ }
+
+ /**
+ * Report something about a timer.
+ */
+ private void report(@NonNull Timer timer, @NonNull String msg) {
+ Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg));
+ }
+
+ /**
+ * Start a timer.
+ */
+ boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+ final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, this);
+ synchronized (mLock) {
+ Timer old = mTimerMap.get(arg);
+ if (old != null) {
+ // There is an existing timer. This is a protocol error in the client. Record
+ // the error and then clean up by canceling running timers and discarding expired
+ // timers.
+ restartedLocked(old.status, arg);
+ if (old.status == TIMER_EXPIRED) {
+ discard(arg);
+ } else {
+ cancel(arg);
+ }
+ }
+ if (mTimerService.start(timer)) {
+ timer.status = TIMER_RUNNING;
+ mTimerMap.put(arg, timer);
+ mTotalStarted++;
+ mMaxStarted = Math.max(mMaxStarted, mTimerMap.size());
+ if (DEBUG) report(timer, "start");
+ return true;
+ } else {
+ Log.e(TAG, "AnrTimer.start failed");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Cancel a timer. Return false if the timer was not found.
+ */
+ boolean cancel(@NonNull V arg) {
+ synchronized (mLock) {
+ Timer timer = removeLocked(arg);
+ if (timer == null) {
+ if (!mLenientCancel) notFoundLocked("cancel", arg);
+ return false;
+ }
+ mTimerService.cancel(timer);
+ // There may be an expiration message in flight. Cancel it.
+ mHandler.removeMessages(mWhat, arg);
+ if (DEBUG) report(timer, "cancel");
+ timer.release();
+ return true;
+ }
+ }
+
+ /**
+ * Accept a timer in the framework-level handler. The timeout has been accepted and the
+ * timeout handler is executing. Return false if the timer was not found.
+ */
+ boolean accept(@NonNull V arg) {
+ synchronized (mLock) {
+ Timer timer = removeLocked(arg);
+ if (timer == null) {
+ notFoundLocked("accept", arg);
+ return false;
+ }
+ mTimerService.accept(timer);
+ traceEnd(timer);
+ if (DEBUG) report(timer, "accept");
+ timer.release();
+ return true;
+ }
+ }
+
+ /**
+ * Discard a timer in the framework-level handler. For whatever reason, the timer is no
+ * longer interesting. No statistics are collected. Return false if the time was not found.
+ */
+ boolean discard(@NonNull V arg) {
+ synchronized (mLock) {
+ Timer timer = removeLocked(arg);
+ if (timer == null) {
+ notFoundLocked("discard", arg);
+ return false;
+ }
+ mTimerService.discard(timer);
+ traceEnd(timer);
+ if (DEBUG) report(timer, "discard");
+ timer.release();
+ return true;
+ }
+ }
+
+ /**
+ * The notifier that a timer has fired. The timer is not modified.
+ */
+ @GuardedBy("mLock")
+ private void onExpiredLocked(@NonNull Timer timer, long when) {
+ if (DEBUG) report(timer, "expire");
+ traceBegin(timer, "expired");
+ mHandler.sendMessage(Message.obtain(mHandler, mWhat, timer.arg));
+ synchronized (mLock) {
+ mTotalExpired++;
+ }
+ }
+
+ /**
+ * Dump a single AnrTimer.
+ */
+ private void dump(IndentingPrintWriter pw) {
+ synchronized (mLock) {
+ pw.format("timer: %s\n", mLabel);
+ pw.increaseIndent();
+ pw.format("started=%d maxStarted=%d running=%d expired=%d error=%d\n",
+ mTotalStarted, mMaxStarted, mTimerMap.size(),
+ mTotalExpired, mTotalErrors);
+ pw.decreaseIndent();
+ }
+ }
+
+ /**
+ * Enable or disable debugging.
+ */
+ static void debug(boolean f) {
+ DEBUG = f;
+ }
+
+ /**
+ * The current time in milliseconds.
+ */
+ private static long now() {
+ return SystemClock.uptimeMillis();
+ }
+
+ /**
+ * Log an error. A limited stack trace leading to the client call that triggered the error is
+ * recorded. The stack trace assumes that this method is not called directly.
+ *
+ * If DEBUG is true, a log message is generated as well.
+ */
+ @GuardedBy("mLock")
+ private void recordErrorLocked(String operation, String errorMsg, Object arg) {
+ StackTraceElement[] s = Thread.currentThread().getStackTrace();
+ final String what = Objects.toString(arg);
+ // The copy range starts at the caller of the timer operation, and includes three levels.
+ // This should be enough to isolate the location of the call.
+ StackTraceElement[] location = Arrays.copyOfRange(s, 6, 9);
+ synchronized (sErrors) {
+ // Ensure the error list does not grow beyond the limit.
+ while (sErrors.size() >= MAX_SAVED_ERROR_COUNT) {
+ sErrors.remove(0);
+ }
+ // Add the new error to the list.
+ sErrors.add(new Error(errorMsg, operation, mLabel, location, what));
+ }
+ if (DEBUG) Log.w(TAG, operation + " " + errorMsg + " " + mLabel + " timer " + what);
+ mTotalErrors++;
+ }
+
+ /**
+ * Log an error about a timer not found.
+ */
+ @GuardedBy("mLock")
+ private void notFoundLocked(String operation, Object arg) {
+ recordErrorLocked(operation, "notFound", arg);
+ }
+
+ /**
+ * Log an error about a timer that is started when there is an existing timer.
+ */
+ @GuardedBy("mLock")
+ private void restartedLocked(@TimerStatus int status, Object arg) {
+ recordErrorLocked("start", status == TIMER_EXPIRED ? "autoDiscard" : "autoCancel", arg);
+ }
+
+ /**
+ * Dump a single error to the output stream.
+ */
+ private static void dump(IndentingPrintWriter ipw, int seq, Error err) {
+ ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag,
+ err.issue, err.arg);
+ ipw.increaseIndent();
+ for (int i = 0; i < err.stack.length; i++) {
+ ipw.println(" " + err.stack[i].toString());
+ }
+ ipw.decreaseIndent();
+ }
+
+ /**
+ * Dump all errors to the output stream.
+ */
+ private static void dumpErrors(IndentingPrintWriter ipw) {
+ ArrayList<Error> errors;
+ synchronized (sErrors) {
+ if (sErrors.size() == 0) return;
+ errors = (ArrayList<Error>) sErrors.clone();
+ }
+ ipw.println("Errors");
+ ipw.increaseIndent();
+ for (int i = 0; i < errors.size(); i++) {
+ dump(ipw, i, errors.get(i));
+ }
+ ipw.decreaseIndent();
+ }
+
+ /**
+ * Dumpsys output.
+ */
+ static void dump(@NonNull PrintWriter pw, boolean verbose) {
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.println("AnrTimer statistics");
+ ipw.increaseIndent();
+ synchronized (sAnrTimerList) {
+ for (int i = 0; i < sAnrTimerList.size(); i++) {
+ AnrTimer client = sAnrTimerList.get(i).get();
+ if (client != null) client.dump(ipw);
+ }
+ }
+ if (verbose) dumpErrors(ipw);
+ ipw.format("AnrTimerEnd\n");
+ ipw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 46e5523ac9a6..928b5d8f3ca7 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -770,17 +770,6 @@ public class AppProfiler {
swapPss * 1024, rss * 1024, statType, procState, pssDuration);
profile.setLastPssTime(now);
profile.addPss(pss, uss, rss, true, statType, pssDuration);
- proc.getPkgList().forEachPackageProcessStats(holder -> {
- FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_MEMORY_STAT_REPORTED,
- proc.info.uid,
- holder.state.getName(),
- holder.state.getPackage(),
- pss, uss, rss,
- statType, pssDuration,
- holder.appVersion,
- profile.getCurrentHostingComponentTypes(),
- profile.getHistoricalHostingComponentTypes());
- });
if (DEBUG_PSS) {
Slog.d(TAG_PSS,
"pss of " + proc.toShortString() + ": " + pss
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index fc8175b76ecd..c35a3b2474aa 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -101,10 +101,9 @@ class BroadcastProcessQueue {
boolean runningOomAdjusted;
/**
- * Snapshotted value of {@link ProcessRecord#getCpuDelayTime()}, typically
- * used when deciding if we should extend the soft ANR timeout.
+ * True if a timer has been started against this queue.
*/
- long lastCpuDelayTime;
+ private boolean mTimeoutScheduled;
/**
* Snapshotted value of {@link ProcessStateRecord#getCurProcState()} before
@@ -1344,6 +1343,21 @@ class BroadcastProcessQueue {
return head;
}
+ /**
+ * Set the timeout flag to indicate that an ANR timer has been started. A value of true means a
+ * timer is running; a value of false means there is no timer running.
+ */
+ void setTimeoutScheduled(boolean timeoutStarted) {
+ mTimeoutScheduled = timeoutStarted;
+ }
+
+ /**
+ * Get the timeout flag
+ */
+ boolean timeoutScheduled() {
+ return mTimeoutScheduled;
+ }
+
@Override
public String toString() {
if (mCachedToString == null) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 8d2edaaadb63..6a41628601b4 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -59,6 +59,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.BatteryManager;
import android.os.Bundle;
import android.os.BundleMerger;
import android.os.Handler;
@@ -149,6 +150,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// We configure runnable size only once at boot; it'd be too complex to
// try resizing dynamically at runtime
mRunning = new BroadcastProcessQueue[mConstants.getMaxRunningQueues()];
+
+ mAnrTimer = new BroadcastAnrTimer(mLocalHandler);
}
/**
@@ -242,14 +245,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
*/
private @UptimeMillisLong long mLastTestFailureTime;
+ /**
+ * The ANR timer service for broadcasts.
+ */
+ @GuardedBy("mService")
+ private final BroadcastAnrTimer mAnrTimer;
+
private static final int MSG_UPDATE_RUNNING_LIST = 1;
- private static final int MSG_DELIVERY_TIMEOUT_SOFT = 2;
- private static final int MSG_DELIVERY_TIMEOUT_HARD = 3;
- private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4;
- private static final int MSG_CHECK_HEALTH = 5;
- private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 6;
- private static final int MSG_PROCESS_FREEZABLE_CHANGED = 7;
- private static final int MSG_UID_STATE_CHANGED = 8;
+ private static final int MSG_DELIVERY_TIMEOUT = 2;
+ private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 3;
+ private static final int MSG_CHECK_HEALTH = 4;
+ private static final int MSG_CHECK_PENDING_COLD_START_VALIDITY = 5;
+ private static final int MSG_PROCESS_FREEZABLE_CHANGED = 6;
+ private static final int MSG_UID_STATE_CHANGED = 7;
private void enqueueUpdateRunningList() {
mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
@@ -264,12 +272,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
updateRunningList();
return true;
}
- case MSG_DELIVERY_TIMEOUT_SOFT: {
- deliveryTimeoutSoft((BroadcastProcessQueue) msg.obj, msg.arg1);
- return true;
- }
- case MSG_DELIVERY_TIMEOUT_HARD: {
- deliveryTimeoutHard((BroadcastProcessQueue) msg.obj);
+ case MSG_DELIVERY_TIMEOUT: {
+ deliveryTimeout((BroadcastProcessQueue) msg.obj);
return true;
}
case MSG_BG_ACTIVITY_START_TIMEOUT: {
@@ -1024,12 +1028,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// immediately assume delivery success
final boolean assumeDelivered = r.isAssumedDelivered(index);
if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
- queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
-
+ queue.setTimeoutScheduled(true);
final int softTimeoutMillis = (int) (r.isForeground() ? mFgConstants.TIMEOUT
: mBgConstants.TIMEOUT);
- mLocalHandler.sendMessageDelayed(Message.obtain(mLocalHandler,
- MSG_DELIVERY_TIMEOUT_SOFT, softTimeoutMillis, 0, queue), softTimeoutMillis);
+ mAnrTimer.start(queue, softTimeoutMillis);
+ } else {
+ queue.setTimeoutScheduled(false);
}
if (r.mBackgroundStartPrivileges.allowsAny()) {
@@ -1074,6 +1078,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
queue.lastProcessState = app.mState.getCurProcState();
if (receiver instanceof BroadcastFilter) {
notifyScheduleRegisteredReceiver(app, r, (BroadcastFilter) receiver);
+ // STOPSHIP(b/298884211): Remove this logging
+ if (Intent.ACTION_BATTERY_CHANGED.equals(receiverIntent.getAction())) {
+ int level = receiverIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ if (level < 0) {
+ Slog.wtf(TAG, "Dispatching unexpected broadcast: " + receiverIntent
+ + " to " + receiver
+ + "; callingUid: " + r.callingUid
+ + ", callingPid: " + r.callingPid);
+ }
+ }
thread.scheduleRegisteredReceiver(
((BroadcastFilter) receiver).receiverList.receiver,
receiverIntent, r.resultCode, r.resultData, r.resultExtras,
@@ -1107,7 +1121,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// If we were trying to deliver a manifest broadcast, throw the error as we need
// to try redelivering the broadcast to this receiver.
if (receiver instanceof ResolveInfo) {
- mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
+ mAnrTimer.cancel(queue);
throw new BroadcastDeliveryFailedException(e);
}
finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
@@ -1156,39 +1170,27 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
r.resultTo = null;
}
- private void deliveryTimeoutSoft(@NonNull BroadcastProcessQueue queue,
- int softTimeoutMillis) {
+ private void deliveryTimeout(@NonNull BroadcastProcessQueue queue) {
synchronized (mService) {
- deliveryTimeoutSoftLocked(queue, softTimeoutMillis);
+ deliveryTimeoutLocked(queue);
}
}
- private void deliveryTimeoutSoftLocked(@NonNull BroadcastProcessQueue queue,
- int softTimeoutMillis) {
- if (queue.app != null) {
- // Instead of immediately triggering an ANR, extend the timeout by
- // the amount of time the process was runnable-but-waiting; we're
- // only willing to do this once before triggering an hard ANR
- final long cpuDelayTime = queue.app.getCpuDelayTime() - queue.lastCpuDelayTime;
- final long hardTimeoutMillis = MathUtils.constrain(cpuDelayTime, 0, softTimeoutMillis);
- mLocalHandler.sendMessageDelayed(
- Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT_HARD, queue),
- hardTimeoutMillis);
- } else {
- deliveryTimeoutHardLocked(queue);
- }
+ private void deliveryTimeoutLocked(@NonNull BroadcastProcessQueue queue) {
+ finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
+ "deliveryTimeoutLocked");
+ demoteFromRunningLocked(queue);
}
- private void deliveryTimeoutHard(@NonNull BroadcastProcessQueue queue) {
- synchronized (mService) {
- deliveryTimeoutHardLocked(queue);
+ private class BroadcastAnrTimer extends AnrTimer<BroadcastProcessQueue> {
+ BroadcastAnrTimer(Handler handler) {
+ super(Objects.requireNonNull(handler),
+ MSG_DELIVERY_TIMEOUT, "BROADCAST_TIMEOUT", true);
}
- }
- private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
- finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
- "deliveryTimeoutHardLocked");
- demoteFromRunningLocked(queue);
+ void start(@NonNull BroadcastProcessQueue queue, long timeoutMillis) {
+ start(queue, queue.app.getPid(), queue.app.uid, timeoutMillis);
+ }
}
@Override
@@ -1292,14 +1294,16 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
r.anrCount++;
if (app != null && !app.isDebugging()) {
+ mAnrTimer.accept(queue);
final String packageName = getReceiverPackageName(receiver);
final String className = getReceiverClassName(receiver);
mService.appNotResponding(queue.app,
TimeoutRecord.forBroadcastReceiver(r.intent, packageName, className));
+ } else {
+ mAnrTimer.discard(queue);
}
- } else {
- mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
- mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
+ } else if (queue.timeoutScheduled()) {
+ mAnrTimer.cancel(queue);
}
// Given that a receiver just finished, check if the "waitingFor" conditions are met.
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 4bfc09075448..21273e0f2785 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -17,10 +17,11 @@
package com.android.server.display;
import android.hardware.display.BrightnessInfo;
+import android.os.Handler;
import android.os.IBinder;
-import android.provider.DeviceConfigInterface;
-import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.brightness.clamper.HdrClamper;
+import com.android.server.display.feature.DisplayManagerFlags;
import java.io.PrintWriter;
import java.util.function.BooleanSupplier;
@@ -31,23 +32,30 @@ class BrightnessRangeController {
private final NormalBrightnessModeController mNormalBrightnessModeController =
new NormalBrightnessModeController();
+ private final HdrClamper mHdrClamper;
+
private final Runnable mModeChangeCallback;
private final boolean mUseNbmController;
+ private final boolean mUseHdrClamper;
+
BrightnessRangeController(HighBrightnessModeController hbmController,
- Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig) {
+ Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler,
+ DisplayManagerFlags flags) {
this(hbmController, modeChangeCallback, displayDeviceConfig,
- new DeviceConfigParameterProvider(DeviceConfigInterface.REAL));
+ new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags);
}
BrightnessRangeController(HighBrightnessModeController hbmController,
Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
- DeviceConfigParameterProvider configParameterProvider) {
+ HdrClamper hdrClamper, DisplayManagerFlags flags) {
mHbmController = hbmController;
mModeChangeCallback = modeChangeCallback;
- mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled();
+ mUseHdrClamper = false;
+ mUseNbmController = flags.isNbmControllerEnabled();
mNormalBrightnessModeController.resetNbmData(displayDeviceConfig.getLuxThrottlingData());
+ mHdrClamper = hdrClamper;
}
void dump(PrintWriter pw) {
@@ -55,7 +63,6 @@ class BrightnessRangeController {
pw.println(" mUseNormalBrightnessController=" + mUseNbmController);
mHbmController.dump(pw);
mNormalBrightnessModeController.dump(pw);
-
}
void onAmbientLuxChange(float ambientLux) {
@@ -63,6 +70,9 @@ class BrightnessRangeController {
() -> mNormalBrightnessModeController.onAmbientLuxChange(ambientLux),
() -> mHbmController.onAmbientLuxChange(ambientLux)
);
+ if (mUseHdrClamper) {
+ mHdrClamper.onAmbientLuxChange(ambientLux);
+ }
}
float getNormalBrightnessMax() {
@@ -118,7 +128,8 @@ class BrightnessRangeController {
}
float getHdrBrightnessValue() {
- return mHbmController.getHdrBrightnessValue();
+ float hdrBrightness = mHbmController.getHdrBrightnessValue();
+ return Math.min(hdrBrightness, mHdrClamper.getMaxBrightness());
}
float getTransitionPoint() {
@@ -138,4 +149,8 @@ class BrightnessRangeController {
hbmChangesFunc.run();
}
}
+
+ public float getHdrTransitionRate() {
+ return mHdrClamper.getTransitionRate();
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index e3dafa4a4cc0..3a6e5f93861b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -53,6 +53,7 @@ import com.android.server.display.config.DisplayBrightnessPoint;
import com.android.server.display.config.DisplayConfiguration;
import com.android.server.display.config.DisplayQuirks;
import com.android.server.display.config.HbmTiming;
+import com.android.server.display.config.HdrBrightnessData;
import com.android.server.display.config.HighBrightnessMode;
import com.android.server.display.config.IntegerArray;
import com.android.server.display.config.LuxThrottling;
@@ -232,7 +233,22 @@ import javax.xml.datatype.DatatypeConfigurationException;
* </point>
* </sdrHdrRatioMap>
* </highBrightnessMode>
- *
+ * <hdrBrightnessConfig>
+ * <brightnessMap>
+ * <point>
+ * <first>500</first>
+ * <second>0.3</second>
+ * </point>
+ * <point>
+ * <first>1200</first>
+ * <second>0.6</second>
+ * </point>
+ * </brightnessMap>
+ * <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>
+ * <brightnessIncreaseDurationMillis>10000</brightnessIncreaseDurationMillis>
+ * <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>
+ * <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>
+ * </hdrBrightnessConfig>
* <luxThrottling>
* <brightnessLimitMap>
* <type>default</type>
@@ -769,6 +785,9 @@ public class DisplayDeviceConfig {
@Nullable
private HostUsiVersion mHostUsiVersion;
+ @Nullable
+ private HdrBrightnessData mHdrBrightnessData;
+
@VisibleForTesting
DisplayDeviceConfig(Context context) {
mContext = context;
@@ -1544,6 +1563,14 @@ public class DisplayDeviceConfig {
}
/**
+ * @return HDR brightness related configuration
+ */
+ @Nullable
+ public HdrBrightnessData getHdrBrightnessData() {
+ return mHdrBrightnessData;
+ }
+
+ /**
* @return Refresh rate range for specific profile id or null
*/
@Nullable
@@ -1759,7 +1786,8 @@ public class DisplayDeviceConfig {
+ "mScreenOffBrightnessSensorValueToLux=" + Arrays.toString(
mScreenOffBrightnessSensorValueToLux)
+ "\n"
- + "mUsiVersion= " + mHostUsiVersion
+ + "mUsiVersion= " + mHostUsiVersion + "\n"
+ + "mHdrBrightnessData" + mHdrBrightnessData
+ "}";
}
@@ -1823,6 +1851,7 @@ public class DisplayDeviceConfig {
loadRefreshRateSetting(config);
loadScreenOffBrightnessSensorValueToLuxFromDdc(config);
loadUsiVersion(config);
+ mHdrBrightnessData = HdrBrightnessData.loadConfig(config);
} else {
Slog.w(TAG, "DisplayDeviceConfig file is null");
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 0802c9d76cf3..540ddd235e8c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -3230,12 +3230,12 @@ public final class DisplayManagerService extends SystemService {
displayPowerController = new DisplayPowerController2(
mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
- () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted);
+ () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags);
} else {
displayPowerController = new DisplayPowerController(
mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler,
mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting,
- () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted);
+ () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags);
}
mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController);
return displayPowerController;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 40dbabf29807..320684fe3a0f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -75,6 +75,7 @@ import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.Layout;
import com.android.server.display.utils.SensorUtils;
import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
@@ -592,7 +593,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata,
- boolean bootCompleted) {
+ boolean bootCompleted, DisplayManagerFlags flags) {
mInjector = injector != null ? injector : new Injector();
mClock = mInjector.getClock();
@@ -677,7 +678,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback);
mBrightnessRangeController = new BrightnessRangeController(hbmController,
- modeChangeCallback, mDisplayDeviceConfig);
+ modeChangeCallback, mDisplayDeviceConfig, mHandler, flags);
mBrightnessThrottler = createBrightnessThrottlerLocked();
@@ -1921,8 +1922,25 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if (isValidBrightnessValue(animateValue)
&& (animateValue != currentBrightness
|| sdrAnimateValue != currentSdrBrightness)) {
- if (initialRampSkip || hasBrightnessBuckets
- || !isDisplayContentVisible || brightnessIsTemporary) {
+ boolean skipAnimation = initialRampSkip || hasBrightnessBuckets
+ || !isDisplayContentVisible || brightnessIsTemporary;
+ if (!skipAnimation && BrightnessSynchronizer.floatEquals(
+ sdrAnimateValue, currentSdrBrightness)) {
+ // Going from HDR to no HDR; visually this should be a "no-op" anyway
+ // as the remaining SDR content's brightness should be holding steady
+ // due to the sdr brightness not shifting
+ if (BrightnessSynchronizer.floatEquals(sdrAnimateValue, animateValue)) {
+ skipAnimation = true;
+ }
+
+ // Going from no HDR to HDR; visually this is a significant scene change
+ // and the animation just prevents advanced clients from doing their own
+ // handling of enter/exit animations if they would like to do such a thing
+ if (BrightnessSynchronizer.floatEquals(sdrAnimateValue, currentBrightness)) {
+ skipAnimation = true;
+ }
+ }
+ if (skipAnimation) {
animateScreenBrightness(animateValue, sdrAnimateValue,
SCREEN_ANIMATION_RATE_MINIMUM);
} else {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 460c351c4027..597738e8b293 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -77,6 +77,7 @@ import com.android.server.display.brightness.clamper.BrightnessClamperController
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.Layout;
import com.android.server.display.state.DisplayStateController;
import com.android.server.display.utils.SensorUtils;
@@ -472,7 +473,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay,
BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting,
Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata,
- boolean bootCompleted) {
+ boolean bootCompleted, DisplayManagerFlags flags) {
mInjector = injector != null ? injector : new Injector();
mClock = mInjector.getClock();
@@ -539,8 +540,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
modeChangeCallback);
mBrightnessThrottler = createBrightnessThrottlerLocked();
- mBrightnessRangeController = new BrightnessRangeController(hbmController,
- modeChangeCallback, mDisplayDeviceConfig);
+ mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController,
+ modeChangeCallback, mDisplayDeviceConfig, mHandler, flags);
mDisplayBrightnessController =
new DisplayBrightnessController(context, null,
@@ -1497,6 +1498,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// allowed range.
float animateValue = clampScreenBrightness(brightnessState);
+ // custom transition duration
+ float customTransitionRate = -1f;
+
// If there are any HDR layers on the screen, we have a special brightness value that we
// use instead. We still preserve the calculated brightness for Standard Dynamic Range
// (SDR) layers, but the main brightness value will be the one for HDR.
@@ -1511,6 +1515,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// We want to scale HDR brightness level with the SDR level, we also need to restore
// SDR brightness immediately when entering dim or low power mode.
animateValue = mBrightnessRangeController.getHdrBrightnessValue();
+ customTransitionRate = mBrightnessRangeController.getHdrTransitionRate();
}
final float currentBrightness = mPowerState.getScreenBrightness();
@@ -1519,10 +1524,30 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
if (BrightnessUtils.isValidBrightnessValue(animateValue)
&& (animateValue != currentBrightness
|| sdrAnimateValue != currentSdrBrightness)) {
- if (initialRampSkip || hasBrightnessBuckets
- || !isDisplayContentVisible || brightnessIsTemporary) {
+ boolean skipAnimation = initialRampSkip || hasBrightnessBuckets
+ || !isDisplayContentVisible || brightnessIsTemporary;
+ if (!skipAnimation && BrightnessSynchronizer.floatEquals(
+ sdrAnimateValue, currentSdrBrightness)) {
+ // Going from HDR to no HDR; visually this should be a "no-op" anyway
+ // as the remaining SDR content's brightness should be holding steady
+ // due to the sdr brightness not shifting
+ if (BrightnessSynchronizer.floatEquals(sdrAnimateValue, animateValue)) {
+ skipAnimation = true;
+ }
+
+ // Going from no HDR to HDR; visually this is a significant scene change
+ // and the animation just prevents advanced clients from doing their own
+ // handling of enter/exit animations if they would like to do such a thing
+ if (BrightnessSynchronizer.floatEquals(sdrAnimateValue, currentBrightness)) {
+ skipAnimation = true;
+ }
+ }
+ if (skipAnimation) {
animateScreenBrightness(animateValue, sdrAnimateValue,
SCREEN_ANIMATION_RATE_MINIMUM);
+ } else if (customTransitionRate > 0) {
+ animateScreenBrightness(animateValue, sdrAnimateValue,
+ customTransitionRate);
} else {
boolean isIncreasing = animateValue > currentBrightness;
final float rampSpeed;
@@ -2968,6 +2993,14 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
hbmChangeCallback, hbmMetadata, context);
}
+ BrightnessRangeController getBrightnessRangeController(
+ HighBrightnessModeController hbmController, Runnable modeChangeCallback,
+ DisplayDeviceConfig displayDeviceConfig, Handler handler,
+ DisplayManagerFlags flags) {
+ return new BrightnessRangeController(hbmController,
+ modeChangeCallback, displayDeviceConfig, handler, flags);
+ }
+
DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
SensorManager sensorManager, Resources resources) {
return DisplayWhiteBalanceFactory.create(handler,
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 52b92c4c7ca6..378cdba09520 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -31,7 +31,12 @@ class RampAnimator<T> {
private final FloatProperty<T> mProperty;
private float mCurrentValue;
- private float mTargetValue;
+
+ // target in HLG space
+ private float mTargetHlgValue;
+
+ // target in linear space
+ private float mTargetLinearValue;
private float mRate;
private float mAnimationIncreaseMaxTimeSecs;
private float mAnimationDecreaseMaxTimeSecs;
@@ -78,7 +83,8 @@ class RampAnimator<T> {
if (mFirstTime || target != mCurrentValue) {
mFirstTime = false;
mRate = 0;
- mTargetValue = target;
+ mTargetHlgValue = target;
+ mTargetLinearValue = targetLinear;
mCurrentValue = target;
setPropertyValue(target);
mAnimating = false;
@@ -105,13 +111,14 @@ class RampAnimator<T> {
// Otherwise, continue at the previous rate.
if (!mAnimating
|| rate > mRate
- || (target <= mCurrentValue && mCurrentValue <= mTargetValue)
- || (mTargetValue <= mCurrentValue && mCurrentValue <= target)) {
+ || (target <= mCurrentValue && mCurrentValue <= mTargetHlgValue)
+ || (mTargetHlgValue <= mCurrentValue && mCurrentValue <= target)) {
mRate = rate;
}
- final boolean changed = (mTargetValue != target);
- mTargetValue = target;
+ final boolean changed = (mTargetHlgValue != target);
+ mTargetHlgValue = target;
+ mTargetLinearValue = targetLinear;
// Start animating.
if (!mAnimating && target != mCurrentValue) {
@@ -135,7 +142,11 @@ class RampAnimator<T> {
* into linear space.
*/
private void setPropertyValue(float val) {
- final float linearVal = BrightnessUtils.convertGammaToLinear(val);
+ // To avoid linearVal inconsistency when converting to HLG and back to linear space
+ // used original target linear value for final animation step
+ float linearVal =
+ val == mTargetHlgValue ? mTargetLinearValue : BrightnessUtils.convertGammaToLinear(
+ val);
mProperty.setValue(mObject, linearVal);
}
@@ -150,13 +161,13 @@ class RampAnimator<T> {
final float scale = ValueAnimator.getDurationScale();
if (scale == 0) {
// Animation off.
- mAnimatedValue = mTargetValue;
+ mAnimatedValue = mTargetHlgValue;
} else {
final float amount = timeDelta * mRate / scale;
- if (mTargetValue > mCurrentValue) {
- mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue);
+ if (mTargetHlgValue > mCurrentValue) {
+ mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetHlgValue);
} else {
- mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue);
+ mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetHlgValue);
}
}
final float oldCurrentValue = mCurrentValue;
@@ -164,7 +175,7 @@ class RampAnimator<T> {
if (oldCurrentValue != mCurrentValue) {
setPropertyValue(mCurrentValue);
}
- if (mTargetValue == mCurrentValue) {
+ if (mTargetHlgValue == mCurrentValue) {
mAnimating = false;
}
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 693611263d66..d910e16de8e8 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -167,6 +167,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
int width, int height, int densityDpi) {
VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
if (device != null) {
+ Slog.v(TAG, "Resize VirtualDisplay " + device.mName + " to " + width
+ + " " + height);
device.resizeLocked(width, height, densityDpi);
}
}
@@ -183,6 +185,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
public void setVirtualDisplaySurfaceLocked(IBinder appToken, Surface surface) {
VirtualDisplayDevice device = mVirtualDisplayDevices.get(appToken);
if (device != null) {
+ Slog.v(TAG, "Update surface for VirtualDisplay " + device.mName);
device.setSurfaceLocked(surface);
}
}
@@ -197,6 +200,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) {
VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
if (device != null) {
+ Slog.v(TAG, "Release VirtualDisplay " + device.mName);
device.destroyLocked(true);
appToken.unlinkToDeath(device, 0);
}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
new file mode 100644
index 000000000000..079a196234a8
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.os.Handler;
+import android.os.PowerManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class HdrClamper {
+
+ private final Configuration mConfiguration = new Configuration();
+
+ private final BrightnessClamperController.ClamperChangeListener mClamperChangeListener;
+
+ private final Handler mHandler;
+
+ private final Runnable mDebouncer;
+
+ private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+
+ // brightness change speed, in units per seconds,
+ private float mTransitionRate = -1f;
+
+ private float mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+
+ private float mDesiredTransitionDuration = -1; // in seconds
+
+ public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener,
+ Handler handler) {
+ mClamperChangeListener = clamperChangeListener;
+ mHandler = handler;
+ mDebouncer = () -> {
+ mTransitionRate = Math.abs((mMaxBrightness - mDesiredMaxBrightness)
+ / mDesiredTransitionDuration);
+ mMaxBrightness = mDesiredMaxBrightness;
+ mClamperChangeListener.onChanged();
+ };
+ }
+
+ // Called in same looper: mHandler.getLooper()
+ public float getMaxBrightness() {
+ return mMaxBrightness;
+ }
+
+ // Called in same looper: mHandler.getLooper()
+ public float getTransitionRate() {
+ return mTransitionRate;
+ }
+
+
+ /**
+ * Updates brightness cap in response to ambient lux change.
+ * Called by ABC in same looper: mHandler.getLooper()
+ */
+ public void onAmbientLuxChange(float ambientLux) {
+ float expectedMaxBrightness = findBrightnessLimit(ambientLux);
+ if (mMaxBrightness == expectedMaxBrightness) {
+ mDesiredMaxBrightness = mMaxBrightness;
+ mDesiredTransitionDuration = -1;
+ mTransitionRate = -1f;
+ mHandler.removeCallbacks(mDebouncer);
+ } else if (mDesiredMaxBrightness != expectedMaxBrightness) {
+ mDesiredMaxBrightness = expectedMaxBrightness;
+ long debounceTime;
+ if (mDesiredMaxBrightness > mMaxBrightness) {
+ debounceTime = mConfiguration.mIncreaseConfig.mDebounceTimeMillis;
+ mDesiredTransitionDuration =
+ (float) mConfiguration.mIncreaseConfig.mTransitionTimeMillis / 1000;
+ } else {
+ debounceTime = mConfiguration.mDecreaseConfig.mDebounceTimeMillis;
+ mDesiredTransitionDuration =
+ (float) mConfiguration.mDecreaseConfig.mTransitionTimeMillis / 1000;
+ }
+
+ mHandler.removeCallbacks(mDebouncer);
+ mHandler.postDelayed(mDebouncer, debounceTime);
+ }
+ }
+
+ @VisibleForTesting
+ Configuration getConfiguration() {
+ return mConfiguration;
+ }
+
+ private float findBrightnessLimit(float ambientLux) {
+ float foundAmbientBoundary = Float.MAX_VALUE;
+ float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
+ for (Map.Entry<Float, Float> brightnessPoint :
+ mConfiguration.mMaxBrightnessLimits.entrySet()) {
+ float ambientBoundary = brightnessPoint.getKey();
+ // find ambient lux upper boundary closest to current ambient lux
+ if (ambientBoundary > ambientLux && ambientBoundary < foundAmbientBoundary) {
+ foundMaxBrightness = brightnessPoint.getValue();
+ foundAmbientBoundary = ambientBoundary;
+ }
+ }
+ return foundMaxBrightness;
+ }
+
+ @VisibleForTesting
+ static class Configuration {
+ final Map<Float, Float> mMaxBrightnessLimits = new HashMap<>();
+ final TransitionConfiguration mIncreaseConfig = new TransitionConfiguration();
+
+ final TransitionConfiguration mDecreaseConfig = new TransitionConfiguration();
+ }
+
+ @VisibleForTesting
+ static class TransitionConfiguration {
+ long mDebounceTimeMillis;
+
+ long mTransitionTimeMillis;
+ }
+}
diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
new file mode 100644
index 000000000000..06d3c5b87520
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config;
+
+import android.annotation.Nullable;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Brightness config for HDR content
+ */
+public class HdrBrightnessData {
+
+ /**
+ * Lux to brightness map
+ */
+ public final Map<Float, Float> mMaxBrightnessLimits;
+
+ /**
+ * Debounce time for brightness increase
+ */
+ public final long mBrightnessIncreaseDebounceMillis;
+
+ /**
+ * Brightness increase animation duration
+ */
+ public final long mBrightnessIncreaseDurationMillis;
+
+ /**
+ * Debounce time for brightness decrease
+ */
+ public final long mBrightnessDecreaseDebounceMillis;
+
+ /**
+ * Brightness decrease animation duration
+ */
+ public final long mBrightnessDecreaseDurationMillis;
+
+ private HdrBrightnessData(Map<Float, Float> maxBrightnessLimits,
+ long brightnessIncreaseDebounceMillis, long brightnessIncreaseDurationMillis,
+ long brightnessDecreaseDebounceMillis, long brightnessDecreaseDurationMillis) {
+ mMaxBrightnessLimits = maxBrightnessLimits;
+ mBrightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis;
+ mBrightnessIncreaseDurationMillis = brightnessIncreaseDurationMillis;
+ mBrightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
+ mBrightnessDecreaseDurationMillis = brightnessDecreaseDurationMillis;
+ }
+
+ @Override
+ public String toString() {
+ return "HdrBrightnessData {"
+ + "mMaxBrightnessLimits: " + mMaxBrightnessLimits
+ + ", mBrightnessIncreaseDebounceMillis: " + mBrightnessIncreaseDebounceMillis
+ + ", mBrightnessIncreaseDurationMillis: " + mBrightnessIncreaseDurationMillis
+ + ", mBrightnessDecreaseDebounceMillis: " + mBrightnessDecreaseDebounceMillis
+ + ", mBrightnessDecreaseDurationMillis: " + mBrightnessDecreaseDurationMillis
+ + "} ";
+ }
+
+ /**
+ * Loads HdrBrightnessData from DisplayConfiguration
+ */
+ @Nullable
+ public static HdrBrightnessData loadConfig(DisplayConfiguration config) {
+ HdrBrightnessConfig hdrConfig = config.getHdrBrightnessConfig();
+ if (hdrConfig == null) {
+ return null;
+ }
+
+ List<NonNegativeFloatToFloatPoint> points = hdrConfig.getBrightnessMap().getPoint();
+ Map<Float, Float> brightnessLimits = new HashMap<>();
+ for (NonNegativeFloatToFloatPoint point: points) {
+ brightnessLimits.put(point.getFirst().floatValue(), point.getSecond().floatValue());
+ }
+
+ return new HdrBrightnessData(brightnessLimits,
+ hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(),
+ hdrConfig.getBrightnessIncreaseDurationMillis().longValue(),
+ hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(),
+ hdrConfig.getBrightnessDecreaseDurationMillis().longValue());
+ }
+}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index fddac6dcf874..aebd8a08ee3f 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -30,43 +30,68 @@ import java.util.function.Supplier;
public class DisplayManagerFlags {
private static final boolean DEBUG = false;
private static final String TAG = "DisplayManagerFlags";
- private boolean mIsConnectedDisplayManagementEnabled = false;
- private boolean mIsConnectedDisplayManagementEnabledSet = false;
- private boolean flagOrSystemProperty(Supplier<Boolean> flagFunction, String flagName) {
- // TODO(b/299462337) Remove when the infrastructure is ready.
- if ((Build.IS_ENG || Build.IS_USERDEBUG)
- && SystemProperties.getBoolean("persist.sys." + flagName, false)) {
- return true;
- }
- try {
- return flagFunction.get();
- } catch (Throwable ex) {
- if (DEBUG) {
- Slog.i(TAG, "Flags not ready yet. Return false for " + flagName, ex);
- }
- return false;
- }
- }
+ private final FlagState mConnectedDisplayManagementFlagState = new FlagState(
+ Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT,
+ Flags::enableConnectedDisplayManagement);
+
+ private final FlagState mNbmControllerFlagState = new FlagState(
+ Flags.FLAG_ENABLE_NBM_CONTROLLER,
+ Flags::enableNbmController);
- // TODO(b/297159910): Simplify using READ-ONLY flags when available.
/** Returns whether connected display management is enabled or not. */
public boolean isConnectedDisplayManagementEnabled() {
- if (mIsConnectedDisplayManagementEnabledSet) {
+ return mConnectedDisplayManagementFlagState.isEnabled();
+ }
+
+ /** Returns whether hdr clamper is enabled on not*/
+ public boolean isNbmControllerEnabled() {
+ return mNbmControllerFlagState.isEnabled();
+ }
+
+ private static class FlagState {
+
+ private final String mName;
+
+ private final Supplier<Boolean> mFlagFunction;
+ private boolean mEnabledSet;
+ private boolean mEnabled;
+
+ private FlagState(String name, Supplier<Boolean> flagFunction) {
+ mName = name;
+ mFlagFunction = flagFunction;
+ }
+
+ // TODO(b/297159910): Simplify using READ-ONLY flags when available.
+ private boolean isEnabled() {
+ if (mEnabledSet) {
+ if (DEBUG) {
+ Slog.d(TAG, mName + ": mEnabled. Recall = " + mEnabled);
+ }
+ return mEnabled;
+ }
+ mEnabled = flagOrSystemProperty(mFlagFunction, mName);
if (DEBUG) {
- Slog.d(TAG, "isConnectedDisplayManagementEnabled. Recall = "
- + mIsConnectedDisplayManagementEnabled);
+ Slog.d(TAG, mName + ": mEnabled. Flag value = " + mEnabled);
}
- return mIsConnectedDisplayManagementEnabled;
+ mEnabledSet = true;
+ return mEnabled;
}
- mIsConnectedDisplayManagementEnabled =
- flagOrSystemProperty(Flags::enableConnectedDisplayManagement,
- Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT);
- if (DEBUG) {
- Slog.d(TAG, "isConnectedDisplayManagementEnabled. Flag value = "
- + mIsConnectedDisplayManagementEnabled);
+
+ private boolean flagOrSystemProperty(Supplier<Boolean> flagFunction, String flagName) {
+ // TODO(b/299462337) Remove when the infrastructure is ready.
+ if ((Build.IS_ENG || Build.IS_USERDEBUG)
+ && SystemProperties.getBoolean("persist.sys." + flagName, false)) {
+ return true;
+ }
+ try {
+ return flagFunction.get();
+ } catch (Throwable ex) {
+ if (DEBUG) {
+ Slog.i(TAG, "Flags not ready yet. Return false for " + flagName, ex);
+ }
+ return false;
+ }
}
- mIsConnectedDisplayManagementEnabledSet = true;
- return mIsConnectedDisplayManagementEnabled;
}
}
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 2c3c66e2dfc9..12306b039225 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -9,3 +9,11 @@ flag {
bug: "280739508"
is_fixed_read_only: true
}
+
+flag {
+ name: "enable_nbm_controller"
+ namespace: "display_manager"
+ description: "Feature flag for Normal Brightness Mode Controller"
+ bug: "277877297"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
index a2c8748a9142..2ede56dcecd9 100644
--- a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
+++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
@@ -62,10 +62,10 @@ class GestureMonitorSpyWindow {
mWindowHandle.ownerUid = uid;
mWindowHandle.scaleFactor = 1.0f;
mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
- mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.SPY;
+ mWindowHandle.inputConfig =
+ InputConfig.NOT_FOCUSABLE | InputConfig.SPY | InputConfig.TRUSTED_OVERLAY;
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_GESTURE_MONITOR);
t.setPosition(mInputSurface, 0, 0);
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 62660c4f3c6d..6b399def4d73 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -95,6 +95,7 @@ import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputMonitor;
+import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.PointerIcon;
import android.view.Surface;
@@ -682,6 +683,12 @@ public class InputManagerService extends IInputManager.Stub
return mNative.getKeyCodeForKeyLocation(deviceId, locationKeyCode);
}
+ @Override // Binder call
+ public KeyCharacterMap getKeyCharacterMap(@NonNull String layoutDescriptor) {
+ Objects.requireNonNull(layoutDescriptor, "layoutDescriptor must not be null");
+ return mKeyboardLayoutManager.getKeyCharacterMap(layoutDescriptor);
+ }
+
/**
* Transfer the current touch gesture to the provided window.
*
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index a5162c09f838..0eb620f3f4df 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -63,6 +63,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.InputDevice;
+import android.view.KeyCharacterMap;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
@@ -430,6 +431,23 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
return result[0];
}
+ @AnyThread
+ public KeyCharacterMap getKeyCharacterMap(@NonNull String layoutDescriptor) {
+ final String[] overlay = new String[1];
+ visitKeyboardLayout(layoutDescriptor,
+ (resources, keyboardLayoutResId, layout) -> {
+ try (InputStreamReader stream = new InputStreamReader(
+ resources.openRawResource(keyboardLayoutResId))) {
+ overlay[0] = Streams.readFully(stream);
+ } catch (IOException | Resources.NotFoundException ignored) {
+ }
+ });
+ if (TextUtils.isEmpty(overlay[0])) {
+ return KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ }
+ return KeyCharacterMap.load(layoutDescriptor, overlay[0]);
+ }
+
private void visitAllKeyboardLayouts(KeyboardLayoutVisitor visitor) {
final PackageManager pm = mContext.getPackageManager();
Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_LAYOUTS);
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index dbbbed31df76..7726f40fa2ae 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -57,13 +57,13 @@ final class HandwritingEventReceiverSurface {
InputConfig.NOT_FOCUSABLE
| InputConfig.NOT_TOUCHABLE
| InputConfig.SPY
- | InputConfig.INTERCEPTS_STYLUS;
+ | InputConfig.INTERCEPTS_STYLUS
+ | InputConfig.TRUSTED_OVERLAY;
// Configure the surface to receive stylus events across the entire display.
mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE);
t.setPosition(mInputSurface, 0, 0);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 699e9c8bcfe0..032778c79768 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -116,7 +116,6 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
-import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -124,7 +123,6 @@ import android.view.WindowManager;
import android.view.WindowManager.DisplayImePolicy;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
-import android.view.WindowManagerGlobal;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputBinding;
@@ -2989,9 +2987,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
ConcurrentUtils.waitForFutureNoInterrupt(mImeDrawsImeNavBarResLazyInitFuture,
"Waiting for the lazy init of mImeDrawsImeNavBarRes");
}
+ // Whether the current display has a navigation bar. When this is false (e.g. emulator),
+ // the IME should not draw the IME navigation bar.
+ final boolean hasNavigationBar = mWindowManagerInternal
+ .hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY
+ ? mCurTokenDisplayId : DEFAULT_DISPLAY);
final boolean canImeDrawsImeNavBar =
- mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get()
- && hasNavigationBarOnCurrentDisplay();
+ mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar;
final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked(
InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE);
return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0)
@@ -2999,21 +3001,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
? InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0);
}
- /**
- * Whether the current display has a navigation bar. When this is {@code false} (e.g. emulator),
- * the IME should <em>not</em> draw the IME navigation bar.
- */
- @GuardedBy("ImfLock.class")
- private boolean hasNavigationBarOnCurrentDisplay() {
- final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
- try {
- return wm.hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY
- ? mCurTokenDisplayId : DEFAULT_DISPLAY);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
@GuardedBy("ImfLock.class")
private boolean shouldShowImeSwitcherLocked(int visibility) {
if (!mShowOngoingImeSwitcherForPhones) return false;
diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
index e31a7fc5d5ff..198040378dc6 100644
--- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java
@@ -508,7 +508,11 @@ class LegacyBluetoothRouteController implements BluetoothRouteController {
case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
if (device != null) {
- addActiveRoute(mBluetoothRoutes.get(device.getAddress()));
+ if (DEBUG) {
+ Log.d(TAG, "Setting active a2dp devices. device=" + device);
+ }
+
+ addActiveDevices(device);
}
notifyBluetoothRoutesUpdated();
break;
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 69e4a5b01aa0..13d166294603 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -932,6 +932,7 @@ public final class MediaProjectionManagerService extends SystemService
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
+ Slog.v(TAG, "Start the token instance " + this);
// Cache result of calling into ActivityManagerService outside of the lock, to prevent
// deadlock with WindowManagerService.
final boolean hasFGS = mActivityManagerInternal.hasRunningForegroundService(
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6a5b9d8c9edd..bada8165766c 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2826,9 +2826,6 @@ public class NotificationManagerService extends SystemService {
mAssistants.onBootPhaseAppsCanStart();
mConditionProviders.onBootPhaseAppsCanStart();
mHistoryManager.onBootPhaseAppsCanStart();
- if (expireBitmaps()) {
- NotificationBitmapJobService.scheduleJob(getContext());
- }
registerDeviceConfigChange();
migrateDefaultNAS();
maybeShowInitialReviewPermissionsNotification();
@@ -2837,6 +2834,10 @@ public class NotificationManagerService extends SystemService {
} else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) {
mPreferencesHelper.updateFixedImportance(mUm.getUsers());
mPreferencesHelper.migrateNotificationPermissions(mUm.getUsers());
+ } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+ if (expireBitmaps()) {
+ NotificationBitmapJobService.scheduleJob(getContext());
+ }
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index a700d3235ea4..71562dc1ed86 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1022,6 +1022,8 @@ public class ZenModeHelper {
@VisibleForTesting
protected void setZenModeSetting(int zen) {
Global.putInt(mContext.getContentResolver(), Global.ZEN_MODE, zen);
+ ZenLog.traceSetZenMode(Global.getInt(mContext.getContentResolver(), Global.ZEN_MODE, -1),
+ "updated setting");
showZenUpgradeNotification(zen);
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index ffa2af1e2f81..316c4aca1891 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1024,7 +1024,7 @@ public class ComputerEngine implements Computer {
if ("android".equals(packageName) || "system".equals(packageName)) {
return androidApplication();
}
- if ((flags & MATCH_KNOWN_PACKAGES) != 0) {
+ if ((flags & (MATCH_KNOWN_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0) {
// Already generates the external package name
return generateApplicationInfoFromSettings(packageName,
flags, filterCallingUid, userId);
@@ -1518,7 +1518,6 @@ public class ComputerEngine implements Computer {
pi.sharedUserId = (sharedUser != null) ? sharedUser.getName() : null;
pi.firstInstallTime = state.getFirstInstallTimeMillis();
pi.lastUpdateTime = ps.getLastUpdateTime();
- pi.isArchived = isArchived(state);
ApplicationInfo ai = new ApplicationInfo();
ai.packageName = ps.getPackageName();
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2ee070653a07..e1e5e6db104a 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -44,6 +44,7 @@ import static android.os.incremental.IncrementalManager.isIncrementalPath;
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.server.pm.DexOptHelper.useArtService;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
@@ -111,6 +112,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.DataLoaderType;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
@@ -1142,15 +1144,18 @@ final class InstallPackageHelper {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
final ParsedPackage parsedPackage;
+ final ArchivedPackageParcel archivedPackage;
try (PackageParser2 pp = mPm.mInjector.getPreparingPackageParser()) {
if (request.getPackageLite() == null || !request.isArchived()) {
// TODO: pass packageLite from install request instead of reparsing the package
parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
+ archivedPackage = null;
} else {
// Archived install mode, no APK.
parsedPackage = pp.parsePackageFromPackageLite(request.getPackageLite(),
parseFlags);
+ archivedPackage = request.getPackageLite().getArchivedPackage();
}
} catch (PackageManagerException e) {
throw new PrepareFailure("Failed parse during installPackageLI", e);
@@ -1833,7 +1838,7 @@ final class InstallPackageHelper {
shouldCloseFreezerBeforeReturn = false;
request.setPrepareResult(replace, targetScanFlags, targetParseFlags,
- oldPackageState, parsedPackage,
+ oldPackageState, parsedPackage, archivedPackage,
replace /* clearCodeCache */, sysPkg, ps, disabledPs);
} finally {
request.setFreezer(freezer);
@@ -2164,6 +2169,9 @@ final class InstallPackageHelper {
}
}
if (installRequest.getReturnCode() == PackageManager.INSTALL_SUCCEEDED) {
+ mPm.createArchiveStateIfNeeded(ps,
+ installRequest.getArchivedPackage(),
+ installRequest.getNewUsers());
mPm.updateSequenceNumberLP(ps, installRequest.getNewUsers());
mPm.updateInstantAppInstallerLocked(packageName);
}
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 3a6d423a574b..ff347acdfd96 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.INSTALL_REASON_UNKNOWN;
import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
import static android.os.Process.INVALID_UID;
+
import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP;
import static com.android.server.pm.PackageManagerService.TAG;
@@ -29,6 +30,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.app.AppOpsManager;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.DataLoaderType;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.PackageInstaller;
@@ -77,6 +79,8 @@ final class InstallRequest {
/** parsed package to be scanned */
@Nullable
private ParsedPackage mParsedPackage;
+ @Nullable
+ private ArchivedPackageParcel mArchivedPackage;
private boolean mClearCodeCache;
private boolean mSystem;
@Nullable
@@ -189,6 +193,7 @@ final class InstallRequest {
}
mInstallArgs = null;
mParsedPackage = parsedPackage;
+ mArchivedPackage = null;
mParseFlags = parseFlags;
mScanFlags = scanFlags;
mScanResult = scanResult;
@@ -448,6 +453,9 @@ final class InstallRequest {
return mParsedPackage;
}
+ @Nullable
+ public ArchivedPackageParcel getArchivedPackage() { return mArchivedPackage; }
+
@ParsingPackageUtils.ParseFlags
public int getParseFlags() {
return mParseFlags;
@@ -753,7 +761,8 @@ final class InstallRequest {
public void setPrepareResult(boolean replace, int scanFlags,
int parseFlags, PackageState existingPackageState,
- ParsedPackage packageToScan, boolean clearCodeCache, boolean system,
+ ParsedPackage packageToScan, ArchivedPackageParcel archivedPackage,
+ boolean clearCodeCache, boolean system,
PackageSetting originalPs, PackageSetting disabledPs) {
mReplace = replace;
mScanFlags = scanFlags;
@@ -761,6 +770,7 @@ final class InstallRequest {
mExistingPackageName =
existingPackageState != null ? existingPackageState.getPackageName() : null;
mParsedPackage = packageToScan;
+ mArchivedPackage = archivedPackage;
mClearCodeCache = clearCodeCache;
mSystem = system;
mOriginalPs = originalPs;
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 64cdca3b5784..e8be748a85d1 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -31,12 +31,15 @@ import android.app.BroadcastOptions;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.ArchivedActivityParcel;
+import android.content.pm.ArchivedPackageParcel;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -56,6 +59,7 @@ import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserStateInternal;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -158,7 +162,8 @@ public class PackageArchiver {
String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
verifyInstaller(responsibleInstallerPackage);
- List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps, userId);
+ List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps.getPackageName(),
+ userId);
final CompletableFuture<ArchiveState> archiveState = new CompletableFuture<>();
mPm.mHandler.post(() -> {
try {
@@ -172,13 +177,34 @@ public class PackageArchiver {
return archiveState;
}
- private ArchiveState createArchiveStateInternal(String packageName, int userId,
+ static ArchiveState createArchiveState(@NonNull ArchivedPackageParcel archivedPackage,
+ int userId, String installerPackage) {
+ try {
+ var packageName = archivedPackage.packageName;
+ var mainActivities = archivedPackage.archivedActivities;
+ List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.length);
+ for (int i = 0, size = mainActivities.length; i < size; ++i) {
+ var mainActivity = mainActivities[i];
+ Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i);
+ ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
+ mainActivity.title, iconPath, null);
+ archiveActivityInfos.add(activityInfo);
+ }
+
+ return new ArchiveState(archiveActivityInfos, installerPackage);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to create archive state", e);
+ return null;
+ }
+ }
+
+ ArchiveState createArchiveStateInternal(String packageName, int userId,
List<LauncherActivityInfo> mainActivities, String installerPackage)
throws IOException {
- List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>();
- for (int i = 0; i < mainActivities.size(); i++) {
+ List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
+ for (int i = 0, size = mainActivities.size(); i < size; i++) {
LauncherActivityInfo mainActivity = mainActivities.get(i);
- Path iconPath = storeIcon(packageName, mainActivity, userId);
+ Path iconPath = storeIcon(packageName, mainActivity, userId, i);
ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
mainActivity.getLabel().toString(), iconPath, null);
archiveActivityInfos.add(activityInfo);
@@ -188,17 +214,30 @@ public class PackageArchiver {
}
// TODO(b/298452477) Handle monochrome icons.
+ private static Path storeIconForParcel(String packageName, ArchivedActivityParcel mainActivity,
+ @UserIdInt int userId, int index) throws IOException {
+ if (mainActivity.iconBitmap == null) {
+ return null;
+ }
+ File iconsDir = createIconsDir(userId);
+ File iconFile = new File(iconsDir, packageName + "-" + index + ".png");
+ try (FileOutputStream out = new FileOutputStream(iconFile)) {
+ out.write(mainActivity.iconBitmap);
+ out.flush();
+ }
+ return iconFile.toPath();
+ }
+
@VisibleForTesting
Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
- @UserIdInt int userId)
- throws IOException {
+ @UserIdInt int userId, int index) throws IOException {
int iconResourceId = mainActivity.getActivityInfo().getIconResource();
if (iconResourceId == 0) {
// The app doesn't define an icon. No need to store anything.
return null;
}
File iconsDir = createIconsDir(userId);
- File iconFile = new File(iconsDir, packageName + "-" + mainActivity.getName() + ".png");
+ File iconFile = new File(iconsDir, packageName + "-" + index + ".png");
Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0));
try (FileOutputStream out = new FileOutputStream(iconFile)) {
// Note: Quality is ignored for PNGs.
@@ -228,6 +267,9 @@ public class PackageArchiver {
*/
public boolean verifySupportsUnarchival(String installerPackage) {
// TODO(b/278553670) Check if installerPackage supports unarchival.
+ if (TextUtils.isEmpty(installerPackage)) {
+ return false;
+ }
return true;
}
@@ -265,6 +307,46 @@ public class PackageArchiver {
mPm.mHandler.post(() -> unarchiveInternal(packageName, userHandle, installerPackage));
}
+ /**
+ * Returns the icon of an archived app. This is the icon of the main activity of the app.
+ *
+ * <p> The icon is returned without any treatment/overlay. In the rare case the app had multiple
+ * launcher activities, only one of the icons is returned arbitrarily.
+ */
+ public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(user);
+
+ Computer snapshot = mPm.snapshotComputer();
+ int callingUid = Binder.getCallingUid();
+ int userId = user.getIdentifier();
+ PackageStateInternal ps;
+ try {
+ ps = getPackageState(packageName, snapshot, callingUid, userId);
+ snapshot.enforceCrossUserPermission(callingUid, userId, true, false,
+ "getArchivedAppIcon");
+ verifyArchived(ps, userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new ParcelableException(e);
+ }
+
+ List<ArchiveActivityInfo> activityInfos = ps.getUserStateOrDefault(
+ userId).getArchiveState().getActivityInfos();
+ if (activityInfos.size() == 0) {
+ return null;
+ }
+
+ // TODO(b/298452477) Handle monochrome icons.
+ // In the rare case the archived app defined more than two launcher activities, we choose
+ // the first one arbitrarily.
+ return decodeIcon(activityInfos.get(0));
+ }
+
+ @VisibleForTesting
+ Bitmap decodeIcon(ArchiveActivityInfo archiveActivityInfo) {
+ return BitmapFactory.decodeFile(archiveActivityInfo.getIconBitmap().toString());
+ }
+
private void verifyArchived(PackageStateInternal ps, int userId)
throws PackageManager.NameNotFoundException {
PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
@@ -310,16 +392,16 @@ public class PackageArchiver {
/* initialExtras= */ null);
}
- private List<LauncherActivityInfo> getLauncherActivityInfos(PackageStateInternal ps,
+ List<LauncherActivityInfo> getLauncherActivityInfos(String packageName,
int userId) throws PackageManager.NameNotFoundException {
List<LauncherActivityInfo> mainActivities =
Binder.withCleanCallingIdentity(() -> getLauncherApps().getActivityList(
- ps.getPackageName(),
+ packageName,
new UserHandle(userId)));
if (mainActivities.isEmpty()) {
throw new PackageManager.NameNotFoundException(
TextUtils.formatSimple("The app %s does not have a main activity.",
- ps.getPackageName()));
+ packageName));
}
return mainActivities;
@@ -340,7 +422,7 @@ public class PackageArchiver {
return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS;
}
- private String getResponsibleInstallerPackage(PackageStateInternal ps) {
+ static String getResponsibleInstallerPackage(PackageStateInternal ps) {
return TextUtils.isEmpty(ps.getInstallSource().mUpdateOwnerPackageName)
? ps.getInstallSource().mInstallerPackageName
: ps.getInstallSource().mUpdateOwnerPackageName;
@@ -423,7 +505,7 @@ public class PackageArchiver {
}
}
- private File createIconsDir(@UserIdInt int userId) throws IOException {
+ private static File createIconsDir(@UserIdInt int userId) throws IOException {
File iconsDir = getIconsDir(userId);
if (!iconsDir.isDirectory()) {
iconsDir.delete();
@@ -436,7 +518,7 @@ public class PackageArchiver {
return iconsDir;
}
- private File getIconsDir(int userId) {
+ private static File getIconsDir(int userId) {
return new File(Environment.getDataSystemCeDirectory(userId), ARCHIVE_ICONS_DIR);
}
@@ -462,4 +544,90 @@ public class PackageArchiver {
drawable.draw(canvas);
return bitmap;
}
+
+ private static byte[] bytesFromBitmapFile(Path path) throws IOException {
+ if (path == null) {
+ return null;
+ }
+ // Technically we could just read the bytes, but we want to be sure we store the
+ // right format.
+ return bytesFromBitmap(BitmapFactory.decodeFile(path.toString()));
+ }
+
+ private static byte[] bytesFromBitmap(Bitmap bitmap) throws IOException {
+ if (bitmap == null) {
+ return null;
+ }
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream(
+ bitmap.getByteCount())) {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
+ return baos.toByteArray();
+ }
+ }
+
+ /**
+ * Creates serializable archived activities from existing ArchiveState.
+ */
+ static ArchivedActivityParcel[] createArchivedActivities(ArchiveState archiveState)
+ throws IOException {
+ var infos = archiveState.getActivityInfos();
+ if (infos == null || infos.isEmpty()) {
+ throw new IllegalArgumentException("No activities in archive state");
+ }
+
+ List<ArchivedActivityParcel> activities = new ArrayList<>(infos.size());
+ for (int i = 0, size = infos.size(); i < size; ++i) {
+ var info = infos.get(i);
+ if (info == null) {
+ continue;
+ }
+ var archivedActivity = new ArchivedActivityParcel();
+ archivedActivity.title = info.getTitle();
+ archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap());
+ archivedActivity.monochromeIconBitmap = bytesFromBitmapFile(
+ info.getMonochromeIconBitmap());
+ activities.add(archivedActivity);
+ }
+
+ if (activities.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Failed to extract title and icon of main activities");
+ }
+
+ return activities.toArray(new ArchivedActivityParcel[activities.size()]);
+ }
+
+ /**
+ * Creates serializable archived activities from launcher activities.
+ */
+ static ArchivedActivityParcel[] createArchivedActivities(List<LauncherActivityInfo> infos)
+ throws IOException {
+ if (infos == null || infos.isEmpty()) {
+ throw new IllegalArgumentException("No launcher activities");
+ }
+
+ List<ArchivedActivityParcel> activities = new ArrayList<>(infos.size());
+ for (int i = 0, size = infos.size(); i < size; ++i) {
+ var info = infos.get(i);
+ if (info == null) {
+ continue;
+ }
+ var archivedActivity = new ArchivedActivityParcel();
+ archivedActivity.title = info.getLabel().toString();
+ archivedActivity.iconBitmap =
+ info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap(
+ drawableToBitmap(info.getIcon(/* density= */ 0)));
+ // TODO(b/298452477) Handle monochrome icons.
+ archivedActivity.monochromeIconBitmap = null;
+ activities.add(archivedActivity);
+ }
+
+ if (activities.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Failed to extract title and icon of main activities");
+ }
+
+ return activities.toArray(new ArchivedActivityParcel[activities.size()]);
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2e9da099b86c..512d338b0b48 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -40,6 +40,7 @@ import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.O_WRONLY;
+
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.util.XmlUtils.readBitmapAttribute;
import static com.android.internal.util.XmlUtils.readByteArrayAttribute;
@@ -1165,11 +1166,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
throw new IllegalArgumentException(
"Archived installation can only use Streaming System DataLoader.");
}
- if (!TextUtils.isEmpty(params.appPackageName) && !isArchivedInstallationAllowed(
- params.appPackageName)) {
- throw new IllegalArgumentException(
- "Archived installation of this package is not allowed.");
- }
}
}
@@ -1548,6 +1544,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
var archPkg = metadata.getArchivedPackage();
+ if (archPkg == null) {
+ throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
+ "Metadata does not contain ArchivedPackage: " + file);
+ }
if (archPkg.packageName == null || archPkg.signingDetails == null) {
throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE,
"ArchivedPackage does not contain required info: " + file);
@@ -3395,8 +3395,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
"Archived installation of this package is not allowed.");
}
- if (!isInstalledByAdb(getInstallSource().mInitiatingPackageName)
- && !mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival(
+ if (!mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival(
getInstallSource().mInstallerPackageName)) {
throw new PackageManagerException(
PackageManager.INSTALL_FAILED_SESSION_INVALID,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d23dcbc5f18e..864ed323697c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -36,6 +36,7 @@ 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 android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
+
import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
import static com.android.server.pm.DexOptHelper.useArtService;
@@ -116,11 +117,7 @@ import android.content.pm.UserPackage;
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.overlay.OverlayPaths;
-import android.content.pm.parsing.ApkLite;
-import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -228,6 +225,7 @@ import com.android.server.pm.permission.LegacyPermissionSettings;
import com.android.server.pm.permission.PermissionManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.ArchiveState;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageUserState;
@@ -1438,6 +1436,92 @@ public class PackageManagerService implements PackageSender, TestUtilityService
return extras;
}
+ ArchivedPackageParcel getArchivedPackageInternal(@NonNull String packageName, int userId) {
+ Objects.requireNonNull(packageName);
+ int binderUid = Binder.getCallingUid();
+
+ Computer snapshot = snapshotComputer();
+ snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
+ "getArchivedPackage");
+
+ ArchivedPackageParcel archPkg = new ArchivedPackageParcel();
+ archPkg.packageName = packageName;
+
+ ArchiveState archiveState;
+ synchronized (mLock) {
+ PackageSetting ps = mSettings.getPackageLPr(packageName);
+ if (ps == null) {
+ return null;
+ }
+ var psi = ps.getUserStateOrDefault(userId);
+ archiveState = psi.getArchiveState();
+ if (archiveState == null && !psi.isInstalled()) {
+ return null;
+ }
+
+ archPkg.signingDetails = ps.getSigningDetails();
+
+ long longVersionCode = ps.getVersionCode();
+ archPkg.versionCodeMajor = (int) (longVersionCode >> 32);
+ archPkg.versionCode = (int) longVersionCode;
+
+ // TODO(b/297916136): extract target sdk version.
+ archPkg.targetSdkVersion = MIN_INSTALLABLE_TARGET_SDK;
+
+ // These get translated in flags important for user data management.
+ archPkg.defaultToDeviceProtectedStorage = String.valueOf(
+ ps.isDefaultToDeviceProtectedStorage());
+ archPkg.requestLegacyExternalStorage = String.valueOf(
+ ps.isRequestLegacyExternalStorage());
+ archPkg.userDataFragile = String.valueOf(ps.isUserDataFragile());
+ }
+
+ try {
+ if (archiveState != null) {
+ archPkg.archivedActivities = PackageArchiver.createArchivedActivities(
+ archiveState);
+ } else {
+ var mainActivities =
+ mInstallerService.mPackageArchiver.getLauncherActivityInfos(packageName,
+ userId);
+ archPkg.archivedActivities = PackageArchiver.createArchivedActivities(
+ mainActivities);
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Package does not have a main activity", e);
+ }
+
+ return archPkg;
+ }
+
+ void createArchiveStateIfNeeded(PackageSetting pkgSetting, ArchivedPackageParcel archivePackage,
+ int[] userIds) {
+ if (pkgSetting == null || archivePackage == null
+ || archivePackage.archivedActivities == null || userIds == null
+ || userIds.length == 0) {
+ return;
+ }
+
+ String responsibleInstallerPackage = PackageArchiver.getResponsibleInstallerPackage(
+ pkgSetting);
+ // TODO(b/278553670) Check if responsibleInstallerPackage supports unarchival.
+ if (TextUtils.isEmpty(responsibleInstallerPackage)) {
+ Slog.e(TAG, "Can't create archive state: responsible installer is empty");
+ return;
+ }
+ for (int userId : userIds) {
+ var archiveState = PackageArchiver.createArchiveState(archivePackage, userId,
+ responsibleInstallerPackage);
+ if (archiveState == null) {
+ continue;
+ }
+ pkgSetting
+ .modifyUserState(userId)
+ .setArchiveState(archiveState);
+ }
+ }
+
+
void scheduleWriteSettings() {
// We normally invalidate when we write settings, but in cases where we delay and
// coalesce settings writes, this strategy would have us invalidate the cache too late.
@@ -6301,35 +6385,13 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
@Override
- public ArchivedPackageParcel getArchivedPackage(String apkPath) {
- ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
- ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(),
- new File(apkPath), ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
- if (result.isError()) {
- throw new IllegalArgumentException(result.getErrorMessage(), result.getException());
- }
- final ApkLite apk = result.getResult();
-
- ArchivedPackageParcel archPkg = new ArchivedPackageParcel();
- archPkg.packageName = apk.getPackageName();
- archPkg.signingDetails = apk.getSigningDetails();
-
- archPkg.versionCodeMajor = apk.getVersionCodeMajor();
- archPkg.versionCode = apk.getVersionCode();
-
- archPkg.targetSdkVersion = apk.getTargetSdkVersion();
-
- // These get translated in flags important for user data management.
- archPkg.backupAllowed = String.valueOf(apk.isBackupAllowed());
- archPkg.defaultToDeviceProtectedStorage = String.valueOf(
- apk.isDefaultToDeviceProtectedStorage());
- archPkg.requestLegacyExternalStorage = String.valueOf(
- apk.isRequestLegacyExternalStorage());
- archPkg.userDataFragile = String.valueOf(apk.isUserDataFragile());
- archPkg.clearUserDataOnFailedRestoreAllowed = String.valueOf(
- apk.isClearUserDataOnFailedRestoreAllowed());
+ public ArchivedPackageParcel getArchivedPackage(@NonNull String packageName, int userId) {
+ return getArchivedPackageInternal(packageName, userId);
+ }
- return archPkg;
+ @Override
+ public Bitmap getArchivedAppIcon(@NonNull String packageName, @NonNull UserHandle user) {
+ return mInstallerService.mPackageArchiver.getArchivedAppIcon(packageName, user);
}
/**
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 8d8208599998..1b30c4b82d82 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -26,6 +26,7 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
import static android.content.pm.PackageManager.RESTRICTION_HIDE_NOTIFICATIONS;
import static android.content.pm.PackageManager.RESTRICTION_NONE;
+
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -82,9 +83,9 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
-import android.os.Environment;
import android.os.IBinder;
import android.os.IUserManager;
+import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.os.PersistableBundle;
@@ -130,6 +131,7 @@ import dalvik.system.DexFile;
import libcore.io.IoUtils;
import libcore.io.Streams;
+import libcore.util.HexEncoding;
import java.io.BufferedReader;
import java.io.File;
@@ -248,8 +250,6 @@ class PackageManagerShellCommand extends ShellCommand {
return runStreamingInstall();
case "install-incremental":
return runIncrementalInstall();
- case "install-archived":
- return runArchivedInstall();
case "install-abandon":
case "install-destroy":
return runInstallAbandon();
@@ -277,6 +277,10 @@ class PackageManagerShellCommand extends ShellCommand {
return runUninstall();
case "clear":
return runClear();
+ case "get-archived-package-metadata":
+ return runGetArchivedPackageMetadata();
+ case "install-archived":
+ return runArchivedInstall();
case "enable":
return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
case "disable":
@@ -1808,6 +1812,56 @@ class PackageManagerShellCommand extends ShellCommand {
return doRemoveSplits(sessionId, splitNames, true /*logSuccess*/);
}
+ private int runGetArchivedPackageMetadata() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ int userId = UserHandle.USER_CURRENT;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "--user":
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ final String packageName = getNextArg();
+ if (packageName == null) {
+ pw.println("Error: package name not specified");
+ return 1;
+ }
+ final int translatedUserId = translateUserId(userId, UserHandle.USER_NULL,
+ "runGetArchivedPackageMetadata");
+
+ try {
+ var archivedPackage = mInterface.getArchivedPackage(packageName, translatedUserId);
+ if (archivedPackage == null) {
+ pw.write("Package not found " + packageName);
+ return -1;
+ }
+
+ Parcel parcel = Parcel.obtain();
+ byte[] bytes;
+ try {
+ parcel.writeParcelable(archivedPackage, 0);
+ bytes = parcel.marshall();
+ } finally {
+ parcel.recycle();
+ }
+
+ String encoded = HexEncoding.encodeToString(bytes);
+ pw.write(encoded);
+ } catch (Exception e) {
+ getErrPrintWriter().println("Failed to get archived package, reason: " + e);
+ pw.println("Failure [failed to get archived package], reason: " + e);
+ return -1;
+ }
+ return 0;
+ }
+
private int runInstallExisting() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
int userId = UserHandle.USER_CURRENT;
@@ -4146,28 +4200,24 @@ class PackageManagerShellCommand extends ShellCommand {
throw new IllegalArgumentException("Error: Can't open file: " + inPath);
}
- File tmpFile = null;
+ final String encoded;
final ParcelFileDescriptor fd = fdWithSize.first;
+ final int size = (int) (long) fdWithSize.second;
try (InputStream inStream = new AutoCloseInputStream(fd)) {
- final long identity = Binder.clearCallingIdentity();
- try {
- File tmpStagingDir = Environment.getDataAppDirectory(null);
- tmpFile = new File(tmpStagingDir, "tmdl" + RANDOM.nextInt() + ".tmp");
-
- try (OutputStream outStream = new FileOutputStream(tmpFile)) {
- Streams.copy(inStream, outStream);
- }
-
- return mInterface.getArchivedPackage(tmpFile.getAbsolutePath());
- } finally {
- if (tmpFile != null) {
- tmpFile.delete();
- }
- Binder.restoreCallingIdentity(identity);
- }
+ byte[] bytes = new byte[size];
+ Streams.readFully(inStream, bytes);
+ encoded = new String(bytes);
} catch (IOException e) {
- throw new IllegalArgumentException("Error: Can't stage file: " + inPath, e);
+ throw new IllegalArgumentException("Error: Can't load archived package from: " + inPath,
+ e);
}
+
+ var result = Metadata.readArchivedPackageParcel(HexEncoding.decode(encoded));
+ if (result == null) {
+ throw new IllegalArgumentException(
+ "Error: Can't parse archived package from: " + inPath);
+ }
+ return result;
}
private void processArgForLocalFile(String arg, PackageInstaller.Session session,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index fbe5a51587fc..9e7f04351327 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -166,16 +166,7 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService {
/** @hide */
@VisibleForTesting
public static Metadata forArchived(ArchivedPackageParcel archivedPackage) {
- Parcel parcel = Parcel.obtain();
- byte[] bytes;
- try {
- parcel.writeParcelable(archivedPackage, 0);
- bytes = parcel.marshall();
- } finally {
- parcel.recycle();
- }
-
- return new Metadata(ARCHIVED, bytes, null);
+ return new Metadata(ARCHIVED, writeArchivedPackageParcel(archivedPackage), null);
}
static Metadata forDataOnlyStreaming(String fileId) {
@@ -270,11 +261,14 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService {
if (getMode() != ARCHIVED) {
throw new IllegalStateException("Not an archived package metadata.");
}
+ return readArchivedPackageParcel(this.mData);
+ }
+ static ArchivedPackageParcel readArchivedPackageParcel(byte[] bytes) {
Parcel parcel = Parcel.obtain();
ArchivedPackageParcel result;
try {
- parcel.unmarshall(this.mData, 0, this.mData.length);
+ parcel.unmarshall(bytes, 0, bytes.length);
parcel.setDataPosition(0);
result = parcel.readParcelable(ArchivedPackageParcel.class.getClassLoader());
} finally {
@@ -282,6 +276,16 @@ public class PackageManagerShellCommandDataLoader extends DataLoaderService {
}
return result;
}
+
+ static byte[] writeArchivedPackageParcel(ArchivedPackageParcel archivedPackage) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeParcelable(archivedPackage, 0);
+ return parcel.marshall();
+ } finally {
+ parcel.recycle();
+ }
+ }
}
private static class DataLoader implements DataLoaderService.DataLoader {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 1884caf6da8b..2e6006465bd9 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -654,6 +654,16 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return (getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0;
}
+ public boolean isRequestLegacyExternalStorage() {
+ return (getPrivateFlags() & ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE)
+ != 0;
+ }
+
+ public boolean isUserDataFragile() {
+ return (getPrivateFlags() & ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA)
+ != 0;
+ }
+
public SigningDetails getSigningDetails() {
return signatures.mSigningDetails;
}
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 4eceb7738836..cc8e62409597 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -378,7 +378,6 @@ public class PackageInfoUtils {
ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT)
| flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD)
| flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN);
-
if ((flags & PackageManager.FILTER_OUT_QUARANTINED_COMPONENTS) != 0
&& state.isQuarantined()) {
ai.enabled = false;
@@ -402,6 +401,14 @@ public class PackageInfoUtils {
ai.resourceDirs = overlayPaths.getResourceDirs().toArray(new String[0]);
ai.overlayPaths = overlayPaths.getOverlayPaths().toArray(new String[0]);
}
+ ai.isArchived = isArchived(state);
+ }
+
+ // TODO(b/288142708) Check for userState.isInstalled() here once this bug is fixed.
+ // If an app has isInstalled() == true - it should not be filtered above in any case, currently
+ // it is.
+ private static boolean isArchived(PackageUserState userState) {
+ return userState.getArchiveState() != null;
}
@Nullable
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index c44b8852447a..bf206537d912 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1490,12 +1490,9 @@ final class DefaultPermissionGrantPolicy {
if (dir.isDirectory() && dir.canRead()) {
Collections.addAll(ret, dir.listFiles());
}
- // For IoT devices, we check the oem partition for default permissions for each app.
- if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
- dir = new File(Environment.getOemDirectory(), "etc/default-permissions");
- if (dir.isDirectory() && dir.canRead()) {
- Collections.addAll(ret, dir.listFiles());
- }
+ dir = new File(Environment.getOemDirectory(), "etc/default-permissions");
+ if (dir.isDirectory() && dir.canRead()) {
+ Collections.addAll(ret, dir.listFiles());
}
return ret.isEmpty() ? null : ret.toArray(new File[0]);
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 812e22833aba..f14941b2d9c8 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -28,6 +28,7 @@ import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_
import static android.os.Build.VERSION_CODES.DONUT;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
import android.annotation.AnyRes;
@@ -509,24 +510,33 @@ public class ParsingPackageUtils {
/* Set the global "on SD card" flag */
.setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0);
+ var archivedPackage = lite.getArchivedPackage();
+ if (archivedPackage == null) {
+ return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
+ "archivePackage is missing");
+ }
+
// parseBaseAppBasicFlags
pkg
// Default true
- .setBackupAllowed(lite.isBackupAllowed())
+ .setBackupAllowed(true)
.setClearUserDataAllowed(true)
- .setClearUserDataOnFailedRestoreAllowed(
- lite.isClearUserDataOnFailedRestoreAllowed())
+ .setClearUserDataOnFailedRestoreAllowed(true)
.setAllowNativeHeapPointerTagging(true)
.setEnabled(true)
.setExtractNativeLibrariesRequested(true)
// targetSdkVersion gated
.setAllowAudioPlaybackCapture(targetSdk >= Build.VERSION_CODES.Q)
.setHardwareAccelerated(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
- .setRequestLegacyExternalStorage(lite.isRequestLegacyExternalStorage())
+ .setRequestLegacyExternalStorage(
+ XmlUtils.convertValueToBoolean(archivedPackage.requestLegacyExternalStorage,
+ targetSdk < Build.VERSION_CODES.Q))
.setCleartextTrafficAllowed(targetSdk < Build.VERSION_CODES.P)
// Default false
- .setDefaultToDeviceProtectedStorage(lite.isDefaultToDeviceProtectedStorage())
- .setUserDataFragile(lite.isUserDataFragile())
+ .setDefaultToDeviceProtectedStorage(XmlUtils.convertValueToBoolean(
+ archivedPackage.defaultToDeviceProtectedStorage, false))
+ .setUserDataFragile(
+ XmlUtils.convertValueToBoolean(archivedPackage.userDataFragile, false))
// Ints
.setCategory(ApplicationInfo.CATEGORY_UNDEFINED)
// Floats Default 0f
diff --git a/services/core/java/com/android/server/security/TEST_MAPPING b/services/core/java/com/android/server/security/TEST_MAPPING
index 673456f889e2..29d52fff3eff 100644
--- a/services/core/java/com/android/server/security/TEST_MAPPING
+++ b/services/core/java/com/android/server/security/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "CtsSecurityTestCases",
"options": [
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index c526016544a6..a5c0fb3c46af 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -1454,6 +1454,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde
boolean hasDesiredDemuxCap = request.desiredFilterTypes
!= DemuxFilterMainType.UNDEFINED;
int smallestNumOfSupportedCaps = Integer.SIZE + 1;
+ int smallestNumOfSupportedCapsInUse = Integer.SIZE + 1;
for (DemuxResource dr : getDemuxResources().values()) {
if (!hasDesiredDemuxCap || dr.hasSufficientCaps(request.desiredFilterTypes)) {
if (!dr.isInUse()) {
@@ -1476,12 +1477,18 @@ public class TunerResourceManagerService extends SystemService implements IBinde
currentLowestPriority = priority;
isRequestFromSameProcess = (requestClient.getProcessId()
== (getClientProfile(dr.getOwnerClientId())).getProcessId());
+
+ // reset the smallest caps when lower priority resource is found
+ smallestNumOfSupportedCapsInUse = numOfSupportedCaps;
+
shouldUpdate = true;
- }
- // update smallest caps
- if (smallestNumOfSupportedCaps > numOfSupportedCaps) {
- smallestNumOfSupportedCaps = numOfSupportedCaps;
- shouldUpdate = true;
+ } else {
+ // This is the case when the priority is the same as previously found
+ // one. Update smallest caps when priority.
+ if (smallestNumOfSupportedCapsInUse > numOfSupportedCaps) {
+ smallestNumOfSupportedCapsInUse = numOfSupportedCaps;
+ shouldUpdate = true;
+ }
}
if (shouldUpdate) {
inUseLowestPriorityDrHandle = dr.getHandle();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3c2d0fcb8cdf..4c525e902b88 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -256,6 +256,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
|| event != CLOSE_WRITE // includes the MOVED_TO case
|| wallpaper.imageWallpaperPending;
+ if (isMigration) {
+ // When separate lock screen engine is supported, migration will be handled by
+ // WallpaperDestinationChangeHandler.
+ return;
+ }
+ if (!(sysWallpaperChanged || lockWallpaperChanged)) {
+ return;
+ }
+
if (DEBUG) {
Slog.v(TAG, "Wallpaper file change: evt=" + event
+ " path=" + path
@@ -270,15 +279,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
+ " needsUpdate=" + needsUpdate);
}
- if (isMigration) {
- // When separate lock screen engine is supported, migration will be handled by
- // WallpaperDestinationChangeHandler.
- return;
- }
- if (!(sysWallpaperChanged || lockWallpaperChanged)) {
- return;
- }
-
int notifyColorsWhich = 0;
synchronized (mLock) {
notifyCallbacksLocked(wallpaper);
@@ -3494,15 +3494,24 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
+ /**
+ * Determines if the given component name is the default component. Note: a null name can be
+ * used to represent the default component.
+ * @param name The component name to check.
+ * @return True if the component name matches the default wallpaper component.
+ */
+ private boolean isDefaultComponent(ComponentName name) {
+ return name == null || name.equals(mDefaultWallpaperComponent);
+ }
+
private boolean changingToSame(ComponentName componentName, WallpaperData wallpaper) {
if (wallpaper.connection != null) {
- if (wallpaper.wallpaperComponent == null) {
- if (componentName == null) {
- if (DEBUG) Slog.v(TAG, "changingToSame: still using default");
- // Still using default wallpaper.
- return true;
- }
- } else if (wallpaper.wallpaperComponent.equals(componentName)) {
+ final ComponentName wallpaperName = wallpaper.wallpaperComponent;
+ if (isDefaultComponent(componentName) && isDefaultComponent(wallpaperName)) {
+ if (DEBUG) Slog.v(TAG, "changingToSame: still using default");
+ // Still using default wallpaper.
+ return true;
+ } else if (wallpaperName != null && wallpaperName.equals(componentName)) {
// Changing to same wallpaper.
if (DEBUG) Slog.v(TAG, "same wallpaper");
return true;
@@ -3519,6 +3528,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// Has the component changed?
if (!force && changingToSame(componentName, wallpaper)) {
try {
+ if (DEBUG_LIVE) {
+ Slog.v(TAG, "Changing to the same component, ignoring");
+ }
if (reply != null) reply.sendResult(null);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to send callback", e);
@@ -3737,7 +3749,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mContext.getMainThreadHandler().removeCallbacks(
wallpaper.connection.mTryToRebindRunnable);
- mContext.unbindService(wallpaper.connection);
+ try {
+ mContext.unbindService(wallpaper.connection);
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Error unbinding wallpaper when detaching", e);
+ }
wallpaper.connection = null;
if (wallpaper == mLastWallpaper) {
mLastWallpaper = null;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index dca2b6f8f2af..216369bf56b4 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -565,8 +565,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
boolean forceNewConfig; // force re-create with new config next time
boolean supportsEnterPipOnTaskSwitch; // This flag is set by the system to indicate that the
// activity can enter picture in picture while pausing (only when switching to another task)
+ // The PiP params used when deferring the entering of picture-in-picture.
PictureInPictureParams pictureInPictureArgs = new PictureInPictureParams.Builder().build();
- // The PiP params used when deferring the entering of picture-in-picture.
boolean shouldDockBigOverlays;
int launchCount; // count of launches since last state
long lastLaunchTime; // time of last launch of this activity
@@ -1732,6 +1732,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mAnimatingActivityRegistry = registry;
}
+ boolean canAutoEnterPip() {
+ // beforeStopping=false since the actual pip-ing will take place after startPausing()
+ final boolean activityCanPip = checkEnterPictureInPictureState(
+ "startActivityUnchecked", false /* beforeStopping */);
+
+ // check if this activity is about to auto-enter pip
+ return activityCanPip && pictureInPictureArgs != null
+ && pictureInPictureArgs.isAutoEnterEnabled();
+ }
+
/**
* Sets {@link #mLastParentBeforePip} to the current parent Task, it's caller's job to ensure
* {@link #getTask()} is set before this is called.
@@ -9180,7 +9190,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
getRequestedOverrideWindowingMode() == WINDOWING_MODE_UNDEFINED
? newParentConfig.windowConfiguration.getWindowingMode()
: getRequestedOverrideWindowingMode();
- if (getWindowingMode() != projectedWindowingMode) {
+ if (getWindowingMode() != projectedWindowingMode
+ // Do not collect a pip activity about to enter pinned mode
+ // as a part of WindowOrganizerController#finishTransition().
+ // If not checked the activity might be collected for the wrong transition,
+ // such as a TRANSIT_OPEN transition requested right after TRANSIT_PIP.
+ && !(mWaitForEnteringPinnedMode
+ && mTransitionController.inFinishingTransition(this))) {
mTransitionController.collect(this);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index a5b1132fe499..25c42b4858a4 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -33,9 +33,7 @@ import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
@@ -240,11 +238,6 @@ class ActivityStartInterceptor {
getInterceptorInfo(null /* clearOptionsAnimation */);
for (int i = 0; i < callbacks.size(); i++) {
- final int orderId = callbacks.keyAt(i);
- if (!shouldInterceptActivityLaunch(orderId, interceptorInfo)) {
- continue;
- }
-
final ActivityInterceptorCallback callback = callbacks.valueAt(i);
final ActivityInterceptResult interceptResult = callback.onInterceptActivityLaunch(
interceptorInfo);
@@ -543,11 +536,6 @@ class ActivityStartInterceptor {
ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo(
r::clearOptionsAnimationForSiblings);
for (int i = 0; i < callbacks.size(); i++) {
- final int orderId = callbacks.keyAt(i);
- if (!shouldNotifyOnActivityLaunch(orderId, info)) {
- continue;
- }
-
final ActivityInterceptorCallback callback = callbacks.valueAt(i);
callback.onActivityLaunched(taskInfo, r.info, info);
}
@@ -565,21 +553,4 @@ class ActivityStartInterceptor {
.build();
}
- private boolean shouldInterceptActivityLaunch(
- @ActivityInterceptorCallback.OrderedId int orderId,
- @NonNull ActivityInterceptorCallback.ActivityInterceptorInfo info) {
- if (orderId == MAINLINE_SDK_SANDBOX_ORDER_ID) {
- return info.getIntent() != null && info.getIntent().isSandboxActivity(mServiceContext);
- }
- return true;
- }
-
- private boolean shouldNotifyOnActivityLaunch(
- @ActivityInterceptorCallback.OrderedId int orderId,
- @NonNull ActivityInterceptorCallback.ActivityInterceptorInfo info) {
- if (orderId == MAINLINE_SDK_SANDBOX_ORDER_ID) {
- return info.getIntent() != null && info.getIntent().isSandboxActivity(mServiceContext);
- }
- return true;
- }
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 6999c6a2a5d8..9c9c63f33903 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -313,6 +313,8 @@ import java.util.Set;
public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
private static final String GRAMMATICAL_GENDER_PROPERTY = "persist.sys.grammatical_gender";
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM;
+ private static final String ENABLE_PIP2_IMPLEMENTATION =
+ "persist.wm.debug.enable_pip2_implementation";
static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK;
static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
@@ -3613,6 +3615,28 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
+ /**
+ * Prepare to enter PiP mode after {@link TransitionController#requestStartTransition}.
+ *
+ * @param r activity auto entering pip
+ * @return true if the activity is about to auto-enter pip or is already in pip mode.
+ */
+ boolean prepareAutoEnterPictureAndPictureMode(ActivityRecord r) {
+ // If the activity is already in picture in picture mode, then just return early
+ if (r.inPinnedWindowingMode()) {
+ return true;
+ }
+
+ if (r.canAutoEnterPip() && getTransitionController().getCollectingTransition() != null) {
+ // This will be used later to construct TransitionRequestInfo for Shell to resolve.
+ // It will also be passed into a direct moveActivityToPinnedRootTask() call via
+ // startTransition()
+ getTransitionController().getCollectingTransition().setPipActivity(r);
+ return true;
+ }
+ return false;
+ }
+
boolean enterPictureInPictureMode(@NonNull ActivityRecord r,
@NonNull PictureInPictureParams params, boolean fromClient) {
return enterPictureInPictureMode(r, params, fromClient, false /* isAutoEnter */);
@@ -7183,4 +7207,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
ActivityTaskManagerService.this.unregisterTaskStackListener(listener);
}
}
+
+ static boolean isPip2ExperimentEnabled() {
+ return SystemProperties.getBoolean(ENABLE_PIP2_IMPLEMENTATION, false);
+ }
}
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 7cd07d6f3a5f..b499dad53326 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -83,6 +83,11 @@ final class ContentRecorder implements WindowContainerListener {
@Nullable private Rect mLastRecordedBounds = null;
/**
+ * The last size of the surface mirrored out to.
+ */
+ @Nullable private Point mLastConsumingSurfaceSize = new Point(0, 0);
+
+ /**
* The last configuration orientation.
*/
@Configuration.Orientation
@@ -141,60 +146,64 @@ final class ContentRecorder implements WindowContainerListener {
*/
void onConfigurationChanged(@Configuration.Orientation int lastOrientation) {
// Update surface for MediaProjection, if this DisplayContent is being used for recording.
- if (isCurrentlyRecording() && mLastRecordedBounds != null) {
- // Recording has already begun, but update recording since the display is now on.
- if (mRecordedWindowContainer == null) {
+ if (!isCurrentlyRecording() || mLastRecordedBounds == null) {
+ return;
+ }
+
+ // Recording has already begun, but update recording since the display is now on.
+ if (mRecordedWindowContainer == null) {
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Unexpectedly null window container; unable to update "
+ + "recording for display %d",
+ mDisplayContent.getDisplayId());
+ return;
+ }
+
+ // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are
+ // inaccurate.
+ if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+ final Task capturedTask = mRecordedWindowContainer.asTask();
+ if (capturedTask.inPinnedWindowingMode()) {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Content Recording: Unexpectedly null window container; unable to update "
- + "recording for display %d",
+ "Content Recording: Display %d was already recording, but "
+ + "pause capture since the task is in PIP",
mDisplayContent.getDisplayId());
+ pauseRecording();
return;
}
+ }
- // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are
- // inaccurate.
- if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
- final Task capturedTask = mRecordedWindowContainer.asTask();
- if (capturedTask.inPinnedWindowingMode()) {
- ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Content Recording: Display %d was already recording, but "
- + "pause capture since the task is in PIP",
- mDisplayContent.getDisplayId());
- pauseRecording();
- return;
- }
- }
-
- ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Content Recording: Display %d was already recording, so apply "
- + "transformations if necessary",
- mDisplayContent.getDisplayId());
- // Retrieve the size of the region to record, and continue with the update
- // if the bounds or orientation has changed.
- final Rect recordedContentBounds = mRecordedWindowContainer.getBounds();
- @Configuration.Orientation int recordedContentOrientation =
- mRecordedWindowContainer.getConfiguration().orientation;
- if (!mLastRecordedBounds.equals(recordedContentBounds)
- || lastOrientation != recordedContentOrientation) {
- Point surfaceSize = fetchSurfaceSizeIfPresent();
- if (surfaceSize != null) {
- ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Content Recording: Going ahead with updating recording for display "
- + "%d to new bounds %s and/or orientation %d.",
- mDisplayContent.getDisplayId(), recordedContentBounds,
- recordedContentOrientation);
- updateMirroredSurface(mRecordedWindowContainer.getSyncTransaction(),
- recordedContentBounds, surfaceSize);
- } else {
- // If the surface removed, do nothing. We will handle this via onDisplayChanged
- // (the display will be off if the surface is removed).
- ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Content Recording: Unable to update recording for display %d to new "
- + "bounds %s and/or orientation %d, since the surface is not "
- + "available.",
- mDisplayContent.getDisplayId(), recordedContentBounds,
- recordedContentOrientation);
- }
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Display %d was already recording, so apply "
+ + "transformations if necessary",
+ mDisplayContent.getDisplayId());
+ // Retrieve the size of the region to record, and continue with the update
+ // if the bounds or orientation has changed.
+ final Rect recordedContentBounds = mRecordedWindowContainer.getBounds();
+ @Configuration.Orientation int recordedContentOrientation =
+ mRecordedWindowContainer.getConfiguration().orientation;
+ final Point surfaceSize = fetchSurfaceSizeIfPresent();
+ if (!mLastRecordedBounds.equals(recordedContentBounds)
+ || lastOrientation != recordedContentOrientation
+ || !mLastConsumingSurfaceSize.equals(surfaceSize)) {
+ if (surfaceSize != null) {
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Going ahead with updating recording for display "
+ + "%d to new bounds %s and/or orientation %d and/or surface "
+ + "size %s",
+ mDisplayContent.getDisplayId(), recordedContentBounds,
+ recordedContentOrientation, surfaceSize);
+ updateMirroredSurface(mRecordedWindowContainer.getSyncTransaction(),
+ recordedContentBounds, surfaceSize);
+ } else {
+ // If the surface removed, do nothing. We will handle this via onDisplayChanged
+ // (the display will be off if the surface is removed).
+ ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+ "Content Recording: Unable to update recording for display %d to new "
+ + "bounds %s and/or orientation %d and/or surface size %s, "
+ + "since the surface is not available.",
+ mDisplayContent.getDisplayId(), recordedContentBounds,
+ recordedContentOrientation, surfaceSize);
}
}
}
@@ -498,10 +507,13 @@ final class ContentRecorder implements WindowContainerListener {
}
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Content Recording: Apply transformations of shift %d x %d, scale %f, crop %d x "
- + "%d for display %d",
+ "Content Recording: Apply transformations of shift %d x %d, scale %f, crop (aka "
+ + "recorded content size) %d x %d for display %d; display has size %d x "
+ + "%d; surface has size %d x %d",
shiftedX, shiftedY, scale, recordedContentBounds.width(),
- recordedContentBounds.height(), mDisplayContent.getDisplayId());
+ recordedContentBounds.height(), mDisplayContent.getDisplayId(),
+ mDisplayContent.getConfiguration().screenWidthDp,
+ mDisplayContent.getConfiguration().screenHeightDp, surfaceSize.x, surfaceSize.y);
transaction
// Crop the area to capture to exclude the 'extra' wallpaper that is used
@@ -515,6 +527,8 @@ final class ContentRecorder implements WindowContainerListener {
// the content will no longer be centered in the output surface.
.setPosition(mRecordedSurface, shiftedX /* x */, shiftedY /* y */);
mLastRecordedBounds = new Rect(recordedContentBounds);
+ mLastConsumingSurfaceSize.x = surfaceSize.x;
+ mLastConsumingSurfaceSize.y = surfaceSize.y;
// Request to notify the client about the resize.
mMediaProjectionManager.notifyActiveProjectionCapturedContentResized(
mLastRecordedBounds.width(), mLastRecordedBounds.height());
@@ -523,6 +537,7 @@ final class ContentRecorder implements WindowContainerListener {
/**
* Returns a non-null {@link Point} if the surface is present, or null otherwise
*/
+ @Nullable
private Point fetchSurfaceSizeIfPresent() {
// Retrieve the default size of the surface the app provided to
// MediaProjection#createVirtualDisplay. Note the app is the consumer of the surface,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 996d66681d05..4309e72c30d7 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -6040,8 +6040,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mOffTokenAcquirer.release(mDisplayId);
}
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
- "Content Recording: Display %d state is now (%d), so update recording?",
- mDisplayId, displayState);
+ "Content Recording: Display %d state was (%d), is now (%d), so update "
+ + "recording?",
+ mDisplayId, lastDisplayState, displayState);
if (lastDisplayState != displayState) {
// If state is on due to surface being added, then start recording.
// If state is off due to surface being removed, then stop recording.
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 8519e4d3e53a..0f1a1053716e 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -21,6 +21,7 @@ import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
+
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
import static com.android.server.wm.DragDropController.MSG_ANIMATION_END;
@@ -32,6 +33,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACT
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.MY_PID;
import static com.android.server.wm.WindowManagerService.MY_UID;
+
import static java.util.concurrent.CompletableFuture.completedFuture;
import android.animation.Animator;
@@ -46,6 +48,7 @@ import android.hardware.input.InputManagerGlobal;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.os.InputConfig;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -183,13 +186,8 @@ class DragState {
// Crop the input surface to the display size.
mTmpClipRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
- // Make trusted overlay to not block any touches while D&D ongoing and allowing
- // touches to pass through to windows underneath. This allows user to interact with the
- // UI to navigate while dragging.
- h.setTrustedOverlay(mTransaction, mInputSurface, true);
mTransaction.show(mInputSurface)
.setInputWindowInfo(mInputSurface, h)
- .setTrustedOverlay(mInputSurface, true)
.setLayer(mInputSurface, Integer.MAX_VALUE)
.setCrop(mInputSurface, mTmpClipRect);
@@ -379,6 +377,11 @@ class DragState {
mDragWindowHandle.ownerUid = MY_UID;
mDragWindowHandle.scaleFactor = 1.0f;
+ // InputConfig.TRUSTED_OVERLAY: To not block any touches while D&D ongoing and allowing
+ // touches to pass through to windows underneath. This allows user to interact with the
+ // UI to navigate while dragging.
+ mDragWindowHandle.inputConfig = InputConfig.TRUSTED_OVERLAY;
+
// The drag window cannot receive new touches.
mDragWindowHandle.touchableRegion.setEmpty();
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index c21930dab5eb..39622c1c5aaf 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -74,7 +74,7 @@ class InputConsumerImpl implements IBinder.DeathRecipient {
mWindowHandle.ownerPid = WindowManagerService.MY_PID;
mWindowHandle.ownerUid = WindowManagerService.MY_UID;
mWindowHandle.scaleFactor = 1.0f;
- mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE;
+ mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.TRUSTED_OVERLAY;
mInputSurface = mService.makeSurfaceBuilder(
mService.mRoot.getDisplayContent(displayId).getSession())
@@ -129,14 +129,12 @@ class InputConsumerImpl implements IBinder.DeathRecipient {
void show(SurfaceControl.Transaction t, WindowContainer w) {
t.show(mInputSurface);
- mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1);
}
void show(SurfaceControl.Transaction t, int layer) {
t.show(mInputSurface);
- mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
t.setInputWindowInfo(mInputSurface, mWindowHandle);
t.setLayer(mInputSurface, layer);
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index f77da6287176..825d38b3eed7 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -40,10 +40,12 @@ import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.LOGTAG_INPUT_FOCUS;
+
import static java.lang.Integer.MAX_VALUE;
import android.annotation.Nullable;
@@ -730,7 +732,7 @@ final class InputMonitor {
new InputWindowHandle(null /* inputApplicationHandle */, displayId));
inputWindowHandle.setName(name);
inputWindowHandle.setLayoutParamsType(TYPE_SECURE_SYSTEM_OVERLAY);
- inputWindowHandle.setTrustedOverlay(t, sc, true);
+ inputWindowHandle.setTrustedOverlay(true);
populateOverlayInputInfo(inputWindowHandle);
setInputWindowInfoIfNeeded(t, sc, inputWindowHandle);
}
diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
index 90d81bd82087..64b7a6064e45 100644
--- a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
+++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
@@ -195,11 +195,6 @@ class InputWindowHandleWrapper {
mChanged = true;
}
- void setTrustedOverlay(SurfaceControl.Transaction t, SurfaceControl sc,
- boolean trustedOverlay) {
- mHandle.setTrustedOverlay(t, sc, trustedOverlay);
- }
-
void setOwnerPid(int pid) {
if (mHandle.ownerPid == pid) {
return;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 01786becda61..3639e1b9cb47 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1126,11 +1126,25 @@ final class LetterboxUiController {
return computeAspectRatio(bounds);
}
+ /**
+ * Whether we should enable users to resize the current app.
+ */
+ boolean shouldEnableUserAspectRatioSettings() {
+ // We use mBooleanPropertyAllowUserAspectRatioOverride to allow apps to opt-out which has
+ // effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null,
+ // the current app doesn't opt-out so the first part of the predicate is true.
+ return !FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+ && mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+ && mActivityRecord.mDisplayContent != null
+ && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest();
+ }
+
+ /**
+ * Whether we should apply the user aspect ratio override to the min aspect ratio for the
+ * current app.
+ */
boolean shouldApplyUserMinAspectRatioOverride() {
- if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
- || !mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
- || mActivityRecord.mDisplayContent == null
- || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
+ if (!shouldEnableUserAspectRatioSettings()) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 21526e789c04..de197a164d10 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3478,9 +3478,9 @@ class Task extends TaskFragment {
}
}
// User Aspect Ratio Settings is enabled if the app is not in SCM
- info.topActivityEligibleForUserAspectRatioButton =
- mWmService.mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
- && top != null && !info.topActivityInSizeCompat;
+ info.topActivityEligibleForUserAspectRatioButton = top != null
+ && !info.topActivityInSizeCompat
+ && top.mLetterboxUiController.shouldEnableUserAspectRatioSettings();
info.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed();
}
@@ -5102,7 +5102,6 @@ class Task extends TaskFragment {
void startActivityLocked(ActivityRecord r, @Nullable Task topTask, boolean newTask,
boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) {
- final ActivityRecord pipCandidate = findEnterPipOnTaskSwitchCandidate(topTask);
Task rTask = r.getTask();
final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront();
final boolean isOrhasTask = rTask == this || hasChild(rTask);
@@ -5166,8 +5165,10 @@ class Task extends TaskFragment {
// supporting picture-in-picture while pausing only if the starting activity
// would not be considered an overlay on top of the current activity
// (eg. not fullscreen, or the assistant)
- enableEnterPipOnTaskSwitch(pipCandidate,
- null /* toFrontTask */, r, options);
+ if (!ActivityTaskManagerService.isPip2ExperimentEnabled()) {
+ final ActivityRecord pipCandidate = findEnterPipOnTaskSwitchCandidate(topTask);
+ enableEnterPipOnTaskSwitch(pipCandidate, null /* toFrontTask */, r, options);
+ }
}
boolean doShow = true;
if (newTask) {
@@ -5240,7 +5241,7 @@ class Task extends TaskFragment {
* enter PiP while it is pausing (if supported). Only one of {@param toFrontTask} or
* {@param toFrontActivity} should be set.
*/
- private static void enableEnterPipOnTaskSwitch(@Nullable ActivityRecord pipCandidate,
+ static void enableEnterPipOnTaskSwitch(@Nullable ActivityRecord pipCandidate,
@Nullable Task toFrontTask, @Nullable ActivityRecord toFrontActivity,
@Nullable ActivityOptions opts) {
if (pipCandidate == null) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 57f44cb599fe..d1b5350bd61a 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1643,7 +1643,17 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// next activity.
final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
"shouldAutoPipWhilePausing", userLeaving);
- if (userLeaving && resumingOccludesParent && lastResumedCanPip
+
+ if (ActivityTaskManagerService.isPip2ExperimentEnabled()) {
+ // If a new task is being launched, then mark the existing top activity as
+ // supporting picture-in-picture while pausing only if the starting activity
+ // would not be considered an overlay on top of the current activity
+ // (eg. not fullscreen, or the assistant)
+ Task.enableEnterPipOnTaskSwitch(prev, resuming.getTask(),
+ resuming, resuming.getOptions());
+ }
+ if (prev.supportsEnterPipOnTaskSwitch && userLeaving
+ && resumingOccludesParent && lastResumedCanPip
&& prev.pictureInPictureArgs.isAutoEnterEnabled()) {
shouldAutoPip = true;
} else if (!lastResumedCanPip) {
@@ -1656,7 +1666,12 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
if (prev.attachedToProcess()) {
- if (shouldAutoPip) {
+ if (shouldAutoPip && ActivityTaskManagerService.isPip2ExperimentEnabled()) {
+ prev.mPauseSchedulePendingForPip = true;
+ boolean willAutoPip = mAtmService.prepareAutoEnterPictureAndPictureMode(prev);
+ ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, requesting PIP mode "
+ + "via requestStartTransition(): %s, willAutoPip: %b", prev, willAutoPip);
+ } else if (shouldAutoPip) {
prev.mPauseSchedulePendingForPip = true;
boolean didAutoPip = mAtmService.enterPictureInPictureMode(
prev, prev.pictureInPictureArgs, false /* fromClient */);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 843e6d154ecb..881fdec4549e 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -174,6 +174,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
private final Token mToken;
private IApplicationThread mRemoteAnimApp;
+ private @Nullable ActivityRecord mPipActivity;
+
/** Only use for clean-up after binder death! */
private SurfaceControl.Transaction mStartTransaction = null;
private SurfaceControl.Transaction mFinishTransaction = null;
@@ -510,6 +512,21 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
/**
+ * Set the pip-able activity participating in this transition.
+ * @param pipActivity activity about to enter pip
+ */
+ void setPipActivity(@Nullable ActivityRecord pipActivity) {
+ mPipActivity = pipActivity;
+ }
+
+ /**
+ * @return pip-able activity participating in this transition.
+ */
+ @Nullable ActivityRecord getPipActivity() {
+ return mPipActivity;
+ }
+
+ /**
* Only set flag to the parent tasks and activity itself.
*/
private void setTransientLaunchToChanges(@NonNull WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 0d7870131133..7d2933a3398a 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -705,13 +705,21 @@ class TransitionController {
try {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Requesting StartTransition: %s", transition);
- ActivityManager.RunningTaskInfo info = null;
+ ActivityManager.RunningTaskInfo startTaskInfo = null;
+ ActivityManager.RunningTaskInfo pipTaskInfo = null;
if (startTask != null) {
- info = new ActivityManager.RunningTaskInfo();
- startTask.fillTaskInfo(info);
+ startTaskInfo = startTask.getTaskInfo();
}
- final TransitionRequestInfo request = new TransitionRequestInfo(
- transition.mType, info, remoteTransition, displayChange, transition.getFlags());
+
+ // set the pip task in the request if provided
+ if (mCollectingTransition.getPipActivity() != null) {
+ pipTaskInfo = mCollectingTransition.getPipActivity().getTask().getTaskInfo();
+ }
+
+ final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType,
+ startTaskInfo, pipTaskInfo, remoteTransition, displayChange,
+ transition.getFlags());
+
transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos();
transition.mLogger.mRequest = request;
mTransitionPlayer.requestStartTransition(transition.getToken(), request);
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index c89360f4ad6b..a4d43d8ef6d0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -963,4 +963,11 @@ public abstract class WindowManagerInternal {
public abstract void captureDisplay(int displayId,
@Nullable ScreenCapture.CaptureArgs captureArgs,
ScreenCapture.ScreenCaptureListener listener);
+
+ /**
+ * Device has a software navigation bar (separate from the status bar) on specific display.
+ *
+ * @param displayId the id of display to check if there is a software navigation bar.
+ */
+ public abstract boolean hasNavigationBar(int displayId);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 10b455db32b6..561848e8dbfa 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8413,6 +8413,11 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public boolean hasNavigationBar(int displayId) {
+ return WindowManagerService.this.hasNavigationBar(displayId);
+ }
+
+ @Override
public void setInputMethodTargetChangeListener(@NonNull ImeTargetChangeListener listener) {
synchronized (mGlobalLock) {
mImeTargetChangeListener = listener;
@@ -8874,6 +8879,11 @@ public class WindowManagerService extends IWindowManager.Stub
h.inputConfig |= InputConfig.NOT_FOCUSABLE;
}
+ // Check private trusted overlay flag to set trustedOverlay field of input window handle.
+ if ((privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0) {
+ h.inputConfig |= InputConfig.TRUSTED_OVERLAY;
+ }
+
h.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
h.ownerUid = callingUid;
h.ownerPid = callingPid;
@@ -8893,8 +8903,6 @@ public class WindowManagerService extends IWindowManager.Stub
}
final SurfaceControl.Transaction t = mTransactionFactory.get();
- // Check private trusted overlay flag to set trustedOverlay field of input window handle.
- h.setTrustedOverlay(t, surface, (privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0);
t.setInputWindowInfo(surface, h);
t.apply();
t.close();
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index bfe055354b9c..74a0bafd3a4c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1279,6 +1279,7 @@ public class WindowManagerShellCommand extends ShellCommand {
mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadiusPx();
mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
+ mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier();
mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
mLetterboxConfiguration.resetEnabledAutomaticReachabilityInBookMode();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 822082ba4c70..b12cc0b30f53 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -28,7 +28,6 @@ import static android.graphics.GraphicsProtos.dumpPointProto;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.InputWindowHandle.USE_SURFACE_TRUSTED_OVERLAY;
import static android.view.SurfaceControl.Transaction;
import static android.view.SurfaceControl.getGlobalTransaction;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
@@ -99,6 +98,7 @@ import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
+
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -1112,9 +1112,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mInputWindowHandle.setName(getName());
mInputWindowHandle.setPackageName(mAttrs.packageName);
mInputWindowHandle.setLayoutParamsType(mAttrs.type);
- if (!USE_SURFACE_TRUSTED_OVERLAY) {
- mInputWindowHandle.setTrustedOverlay(isWindowTrustedOverlay());
- }
+ mInputWindowHandle.setTrustedOverlay(shouldWindowHandleBeTrusted(s));
if (DEBUG) {
Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
+ " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
@@ -1195,12 +1193,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
: service.mAtmService.getProcessController(s.mPid, s.mUid);
}
- private boolean isWindowTrustedOverlay() {
+ boolean shouldWindowHandleBeTrusted(Session s) {
return InputMonitor.isTrustedOverlay(mAttrs.type)
|| ((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0
- && mSession.mCanAddInternalSystemWindow)
+ && s.mCanAddInternalSystemWindow)
|| ((mAttrs.privateFlags & PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY) != 0
- && mSession.mCanCreateSystemApplicationOverlay);
+ && s.mCanCreateSystemApplicationOverlay);
}
int getTouchOcclusionMode() {
@@ -5194,9 +5192,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
updateFrameRateSelectionPriorityIfNeeded();
updateScaleIfNeeded();
mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
- if (USE_SURFACE_TRUSTED_OVERLAY) {
- getSyncTransaction().setTrustedOverlay(mSurfaceControl, isWindowTrustedOverlay());
- }
}
super.prepareSurfaces();
}
@@ -5949,13 +5944,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
boolean isTrustedOverlay() {
- if (USE_SURFACE_TRUSTED_OVERLAY) {
- WindowState parentWindow = getParentWindow();
- return isWindowTrustedOverlay() || (parentWindow != null
- && parentWindow.isWindowTrustedOverlay());
- } else {
- return mInputWindowHandle.isTrustedOverlay();
- }
+ return mInputWindowHandle.isTrustedOverlay();
}
public boolean receiveFocusFromTapOutside() {
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index d22e02e9dae6..4203e89ec618 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -50,6 +50,13 @@
maxOccurs="1"/>
<xs:element type="highBrightnessMode" name="highBrightnessMode" minOccurs="0"
maxOccurs="1"/>
+
+ <xs:element name="hdrBrightnessConfig" type="hdrBrightnessConfig"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="nullable"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+
<xs:element type="displayQuirks" name="quirks" minOccurs="0" maxOccurs="1"/>
<xs:element type="autoBrightness" name="autoBrightness" minOccurs="0"
maxOccurs="1"/>
@@ -238,6 +245,31 @@
</xs:all>
</xs:complexType>
+ <!-- brightness config for HDR content -->
+ <xs:complexType name="hdrBrightnessConfig">
+ <!-- lux level from light sensor to screen brightness recommended max value map. -->
+ <xs:element name="brightnessMap" type="nonNegativeFloatToFloatMap">
+ <xs:annotation name="nonnull"/>
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Debounce for brightness increase in millis -->
+ <xs:element name="brightnessIncreaseDebounceMillis" type="xs:nonNegativeInteger">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Debounce for brightness decrease in millis -->
+ <xs:element name="brightnessDecreaseDebounceMillis" type="xs:nonNegativeInteger">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Animation time for brightness increase in millis -->
+ <xs:element name="brightnessIncreaseDurationMillis" type="xs:nonNegativeInteger">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Animation time for brightness decrease in millis -->
+ <xs:element name="brightnessDecreaseDurationMillis" type="xs:nonNegativeInteger">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:complexType>
+
<!-- Maps to PowerManager.THERMAL_STATUS_* values. -->
<xs:simpleType name="thermalStatus">
<xs:restriction base="xs:string">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 6364c1feb659..ebd9b1c23a50 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -101,6 +101,7 @@ package com.android.server.display.config {
method @Nullable public final com.android.server.display.config.DensityMapping getDensityMapping();
method @NonNull public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholds();
method public final com.android.server.display.config.Thresholds getDisplayBrightnessChangeThresholdsIdle();
+ method @Nullable public final com.android.server.display.config.HdrBrightnessConfig getHdrBrightnessConfig();
method public com.android.server.display.config.HighBrightnessMode getHighBrightnessMode();
method public final com.android.server.display.config.SensorDetails getLightSensor();
method public com.android.server.display.config.LuxThrottling getLuxThrottling();
@@ -130,6 +131,7 @@ package com.android.server.display.config {
method public final void setDensityMapping(@Nullable com.android.server.display.config.DensityMapping);
method public final void setDisplayBrightnessChangeThresholds(@NonNull com.android.server.display.config.Thresholds);
method public final void setDisplayBrightnessChangeThresholdsIdle(com.android.server.display.config.Thresholds);
+ method public final void setHdrBrightnessConfig(@Nullable com.android.server.display.config.HdrBrightnessConfig);
method public void setHighBrightnessMode(com.android.server.display.config.HighBrightnessMode);
method public final void setLightSensor(com.android.server.display.config.SensorDetails);
method public void setLuxThrottling(com.android.server.display.config.LuxThrottling);
@@ -168,6 +170,20 @@ package com.android.server.display.config {
method public final void setTimeWindowSecs_all(@NonNull java.math.BigInteger);
}
+ public class HdrBrightnessConfig {
+ ctor public HdrBrightnessConfig();
+ method public final java.math.BigInteger getBrightnessDecreaseDebounceMillis();
+ method public final java.math.BigInteger getBrightnessDecreaseDurationMillis();
+ method public final java.math.BigInteger getBrightnessIncreaseDebounceMillis();
+ method public final java.math.BigInteger getBrightnessIncreaseDurationMillis();
+ method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getBrightnessMap();
+ method public final void setBrightnessDecreaseDebounceMillis(java.math.BigInteger);
+ method public final void setBrightnessDecreaseDurationMillis(java.math.BigInteger);
+ method public final void setBrightnessIncreaseDebounceMillis(java.math.BigInteger);
+ method public final void setBrightnessIncreaseDurationMillis(java.math.BigInteger);
+ method public final void setBrightnessMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+ }
+
public class HighBrightnessMode {
ctor public HighBrightnessMode();
method @NonNull public final boolean getAllowInLowPowerMode_all();
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index d8c684fb8c1e..82d39d6c6982 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -35,11 +35,13 @@ import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialRequest;
import android.credentials.CredentialOption;
import android.credentials.CredentialProviderInfo;
+import android.credentials.GetCandidateCredentialsRequest;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
+import android.credentials.IGetCandidateCredentialsCallback;
import android.credentials.IGetCredentialCallback;
import android.credentials.IPrepareGetCredentialCallback;
import android.credentials.ISetEnabledProvidersCallback;
@@ -96,6 +98,9 @@ public final class CredentialManagerService
private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER =
"enable_credential_manager";
+ private static final String DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API =
+ "enable_credential_description_api";
+
private final Context mContext;
/** Cache of system service list per user id. */
@@ -321,7 +326,14 @@ public final class CredentialManagerService
}
public static boolean isCredentialDescriptionApiEnabled() {
- return true;
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_DESC_API,
+ false);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
}
@SuppressWarnings("GuardedBy") // ErrorProne requires initiateProviderSessionForRequestLocked
@@ -461,6 +473,17 @@ public final class CredentialManagerService
final class CredentialManagerServiceStub extends ICredentialManager.Stub {
@Override
+ public ICancellationSignal getCandidateCredentials(
+ GetCandidateCredentialsRequest request,
+ IGetCandidateCredentialsCallback callback,
+ final String callingPackage) {
+ Slog.i(TAG, "starting getCandidateCredentials with callingPackage: "
+ + callingPackage);
+ // TODO(): Implement
+ return CancellationSignal.createTransport();
+ }
+
+ @Override
public ICancellationSignal executeGetCredential(
GetCredentialRequest request,
IGetCredentialCallback callback,
@@ -954,7 +977,7 @@ public final class CredentialManagerService
Slog.i(TAG, "registerCredentialDescription with callingPackage: " + callingPackage);
if (!isCredentialDescriptionApiEnabled()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Feature not supported");
}
enforceCallingPackage(callingPackage, Binder.getCallingUid());
@@ -974,7 +997,7 @@ public final class CredentialManagerService
if (!isCredentialDescriptionApiEnabled()) {
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException("Feature not supported");
}
enforceCallingPackage(callingPackage, Binder.getCallingUid());
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 21b12914bc4c..d0ead141b58c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -1472,11 +1472,10 @@ final class DevicePolicyEngine {
synchronized (mLock) {
clear();
new DevicePoliciesReaderWriter().readFromFileLocked();
- reapplyAllPoliciesLocked();
}
}
- private <V> void reapplyAllPoliciesLocked() {
+ <V> void reapplyAllPoliciesLocked() {
for (PolicyKey policy : mGlobalPolicies.keySet()) {
PolicyState<?> policyState = mGlobalPolicies.get(policy);
// Policy definition and value will always be of the same type
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 50dc061c1ab2..84d1a452fa7e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3347,6 +3347,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mOwners.systemReady();
applyManagedSubscriptionsPolicyIfRequired();
break;
+ case SystemService.PHASE_SYSTEM_SERVICES_READY:
+ synchronized (getLockObject()) {
+ mDevicePolicyEngine.reapplyAllPoliciesLocked();
+ }
+ break;
case SystemService.PHASE_ACTIVITY_MANAGER_READY:
synchronized (getLockObject()) {
migrateToProfileOnOrganizationOwnedDeviceIfCompLocked();
@@ -15836,6 +15841,83 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
/**
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA},
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE} and {@link
+ * DevicePolicyManager#POLICY_SUSPEND_PACKAGES}.
+ */
+ private Set<android.app.admin.EnforcingAdmin> getEnforcingAdminsForRestrictionInternal(
+ int userId, @NonNull String restriction) {
+ Objects.requireNonNull(restriction);
+ Set<android.app.admin.EnforcingAdmin> admins = new HashSet<>();
+ // For POLICY_SUSPEND_PACKAGES return PO or DO to keep the behavior same as
+ // before the bug fix for b/192245204.
+ if (DevicePolicyManager.POLICY_SUSPEND_PACKAGES.equals(
+ restriction)) {
+ ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
+ if (profileOwner != null) {
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ profileOwner, userId);
+ admins.add(admin.getParcelableAdmin());
+ return admins;
+ }
+ final Pair<Integer, ComponentName> deviceOwner =
+ mOwners.getDeviceOwnerUserIdAndComponent();
+ if (deviceOwner != null && deviceOwner.first == userId) {
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ deviceOwner.second, deviceOwner.first);
+ admins.add(admin.getParcelableAdmin());
+ return admins;
+ }
+ } else {
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ PolicyDefinition<Boolean> policyDefinition = getPolicyDefinitionForRestriction(
+ restriction);
+ Boolean value = mDevicePolicyEngine.getResolvedPolicy(policyDefinition, userId);
+ if (value != null && value) {
+ Map<EnforcingAdmin, PolicyValue<Boolean>> globalPolicies =
+ mDevicePolicyEngine.getGlobalPoliciesSetByAdmins(policyDefinition);
+ for (EnforcingAdmin admin : globalPolicies.keySet()) {
+ if (globalPolicies.get(admin) != null
+ && Boolean.TRUE.equals(globalPolicies.get(admin).getValue())) {
+ admins.add(admin.getParcelableAdmin());
+ }
+ }
+
+ Map<EnforcingAdmin, PolicyValue<Boolean>> localPolicies =
+ mDevicePolicyEngine.getLocalPoliciesSetByAdmins(
+ policyDefinition, userId);
+ for (EnforcingAdmin admin : localPolicies.keySet()) {
+ if (localPolicies.get(admin) != null
+ && Boolean.TRUE.equals(localPolicies.get(admin).getValue())) {
+ admins.add(admin.getParcelableAdmin());
+ }
+ }
+ return admins;
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ }
+ return admins;
+ }
+
+ private static PolicyDefinition<Boolean> getPolicyDefinitionForRestriction(
+ @NonNull String restriction) {
+ Objects.requireNonNull(restriction);
+ if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
+ return PolicyDefinition.getPolicyDefinitionForUserRestriction(
+ UserManager.DISALLOW_CAMERA);
+ } else if (DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
+ return PolicyDefinition.SCREEN_CAPTURE_DISABLED;
+ } else {
+ return PolicyDefinition.getPolicyDefinitionForUserRestriction(restriction);
+ }
+ }
+
+
+ /**
* Excludes restrictions imposed by UserManager.
*/
private List<UserManager.EnforcingUser> getDevicePolicySources(
@@ -15873,6 +15955,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return getEnforcingAdminAndUserDetailsInternal(userId, restriction);
}
+ @Override
+ public List<android.app.admin.EnforcingAdmin> getEnforcingAdminsForRestriction(
+ int userId, String restriction) {
+ Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
+ return new ArrayList<>(getEnforcingAdminsForRestrictionInternal(userId, restriction));
+ }
+
/**
* @param restriction The restriction enforced by admin. It could be any user restriction or
* policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
index 5243d14af1ab..006642235615 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnforcingAdmin.java
@@ -228,7 +228,8 @@ final class EnforcingAdmin {
return new android.app.admin.EnforcingAdmin(
mPackageName,
authority,
- UserHandle.of(mUserId));
+ UserHandle.of(mUserId),
+ mComponentName);
}
/**
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 82d00a6fcbca..7374901763db 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -44,6 +44,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.server.display.config.HdrBrightnessData;
import com.android.server.display.config.ThermalStatus;
import org.junit.Before;
@@ -477,6 +478,23 @@ public final class DisplayDeviceConfigTest {
mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA);
}
+ @Test
+ public void testHdrBrightnessDataFromDisplayConfig() throws IOException {
+ setupDisplayDeviceConfigFromDisplayConfigFile();
+
+ HdrBrightnessData data = mDisplayDeviceConfig.getHdrBrightnessData();
+
+ assertNotNull(data);
+ assertEquals(2, data.mMaxBrightnessLimits.size());
+ assertEquals(13000, data.mBrightnessDecreaseDebounceMillis);
+ assertEquals(10000, data.mBrightnessDecreaseDurationMillis);
+ assertEquals(1000, data.mBrightnessIncreaseDebounceMillis);
+ assertEquals(11000, data.mBrightnessIncreaseDurationMillis);
+
+ assertEquals(0.3f, data.mMaxBrightnessLimits.get(500f), SMALL_DELTA);
+ assertEquals(0.6f, data.mMaxBrightnessLimits.get(1200f), SMALL_DELTA);
+ }
+
private void verifyConfigValuesFromConfigResource() {
assertNull(mDisplayDeviceConfig.getName());
assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
@@ -694,6 +712,25 @@ public final class DisplayDeviceConfigTest {
+ "</proxSensor>\n";
}
+ private String getHdrBrightnessConfig() {
+ return "<hdrBrightnessConfig>\n"
+ + " <brightnessMap>\n"
+ + " <point>\n"
+ + " <first>500</first>\n"
+ + " <second>0.3</second>\n"
+ + " </point>\n"
+ + " <point>\n"
+ + " <first>1200</first>\n"
+ + " <second>0.6</second>\n"
+ + " </point>\n"
+ + " </brightnessMap>\n"
+ + " <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>\n"
+ + " <brightnessIncreaseDurationMillis>11000</brightnessIncreaseDurationMillis>\n"
+ + " <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>\n"
+ + " <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>\n"
+ + "</hdrBrightnessConfig>";
+ }
+
private String getContent() {
return getContent(getValidLuxThrottling(), getValidProxSensor());
}
@@ -784,6 +821,7 @@ public final class DisplayDeviceConfigTest {
+ "</point>\n"
+ "</sdrHdrRatioMap>\n"
+ "</highBrightnessMode>\n"
+ + getHdrBrightnessConfig()
+ brightnessCapConfig
+ "<lightSensor>\n"
+ "<type>test_light_sensor</type>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index e7dc48e529eb..89e28cb8ab83 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -43,6 +43,7 @@ import android.content.res.Resources;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
@@ -67,7 +68,9 @@ import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.Layout;
import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
import com.android.server.policy.WindowManagerPolicy;
@@ -97,6 +100,7 @@ public final class DisplayPowerController2Test {
private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private static final float PROX_SENSOR_MAX_RANGE = 5;
+ private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
@@ -695,6 +699,80 @@ public final class DisplayPowerController2Test {
}
@Test
+ public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ final float sdrBrightness = 0.1f;
+ final float hdrBrightness = 0.3f;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+ when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(sdrBrightness);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
+
+ when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+ when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+ clearInvocations(mHolder.animator);
+
+ mHolder.dpc.updateBrightness();
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
+ eq(BRIGHTNESS_RAMP_RATE_MINIMUM));
+ }
+
+ @Test
+ public void testDisplayBrightnessHdr_SkipAnimationOnHdrRemoval() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ final float sdrBrightness = 0.1f;
+ final float hdrBrightness = 0.3f;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+ when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
+
+ when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+ when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(hdrBrightness);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
+ when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+
+ clearInvocations(mHolder.animator);
+
+ mHolder.dpc.updateBrightness();
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
+ eq(BRIGHTNESS_RAMP_RATE_MINIMUM));
+ }
+
+ @Test
public void testDoesNotSetScreenStateForNonDefaultDisplayUntilBootCompleted() {
// We should still set screen state for the default display
DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -1175,6 +1253,28 @@ public final class DisplayPowerController2Test {
verify(mHolder.displayPowerState, times(1)).stop();
}
+ @Test
+ public void testRampRateForHdrContent() {
+ float clampedBrightness = 0.6f;
+ float transitionRate = 35.5f;
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
+ when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+ when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(PowerManager.BRIGHTNESS_MAX);
+ when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness);
+ when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate);
+
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator, atLeastOnce()).animateTo(eq(clampedBrightness), anyFloat(),
+ eq(transitionRate));
+ }
+
/**
* Creates a mock and registers it to {@link LocalServices}.
*/
@@ -1270,12 +1370,15 @@ public final class DisplayPowerController2Test {
final ScreenOffBrightnessSensorController screenOffBrightnessSensorController =
mock(ScreenOffBrightnessSensorController.class);
final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
+ final HdrClamper hdrClamper = mock(HdrClamper.class);
+ final DisplayManagerFlags flags = mock(DisplayManagerFlags.class);
when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
TestInjector injector = spy(new TestInjector(displayPowerState, animator,
automaticBrightnessController, wakelockController, brightnessMappingStrategy,
- hysteresisLevels, screenOffBrightnessSensorController, hbmController));
+ hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
+ flags));
final LogicalDisplay display = mock(LogicalDisplay.class);
final DisplayDevice device = mock(DisplayDevice.class);
@@ -1289,11 +1392,11 @@ public final class DisplayPowerController2Test {
mContext, injector, mDisplayPowerCallbacksMock, mHandler,
mSensorManagerMock, mDisplayBlankerMock, display,
mBrightnessTrackerMock, brightnessSetting, () -> {},
- hbmMetadata, /* bootCompleted= */ false);
+ hbmMetadata, /* bootCompleted= */ false, flags);
return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
animator, automaticBrightnessController, wakelockController,
- screenOffBrightnessSensorController, hbmController, hbmMetadata,
+ screenOffBrightnessSensorController, hbmController, hdrClamper, hbmMetadata,
brightnessMappingStrategy, injector);
}
@@ -1311,6 +1414,8 @@ public final class DisplayPowerController2Test {
public final WakelockController wakelockController;
public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController;
public final HighBrightnessModeController hbmController;
+
+ public final HdrClamper hdrClamper;
public final HighBrightnessModeMetadata hbmMetadata;
public final BrightnessMappingStrategy brightnessMappingStrategy;
public final DisplayPowerController2.Injector injector;
@@ -1322,6 +1427,7 @@ public final class DisplayPowerController2Test {
WakelockController wakelockController,
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
HighBrightnessModeController hbmController,
+ HdrClamper hdrClamper,
HighBrightnessModeMetadata hbmMetadata,
BrightnessMappingStrategy brightnessMappingStrategy,
DisplayPowerController2.Injector injector) {
@@ -1334,6 +1440,7 @@ public final class DisplayPowerController2Test {
this.wakelockController = wakelockController;
this.screenOffBrightnessSensorController = screenOffBrightnessSensorController;
this.hbmController = hbmController;
+ this.hdrClamper = hdrClamper;
this.hbmMetadata = hbmMetadata;
this.brightnessMappingStrategy = brightnessMappingStrategy;
this.injector = injector;
@@ -1350,13 +1457,19 @@ public final class DisplayPowerController2Test {
private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
private final HighBrightnessModeController mHighBrightnessModeController;
+ private final HdrClamper mHdrClamper;
+
+ private final DisplayManagerFlags mFlags;
+
TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator,
AutomaticBrightnessController automaticBrightnessController,
WakelockController wakelockController,
BrightnessMappingStrategy brightnessMappingStrategy,
HysteresisLevels hysteresisLevels,
ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
- HighBrightnessModeController highBrightnessModeController) {
+ HighBrightnessModeController highBrightnessModeController,
+ HdrClamper hdrClamper,
+ DisplayManagerFlags flags) {
mDisplayPowerState = dps;
mAnimator = animator;
mAutomaticBrightnessController = automaticBrightnessController;
@@ -1365,6 +1478,8 @@ public final class DisplayPowerController2Test {
mHysteresisLevels = hysteresisLevels;
mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
mHighBrightnessModeController = highBrightnessModeController;
+ mHdrClamper = hdrClamper;
+ mFlags = flags;
}
@Override
@@ -1471,6 +1586,15 @@ public final class DisplayPowerController2Test {
}
@Override
+ BrightnessRangeController getBrightnessRangeController(
+ HighBrightnessModeController hbmController, Runnable modeChangeCallback,
+ DisplayDeviceConfig displayDeviceConfig, Handler handler,
+ DisplayManagerFlags flags) {
+ return new BrightnessRangeController(hbmController, modeChangeCallback,
+ displayDeviceConfig, mHdrClamper, mFlags);
+ }
+
+ @Override
DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler,
SensorManager sensorManager, Resources resources) {
return mDisplayWhiteBalanceControllerMock;
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 e0c0ae2349f9..971ece365f30 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -43,6 +43,7 @@ import android.content.res.Resources;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.hardware.display.BrightnessInfo;
import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
@@ -69,6 +70,7 @@ import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.display.layout.Layout;
import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
import com.android.server.policy.WindowManagerPolicy;
@@ -97,6 +99,7 @@ public final class DisplayPowerControllerTest {
private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private static final float PROX_SENSOR_MAX_RANGE = 5;
+ private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
private static final float BRIGHTNESS_RAMP_RATE_SLOW_DECREASE = 0.1f;
@@ -1184,6 +1187,77 @@ public final class DisplayPowerControllerTest {
verify(mHolder.displayPowerState, times(1)).stop();
}
+ @Test
+ public void testDisplayBrightnessHdr_SkipAnimationOnHdrAppearance() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ final float sdrBrightness = 0.1f;
+ final float hdrBrightness = 0.3f;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(sdrBrightness);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
+
+ when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+ when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+ clearInvocations(mHolder.animator);
+
+ mHolder.dpc.updateBrightness();
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
+ eq(BRIGHTNESS_RAMP_RATE_MINIMUM));
+ }
+
+ @Test
+ public void testDisplayBrightnessHdr_SkipAnimationOnHdrRemoval() {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ final float sdrBrightness = 0.1f;
+ final float hdrBrightness = 0.3f;
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(sdrBrightness);
+
+ when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR);
+ when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(hdrBrightness);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(hdrBrightness), eq(sdrBrightness),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE));
+
+ when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(hdrBrightness);
+ when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(sdrBrightness);
+ when(mHolder.hbmController.getHighBrightnessMode()).thenReturn(
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+
+ clearInvocations(mHolder.animator);
+
+ mHolder.dpc.updateBrightness();
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(sdrBrightness), eq(sdrBrightness),
+ eq(BRIGHTNESS_RAMP_RATE_MINIMUM));
+ }
+
private void advanceTime(long timeMs) {
mClock.fastForward(timeMs);
mTestLooper.dispatchAll();
@@ -1282,6 +1356,7 @@ public final class DisplayPowerControllerTest {
final HighBrightnessModeMetadata hbmMetadata = mock(HighBrightnessModeMetadata.class);
final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
+ final DisplayManagerFlags flags = mock(DisplayManagerFlags.class);
setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
@@ -1289,7 +1364,7 @@ public final class DisplayPowerControllerTest {
mContext, injector, mDisplayPowerCallbacksMock, mHandler,
mSensorManagerMock, mDisplayBlankerMock, display,
mBrightnessTrackerMock, brightnessSetting, () -> {},
- hbmMetadata, /* bootCompleted= */ false);
+ hbmMetadata, /* bootCompleted= */ false, flags);
return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting,
animator, automaticBrightnessController, screenOffBrightnessSensorController,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java b/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java
new file mode 100644
index 000000000000..2820da7c49c1
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/RampAnimatorTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertEquals;
+
+import android.util.FloatProperty;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class RampAnimatorTest {
+
+ private RampAnimator<TestObject> mRampAnimator;
+
+ private final TestObject mTestObject = new TestObject();
+
+ private final FloatProperty<TestObject> mTestProperty = new FloatProperty<>("mValue") {
+ @Override
+ public void setValue(TestObject object, float value) {
+ object.mValue = value;
+ }
+
+ @Override
+ public Float get(TestObject object) {
+ return object.mValue;
+ }
+ };
+
+ @Before
+ public void setUp() {
+ mRampAnimator = new RampAnimator<>(mTestObject, mTestProperty);
+ }
+
+ @Test
+ public void testInitialValueUsedInLastAnimationStep() {
+ mRampAnimator.setAnimationTarget(0.67f, 0.1f);
+
+ assertEquals(0.67f, mTestObject.mValue, 0);
+ }
+
+ private static class TestObject {
+ private float mValue;
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
new file mode 100644
index 000000000000..0ebe46ac0c88
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.testutils.OffsettableClock;
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+public class HdrClamperTest {
+
+ public static final float FLOAT_TOLERANCE = 0.0001f;
+
+ @Rule
+ public MockitoRule mRule = MockitoJUnit.rule();
+
+ @Mock
+ private BrightnessClamperController.ClamperChangeListener mMockListener;
+
+ OffsettableClock mClock = new OffsettableClock.Stopped();
+
+ private final TestHandler mTestHandler = new TestHandler(null, mClock);
+
+
+ private HdrClamper mHdrClamper;
+
+
+ @Before
+ public void setUp() {
+ mHdrClamper = new HdrClamper(mMockListener, mTestHandler);
+ configureClamper();
+ }
+
+ @Test
+ public void testClamper_AmbientLuxChangesAboveLimit() {
+ mHdrClamper.onAmbientLuxChange(500);
+
+ assertFalse(mTestHandler.hasMessagesOrCallbacks());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testClamper_AmbientLuxChangesBelowLimit() {
+ mHdrClamper.onAmbientLuxChange(499);
+
+ assertTrue(mTestHandler.hasMessagesOrCallbacks());
+ TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek();
+ assertEquals(2000, msgInfo.sendTime);
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+
+ mClock.fastForward(2000);
+ mTestHandler.timeAdvance();
+ assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testClamper_AmbientLuxChangesBelowLimit_ThenFastAboveLimit() {
+ mHdrClamper.onAmbientLuxChange(499);
+ mHdrClamper.onAmbientLuxChange(500);
+
+ assertFalse(mTestHandler.hasMessagesOrCallbacks());
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+ }
+
+ @Test
+ public void testClamper_AmbientLuxChangesBelowLimit_ThenSlowlyAboveLimit() {
+ mHdrClamper.onAmbientLuxChange(499);
+ mClock.fastForward(2000);
+ mTestHandler.timeAdvance();
+
+ mHdrClamper.onAmbientLuxChange(500);
+
+ assertTrue(mTestHandler.hasMessagesOrCallbacks());
+ TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek();
+ assertEquals(3000, msgInfo.sendTime); // 2000 + 1000
+
+ mClock.fastForward(1000);
+ mTestHandler.timeAdvance();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE);
+ }
+
+ private void configureClamper() {
+ mHdrClamper.getConfiguration().mMaxBrightnessLimits.put(500f, 0.6f);
+ mHdrClamper.getConfiguration().mIncreaseConfig.mDebounceTimeMillis = 1000;
+ mHdrClamper.getConfiguration().mIncreaseConfig.mTransitionTimeMillis = 1500;
+ mHdrClamper.getConfiguration().mDecreaseConfig.mDebounceTimeMillis = 2000;
+ mHdrClamper.getConfiguration().mDecreaseConfig.mTransitionTimeMillis = 2500;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index d988063bdfb0..614fd11e0517 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -18,7 +18,9 @@ package com.android.server.pm;
import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -157,11 +159,12 @@ public class PackageArchiverTest {
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.getResourcesForApplication(eq(PACKAGE))).thenReturn(
mock(Resources.class));
- when(mIcon.compress(eq(Bitmap.CompressFormat.PNG), eq(100), any())).thenReturn(true);
mArchiveManager = spy(new PackageArchiver(mContext, pm));
doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE),
- any(LauncherActivityInfo.class), eq(mUserId));
+ any(LauncherActivityInfo.class), eq(mUserId), anyInt());
+ doReturn(mIcon).when(mArchiveManager).decodeIcon(
+ any(ArchiveState.ArchiveActivityInfo.class));
}
@Test
@@ -249,7 +252,7 @@ public class PackageArchiverTest {
public void archiveApp_storeIconFails() throws IntentSender.SendIntentException, IOException {
IOException e = new IOException("IO");
doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
- any(LauncherActivityInfo.class), eq(mUserId));
+ any(LauncherActivityInfo.class), eq(mUserId), anyInt());
mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
@@ -372,6 +375,38 @@ public class PackageArchiverTest {
assertThat(intent.getPackage()).isEqualTo(INSTALLER_PACKAGE);
}
+ @Test
+ public void getArchivedAppIcon_packageNotInstalled() {
+ when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
+ null);
+
+ Exception e = assertThrows(
+ ParcelableException.class,
+ () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ String.format("Package %s not found.", PACKAGE));
+ }
+
+ @Test
+ public void getArchivedAppIcon_notArchived() {
+ Exception e = assertThrows(
+ ParcelableException.class,
+ () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
+ assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+ assertThat(e.getCause()).hasMessageThat().isEqualTo(
+ String.format("Package %s is not currently archived.", PACKAGE));
+ }
+
+ @Test
+ public void getArchivedAppIcon_success() {
+ mUserState.setArchiveState(createArchiveState()).setInstalled(false);
+
+ assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isEqualTo(
+ mIcon);
+ }
+
+
private static ArchiveState createArchiveState() {
List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
new file mode 100644
index 000000000000..9fdbdda38c75
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/AnrTimerTest.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.Presubmit;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:AnrTimerTest
+ */
+@SmallTest
+@Presubmit
+public class AnrTimerTest {
+
+ /**
+ * A handler that allows control over when to dispatch messages and callbacks. Because most
+ * Handler methods are final, the only thing this handler can intercept is sending messages.
+ * This handler allows unit tests to be written without a need to sleep (which leads to flaky
+ * tests).
+ *
+ * This code was cloned from {@link com.android.systemui.utils.os.FakeHandler}.
+ */
+ static class TestHandler extends Handler {
+
+ private boolean mImmediate = true;
+ private ArrayList<Message> mQueuedMessages = new ArrayList<>();
+
+ ArrayList<Long> mDelays = new ArrayList<>();
+
+ TestHandler(Looper looper, Callback callback, boolean immediate) {
+ super(looper, callback);
+ mImmediate = immediate;
+ }
+
+ TestHandler(Looper looper, Callback callback) {
+ this(looper, callback, true);
+ }
+
+ /**
+ * Override sendMessageAtTime. In immediate mode, the message is immediately dispatched.
+ * In non-immediate mode, the message is enqueued to the real handler. In both cases, the
+ * original delay is computed by comparing the target dispatch time with 'now'. This
+ * computation is prone to errors if the code experiences delays. The computed time is
+ * captured in the mDelays list.
+ */
+ @Override
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ long delay = uptimeMillis - SystemClock.uptimeMillis();
+ mDelays.add(delay);
+ if (mImmediate) {
+ mQueuedMessages.add(msg);
+ dispatchQueuedMessages();
+ } else {
+ super.sendMessageAtTime(msg, uptimeMillis);
+ }
+ return true;
+ }
+
+ void setImmediate(boolean immediate) {
+ mImmediate = immediate;
+ }
+
+ /** Dispatch any messages that have been queued on the calling thread. */
+ void dispatchQueuedMessages() {
+ ArrayList<Message> messages = new ArrayList<>(mQueuedMessages);
+ mQueuedMessages.clear();
+ for (Message msg : messages) {
+ dispatchMessage(msg);
+ }
+ }
+
+ /**
+ * Compare the captured delays with the input array. The comparison is fuzzy because the
+ * captured delay (see sendMessageAtTime) is affected by process delays.
+ */
+ void verifyDelays(long[] r) {
+ final long FUZZ = 10;
+ assertEquals(r.length, mDelays.size());
+ for (int i = 0; i < mDelays.size(); i++) {
+ long t = r[i];
+ long v = mDelays.get(i);
+ assertTrue(v >= t - FUZZ && v <= t + FUZZ);
+ }
+ }
+ }
+
+ private Handler mHandler;
+ private CountDownLatch mLatch = null;
+ private ArrayList<Message> mMessages;
+
+ // The commonly used message timeout key.
+ private static final int MSG_TIMEOUT = 1;
+
+ @Before
+ public void setUp() {
+ mHandler = new Handler(Looper.getMainLooper(), this::expirationHandler);
+ mMessages = new ArrayList<>();
+ mLatch = new CountDownLatch(1);
+ AnrTimer.resetTimerListForHermeticTest();
+ }
+
+ @After
+ public void tearDown() {
+ mHandler = null;
+ mMessages = null;
+ }
+
+ // When a timer expires, set the expiration time in the message and add it to the queue.
+ private boolean expirationHandler(Message msg) {
+ mMessages.add(Message.obtain(msg));
+ mLatch.countDown();
+ return false;
+ }
+
+ // The test argument includes a pid and uid, and a tag. The tag is used to distinguish
+ // different message instances.
+ private static class TestArg {
+ final int pid;
+ final int uid;
+ final int tag;
+
+ TestArg(int pid, int uid, int tag) {
+ this.pid = pid;
+ this.uid = uid;
+ this.tag = tag;
+ }
+ @Override
+ public String toString() {
+ return String.format("pid=%d uid=%d tag=%d", pid, uid, tag);
+ }
+ }
+
+ /**
+ * An instrumented AnrTimer.
+ */
+ private class TestAnrTimer extends AnrTimer {
+ // A local copy of 'what'. The field in AnrTimer is private.
+ final int mWhat;
+
+ TestAnrTimer(Handler h, int key, String tag) {
+ super(h, key, tag);
+ mWhat = key;
+ }
+
+ TestAnrTimer() {
+ this(mHandler, MSG_TIMEOUT, caller());
+ }
+
+ TestAnrTimer(Handler h, int key, String tag, boolean extend, TestInjector injector) {
+ super(h, key, tag, extend, injector);
+ mWhat = key;
+ }
+
+ TestAnrTimer(boolean extend, TestInjector injector) {
+ this(mHandler, MSG_TIMEOUT, caller(), extend, injector);
+ }
+
+ // Return the name of method that called the constructor, assuming that this function is
+ // called from inside the constructor. The calling method is used to name the AnrTimer
+ // instance so that logs are easier to understand.
+ private static String caller() {
+ final int n = 4;
+ StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+ if (stack.length < n+1) return "test";
+ return stack[n].getMethodName();
+ }
+
+ boolean start(TestArg arg, long millis) {
+ return start(arg, arg.pid, arg.uid, millis);
+ }
+
+ int what() {
+ return mWhat;
+ }
+ }
+
+ private static class TestTracker extends AnrTimer.CpuTracker {
+ long index = 0;
+ final int skip;
+ TestTracker(int skip) {
+ this.skip = skip;
+ }
+ long delay(int pid) {
+ return index++ * skip;
+ }
+ }
+
+ private class TestInjector extends AnrTimer.Injector {
+ final boolean mImmediate;
+ final AnrTimer.CpuTracker mTracker;
+ TestHandler mTestHandler;
+
+ TestInjector(int skip, boolean immediate) {
+ mTracker = new TestTracker(skip);
+ mImmediate = immediate;
+ }
+
+ TestInjector(int skip) {
+ this(skip, true);
+ }
+
+ @Override
+ Handler getHandler(Handler.Callback callback) {
+ if (mTestHandler == null) {
+ mTestHandler = new TestHandler(mHandler.getLooper(), callback, mImmediate);
+ }
+ return mTestHandler;
+ }
+
+ /** Fetch the allocated handle. This does not check for nulls. */
+ TestHandler getHandler() {
+ return mTestHandler;
+ }
+
+ AnrTimer.CpuTracker getTracker() {
+ return mTracker;
+ }
+ }
+
+ // Tests
+ // 1. Start a timer and wait for expiration.
+ // 2. Start a timer and cancel it. Verify no expiration.
+ // 3. Start a timer. Shortly thereafter, restart it. Verify only one expiration.
+ // 4. Start a couple of timers. Verify max active timers. Discard one and verify the active
+ // count drops by 1. Accept one and verify the active count drops by 1.
+
+
+ @Test
+ public void testSimpleTimeout() throws Exception {
+ // Create an immediate TestHandler.
+ TestInjector injector = new TestInjector(0);
+ TestAnrTimer timer = new TestAnrTimer(false, injector);
+ TestArg t = new TestArg(1, 1, 3);
+ assertTrue(timer.start(t, 10));
+ // Delivery is immediate but occurs on a different thread.
+ assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
+ assertEquals(1, mMessages.size());
+ Message m = mMessages.get(0);
+ assertEquals(timer.what(), m.what);
+ assertEquals(t, m.obj);
+
+ // Verify that the timer is still present.
+ assertEquals(1, AnrTimer.sizeOfTimerList());
+ assertTrue(timer.accept(t));
+ assertEquals(0, AnrTimer.sizeOfTimerList());
+
+ // Verify that the timer no longer exists.
+ assertFalse(timer.accept(t));
+ }
+
+ @Test
+ public void testCancel() throws Exception {
+ // Create an non-immediate TestHandler.
+ TestInjector injector = new TestInjector(0, false);
+ TestAnrTimer timer = new TestAnrTimer(false, injector);
+
+ Handler handler = injector.getHandler();
+ assertNotNull(handler);
+ assertTrue(handler instanceof TestHandler);
+
+ // The tests that follow check for a 'what' of 0 (zero), which is the message key used
+ // by AnrTimer internally.
+ TestArg t = new TestArg(1, 1, 3);
+ assertFalse(handler.hasMessages(0));
+ assertTrue(timer.start(t, 100));
+ assertTrue(handler.hasMessages(0));
+ assertTrue(timer.cancel(t));
+ assertFalse(handler.hasMessages(0));
+
+ // Verify that no expiration messages were delivered.
+ assertEquals(0, mMessages.size());
+ assertEquals(0, AnrTimer.sizeOfTimerList());
+ }
+
+ @Test
+ public void testRestart() throws Exception {
+ // Create an non-immediate TestHandler.
+ TestInjector injector = new TestInjector(0, false);
+ TestAnrTimer timer = new TestAnrTimer(false, injector);
+
+ TestArg t = new TestArg(1, 1, 3);
+ assertTrue(timer.start(t, 2500));
+ assertTrue(timer.start(t, 1000));
+
+ // Verify that the test handler saw two timeouts.
+ injector.getHandler().verifyDelays(new long[] { 2500, 1000 });
+
+ // Verify that there is a single timer. Then cancel it.
+ assertEquals(1, AnrTimer.sizeOfTimerList());
+ assertTrue(timer.cancel(t));
+ assertEquals(0, AnrTimer.sizeOfTimerList());
+ }
+
+ @Test
+ public void testExtendNormal() throws Exception {
+ // Create an immediate TestHandler.
+ TestInjector injector = new TestInjector(5);
+ TestAnrTimer timer = new TestAnrTimer(true, injector);
+ TestArg t = new TestArg(1, 1, 3);
+ assertTrue(timer.start(t, 10));
+
+ assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
+ assertEquals(1, mMessages.size());
+ Message m = mMessages.get(0);
+ assertEquals(timer.what(), m.what);
+ assertEquals(t, m.obj);
+
+ // Verify that the test handler saw two timeouts: one of 10ms and one of 5ms.
+ injector.getHandler().verifyDelays(new long[] { 10, 5 });
+
+ // Verify that the timer is still present. Then remove it and verify that the list is
+ // empty.
+ assertEquals(1, AnrTimer.sizeOfTimerList());
+ assertTrue(timer.accept(t));
+ assertEquals(0, AnrTimer.sizeOfTimerList());
+ }
+
+ @Test
+ public void testExtendOversize() throws Exception {
+ // Create an immediate TestHandler.
+ TestInjector injector = new TestInjector(25);
+ TestAnrTimer timer = new TestAnrTimer(true, injector);
+ TestArg t = new TestArg(1, 1, 3);
+ assertTrue(timer.start(t, 10));
+
+ assertTrue(mLatch.await(100, TimeUnit.MILLISECONDS));
+ assertEquals(1, mMessages.size());
+ Message m = mMessages.get(0);
+ assertEquals(timer.what(), m.what);
+ assertEquals(t, m.obj);
+
+ // Verify that the test handler saw two timeouts: one of 10ms and one of 10ms.
+ injector.getHandler().verifyDelays(new long[] { 10, 10 });
+
+ // Verify that the timer is still present. Then remove it and verify that the list is
+ // empty.
+ assertEquals(1, AnrTimer.sizeOfTimerList());
+ assertTrue(timer.accept(t));
+ assertEquals(0, AnrTimer.sizeOfTimerList());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index b0fd60672853..d85768dd7588 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -79,6 +79,9 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
/**
* Tests for the {@link MediaProjectionManagerService} class.
*
@@ -202,7 +205,8 @@ public class MediaProjectionManagerServiceTest {
}
@Test
- public void testCreateProjection_priorProjectionGrant() throws NameNotFoundException {
+ public void testCreateProjection_priorProjectionGrant() throws
+ NameNotFoundException, InterruptedException {
// Create a first projection.
MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
FakeIMediaProjectionCallback callback1 = new FakeIMediaProjectionCallback();
@@ -214,9 +218,13 @@ public class MediaProjectionManagerServiceTest {
FakeIMediaProjectionCallback callback2 = new FakeIMediaProjectionCallback();
secondProjection.start(callback2);
- // Check that the second projection's callback hasn't been stopped.
- assertThat(callback1.mStopped).isTrue();
- assertThat(callback2.mStopped).isFalse();
+ // Check that the first projection get stopped, but not the second projection.
+ final int timeout = 5;
+ boolean stoppedCallback1 = callback1.mLatch.await(timeout, TimeUnit.SECONDS);
+ boolean stoppedCallback2 = callback2.mLatch.await(timeout, TimeUnit.SECONDS);
+
+ assertThat(stoppedCallback1).isTrue();
+ assertThat(stoppedCallback2).isFalse();
}
@Test
@@ -803,11 +811,10 @@ public class MediaProjectionManagerServiceTest {
}
private static class FakeIMediaProjectionCallback extends IMediaProjectionCallback.Stub {
- boolean mStopped = false;
-
+ CountDownLatch mLatch = new CountDownLatch(1);
@Override
public void onStop() throws RemoteException {
- mStopped = true;
+ mLatch.countDown();
}
@Override
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index d6a38773bf6b..42e3383987d6 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -47,8 +47,6 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"/>
- <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
-
<!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
<application android:debuggable="true"
@@ -106,11 +104,6 @@
android:showWhenLocked="true"
android:turnScreenOn="true" />
- <activity android:name="android.app.Activity"
- android:exported="true"
- android:showWhenLocked="true"
- android:turnScreenOn="true" />
-
<activity
android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity"
android:exported="true">
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 479194673e80..0a7bb00ce1c2 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -297,6 +297,7 @@ public class ShortcutLoggingTests extends ShortcutKeyTestBase {
mPhoneWindowManager.overrideUserSetupComplete();
mPhoneWindowManager.setupAssistForLaunch();
mPhoneWindowManager.overrideTogglePanel();
+ mPhoneWindowManager.overrideInjectKeyEvent();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index e301da7c58fc..6d46d9ccf02e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -464,6 +464,10 @@ class TestPhoneWindowManager {
doReturn(device).when(mInputManager).getInputDevice(anyInt());
}
+ void overrideInjectKeyEvent() {
+ doReturn(true).when(mInputManager).injectInputEvent(any(KeyEvent.class), anyInt());
+ }
+
void overrideSearchKeyBehavior(int behavior) {
mPhoneWindowManager.mSearchKeyBehavior = behavior;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 0989db4c25ac..568471d67c31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
@@ -26,7 +25,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -36,7 +34,6 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,13 +41,11 @@ import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManagerInternal;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
@@ -76,7 +71,6 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
/**
@@ -509,88 +503,4 @@ public class ActivityStartInterceptorTest {
verify(callback, times(1)).onActivityLaunched(any(), any(), any());
}
-
- @Test
- public void testSandboxServiceInterceptionHappensToIntentWithSandboxActivityAction() {
- ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
- mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
- PackageManager packageManagerMock = mock(PackageManager.class);
- String sandboxPackageNameMock = "com.sandbox.mock";
- when(mContext.getPackageManager()).thenReturn(packageManagerMock);
- when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
- Intent intent = new Intent().setAction(ACTION_START_SANDBOXED_ACTIVITY);
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- verify(spyCallback, times(1)).onInterceptActivityLaunch(
- any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
- }
-
- @Test
- public void testSandboxServiceInterceptionHappensToIntentWithSandboxPackage() {
- ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
- mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
- PackageManager packageManagerMock = mock(PackageManager.class);
- String sandboxPackageNameMock = "com.sandbox.mock";
- when(mContext.getPackageManager()).thenReturn(packageManagerMock);
- when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
- Intent intent = new Intent().setPackage(sandboxPackageNameMock);
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- verify(spyCallback, times(1)).onInterceptActivityLaunch(
- any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
- }
-
- @Test
- public void testSandboxServiceInterceptionHappensToIntentWithComponentNameWithSandboxPackage() {
- ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
- mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
- PackageManager packageManagerMock = mock(PackageManager.class);
- String sandboxPackageNameMock = "com.sandbox.mock";
- when(mContext.getPackageManager()).thenReturn(packageManagerMock);
- when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
- Intent intent = new Intent().setComponent(new ComponentName(sandboxPackageNameMock, ""));
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- verify(spyCallback, times(1)).onInterceptActivityLaunch(
- any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
- }
-
- @Test
- public void testSandboxServiceInterceptionNotCalledWhenIntentNotRelatedToSandbox() {
- ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
- mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
- PackageManager packageManagerMock = mock(PackageManager.class);
- String sandboxPackageNameMock = "com.sandbox.mock";
- when(mContext.getPackageManager()).thenReturn(packageManagerMock);
- when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
- // Intent: null
- mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null);
-
- // Action: null, Package: null, ComponentName: null
- Intent intent = new Intent();
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- // Wrong Action
- intent = new Intent().setAction(Intent.ACTION_VIEW);
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- // Wrong Package
- intent = new Intent().setPackage("Random");
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- // Wrong ComponentName's package
- intent = new Intent().setComponent(new ComponentName("Random", ""));
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- verify(spyCallback, never()).onInterceptActivityLaunch(
- any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index ecd84e174615..3d3531e92abe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -304,6 +304,30 @@ public class ContentRecorderTests extends WindowTestsBase {
anyFloat(), anyFloat());
}
+ /**
+ * Test that resizing the output surface results in resizing the mirrored content to fit.
+ */
+ @Test
+ public void testOnConfigurationChanged_resizeSurface() {
+ mContentRecorder.setContentRecordingSession(mDisplaySession);
+ mContentRecorder.updateRecording();
+
+ // Resize the output surface.
+ final Point newSurfaceSize = new Point(Math.round(sSurfaceSize.x / 2f),
+ Math.round(sSurfaceSize.y * 2));
+ doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize(
+ anyInt());
+ mContentRecorder.onConfigurationChanged(
+ mVirtualDisplayContent.getConfiguration().orientation);
+
+ // No resize is issued, only the initial transformations when we started recording.
+ verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
+ anyFloat());
+ verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
+ anyFloat(), anyFloat());
+
+ }
+
@Test
public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
mContentRecorder.setContentRecordingSession(mTaskSession);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 2ad9fa0e5b13..0566f460c655 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -862,6 +862,39 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
}
@Test
+ public void testShouldEnableUserAspectRatioSettings_falseProperty_returnsFalse()
+ throws Exception {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldEnableUserAspectRatioSettings());
+ }
+
+ @Test
+ public void testShouldEnableUserAspectRatioSettings_trueProperty_returnsTrue()
+ throws Exception {
+ prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertTrue(mController.shouldEnableUserAspectRatioSettings());
+ }
+
+ @Test
+ public void testShouldEnableUserAspectRatioSettings_noIgnoreOrientaion_returnsFalse()
+ throws Exception {
+ prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false);
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
+
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ assertFalse(mController.shouldEnableUserAspectRatioSettings());
+ }
+
+ @Test
public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse()
throws Exception {
prepareActivityThatShouldApplyUserMinAspectRatioOverride();
@@ -898,13 +931,26 @@ public class LetterboxUiControllerTest extends WindowTestsBase {
assertTrue(mController.shouldApplyUserMinAspectRatioOverride());
}
- private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
+ @Test
+ public void testShouldApplyUserMinAspectRatioOverride_noIgnoreOrientationreturnsFalse() {
+ prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ false);
+
+ assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+ }
+
+ private void prepareActivityForShouldApplyUserMinAspectRatioOverride(
+ boolean orientationRequest) {
spyOn(mController);
- doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+ doReturn(orientationRequest).when(
+ mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
mDisplayContent.setIgnoreOrientationRequest(true);
doReturn(USER_MIN_ASPECT_RATIO_3_2).when(mController).getUserMinAspectRatioOverrideCode();
}
+ private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
+ prepareActivityForShouldApplyUserMinAspectRatioOverride(/* orientationRequest */ true);
+ }
+
private void prepareActivityThatShouldApplyUserFullscreenOverride() {
spyOn(mController);
doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 1dd71e00841f..fb27d6368a7e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -571,26 +571,28 @@ public class TaskTests extends WindowTestsBase {
final Task task = rootTask.getBottomMostTask();
final ActivityRecord root = task.getTopNonFinishingActivity();
spyOn(mWm.mLetterboxConfiguration);
-
- // When device config flag is disabled the button is not enabled
- doReturn(false).when(mWm.mLetterboxConfiguration)
- .isUserAppAspectRatioSettingsEnabled();
- doReturn(false).when(mWm.mLetterboxConfiguration)
- .isTranslucentLetterboxingEnabled();
- assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
-
- // The flag is enabled
- doReturn(true).when(mWm.mLetterboxConfiguration)
- .isUserAppAspectRatioSettingsEnabled();
spyOn(root);
- doReturn(task).when(root).getOrganizedTask();
- // When the flag is enabled and the top activity is not in size compat mode.
+ spyOn(root.mLetterboxUiController);
+
+ doReturn(true).when(root.mLetterboxUiController)
+ .shouldEnableUserAspectRatioSettings();
doReturn(false).when(root).inSizeCompatMode();
+ doReturn(task).when(root).getOrganizedTask();
+
+ // The button should be eligible to be displayed
assertTrue(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+ // When shouldApplyUserMinAspectRatioOverride is disable the button is not enabled
+ doReturn(false).when(root.mLetterboxUiController)
+ .shouldEnableUserAspectRatioSettings();
+ assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+ doReturn(true).when(root.mLetterboxUiController)
+ .shouldEnableUserAspectRatioSettings();
+
// When in size compat mode the button is not enabled
doReturn(true).when(root).inSizeCompatMode();
assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton);
+ doReturn(false).when(root).inSizeCompatMode();
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
deleted file mode 100644
index e8a847c5d8a4..000000000000
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.view.InputWindowHandle.USE_SURFACE_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.os.IBinder;
-import android.platform.test.annotations.Presubmit;
-import android.server.wm.BuildUtils;
-import android.server.wm.CtsWindowInfoUtils;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.WindowManager;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@Presubmit
-public class TrustedOverlayTests {
- private static final String TAG = "TrustedOverlayTests";
- private static final long TIMEOUT_S = 5L * BuildUtils.HW_TIMEOUT_MULTIPLIER;
-
- @Rule
- public TestName mName = new TestName();
-
- private final ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(
- Activity.class);
-
- private Instrumentation mInstrumentation;
- private Activity mActivity;
-
- @Before
- public void setup() {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mActivity = mActivityRule.launchActivity(null);
- }
-
- @Test
- public void setTrustedOverlayInputWindow() throws InterruptedException {
- assumeFalse(USE_SURFACE_TRUSTED_OVERLAY);
- testTrustedOverlayChildHelper(false);
- }
-
- @Test
- public void setTrustedOverlayChildLayer() throws InterruptedException {
- assumeTrue(USE_SURFACE_TRUSTED_OVERLAY);
- testTrustedOverlayChildHelper(true);
- }
-
- private void testTrustedOverlayChildHelper(boolean expectTrusted) throws InterruptedException {
- IBinder[] tokens = new IBinder[2];
- CountDownLatch hostTokenReady = new CountDownLatch(1);
- mInstrumentation.runOnMainSync(() -> {
- mActivity.getWindow().addPrivateFlags(PRIVATE_FLAG_TRUSTED_OVERLAY);
- View rootView = mActivity.getWindow().getDecorView();
- if (rootView.isAttachedToWindow()) {
- tokens[0] = rootView.getWindowToken();
- hostTokenReady.countDown();
- } else {
- rootView.getViewTreeObserver().addOnWindowAttachListener(
- new ViewTreeObserver.OnWindowAttachListener() {
- @Override
- public void onWindowAttached() {
- tokens[0] = rootView.getWindowToken();
- hostTokenReady.countDown();
- }
-
- @Override
- public void onWindowDetached() {
- }
- });
- }
- });
-
- assertTrue("Failed to wait for host to get added",
- hostTokenReady.await(TIMEOUT_S, TimeUnit.SECONDS));
-
- mInstrumentation.runOnMainSync(() -> {
- WindowManager wm = mActivity.getSystemService(WindowManager.class);
-
- View childView = new View(mActivity) {
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- tokens[1] = getWindowToken();
- }
- };
- WindowManager.LayoutParams params = new WindowManager.LayoutParams();
- params.token = tokens[0];
- params.type = TYPE_APPLICATION_PANEL;
- wm.addView(childView, params);
- });
-
- boolean[] foundTrusted = new boolean[2];
-
- CtsWindowInfoUtils.waitForWindowInfos(
- windowInfos -> {
- for (var windowInfo : windowInfos) {
- if (windowInfo.windowToken == tokens[0]
- && windowInfo.isTrustedOverlay) {
- foundTrusted[0] = true;
- } else if (windowInfo.windowToken == tokens[1]
- && windowInfo.isTrustedOverlay) {
- foundTrusted[1] = true;
- }
- }
- return foundTrusted[0] && foundTrusted[1];
- }, TIMEOUT_S, TimeUnit.SECONDS);
-
- if (!foundTrusted[0] || !foundTrusted[1]) {
- CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName.getMethodName());
- }
-
- assertEquals("Failed to find parent window or was not marked trusted", expectTrusted,
- foundTrusted[0]);
- assertEquals("Failed to find child window or was not marked trusted", expectTrusted,
- foundTrusted[1]);
- }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
index f173d661c0a4..c5dd447b5b0c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java
@@ -18,16 +18,16 @@ package com.android.server.wm;
import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER;
-
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.platform.test.annotations.Presubmit;
-import android.util.Log;
+import android.server.wm.CtsWindowInfoUtils;
import android.view.SurfaceControl;
import android.view.SurfaceControl.TrustedPresentationThresholds;
+import androidx.annotation.GuardedBy;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import com.android.server.wm.utils.CommonUtils;
@@ -36,9 +36,8 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestName;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
@@ -53,6 +52,15 @@ public class TrustedPresentationCallbackTest {
private static final float FRACTION_VISIBLE = 0.1f;
+ private final Object mResultsLock = new Object();
+ @GuardedBy("mResultsLock")
+ private boolean mResult;
+ @GuardedBy("mResultsLock")
+ private boolean mReceivedResults;
+
+ @Rule
+ public TestName mName = new TestName();
+
@Rule
public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule(
TestActivity.class);
@@ -71,36 +79,32 @@ public class TrustedPresentationCallbackTest {
@Test
public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException {
- boolean[] results = new boolean[1];
- CountDownLatch receivedResults = new CountDownLatch(1);
TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds,
Runnable::run, inTrustedPresentationState -> {
- Log.d(TAG, "onTrustedPresentationChanged " + inTrustedPresentationState);
- results[0] = inTrustedPresentationState;
- receivedResults.countDown();
+ synchronized (mResultsLock) {
+ mResult = inTrustedPresentationState;
+ mReceivedResults = true;
+ mResultsLock.notify();
+ }
});
t.apply();
-
- assertTrue("Timed out waiting for results",
- receivedResults.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
- assertTrue(results[0]);
+ synchronized (mResultsLock) {
+ assertResults();
+ }
}
@Test
public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException {
- final Object resultsLock = new Object();
- boolean[] results = new boolean[1];
- boolean[] receivedResults = new boolean[1];
TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds(
1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS);
Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> {
- synchronized (resultsLock) {
- results[0] = inTrustedPresentationState;
- receivedResults[0] = true;
- resultsLock.notify();
+ synchronized (mResultsLock) {
+ mResult = inTrustedPresentationState;
+ mReceivedResults = true;
+ mResultsLock.notify();
}
};
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
@@ -108,32 +112,41 @@ public class TrustedPresentationCallbackTest {
Runnable::run, trustedPresentationCallback);
t.apply();
- synchronized (resultsLock) {
- if (!receivedResults[0]) {
- resultsLock.wait(WAIT_TIME_MS);
+ synchronized (mResultsLock) {
+ if (!mReceivedResults) {
+ mResultsLock.wait(WAIT_TIME_MS);
}
- // Make sure we received the results and not just timed out
- assertTrue("Timed out waiting for results", receivedResults[0]);
- assertTrue(results[0]);
-
+ assertResults();
// reset the state
- receivedResults[0] = false;
+ mReceivedResults = false;
}
mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t,
trustedPresentationCallback);
t.apply();
- synchronized (resultsLock) {
- if (!receivedResults[0]) {
- resultsLock.wait(WAIT_TIME_MS);
+ synchronized (mResultsLock) {
+ if (!mReceivedResults) {
+ mResultsLock.wait(WAIT_TIME_MS);
}
// Ensure we waited the full time and never received a notify on the result from the
// callback.
- assertFalse("Should never have received a callback", receivedResults[0]);
+ assertFalse("Should never have received a callback", mReceivedResults);
// results shouldn't have changed.
- assertTrue(results[0]);
+ assertTrue(mResult);
+ }
+ }
+
+ @GuardedBy("mResultsLock")
+ private void assertResults() throws InterruptedException {
+ mResultsLock.wait(WAIT_TIME_MS);
+
+ if (!mReceivedResults) {
+ CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName());
}
+ // Make sure we received the results and not just timed out
+ assertTrue("Timed out waiting for results", mReceivedResults);
+ assertTrue(mResult);
}
public static class TestActivity extends Activity {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 3d78a1dd8943..0a70a5f9b947 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -27,9 +27,9 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPH
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__SERVICE_CRASH;
-import android.app.AppOpsManager;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.Disabled;
import android.content.ComponentName;
@@ -50,6 +50,7 @@ import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SharedMemory;
+import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.service.voice.HotwordDetectionService;
import android.service.voice.HotwordDetectionServiceFailure;
@@ -114,6 +115,9 @@ final class HotwordDetectionConnection {
private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
+ private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED =
+ SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false);
+
/**
* Indicates the {@link HotwordDetectionService} is created.
*/
@@ -680,7 +684,8 @@ final class HotwordDetectionConnection {
mIntent = intent;
mDetectionServiceType = detectionServiceType;
int flags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0;
- if (mVisualQueryDetectionComponentName != null
+ if (SYSPROP_VISUAL_QUERY_SERVICE_ENABLED
+ && mVisualQueryDetectionComponentName != null
&& mHotwordDetectionComponentName != null) {
flags |= Context.BIND_SHARED_ISOLATED_PROCESS;
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 471acc118572..6ba77da1d972 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -57,6 +57,7 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SharedMemory;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.service.voice.HotwordDetector;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
@@ -96,6 +97,8 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
/** The delay time for retrying to request DirectActions. */
private static final long REQUEST_DIRECT_ACTIONS_RETRY_TIME_MS = 200;
+ private static final boolean SYSPROP_VISUAL_QUERY_SERVICE_ENABLED =
+ SystemProperties.getBoolean("ro.hotword.visual_query_service_enabled", false);
final boolean mValid;
@@ -715,7 +718,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
} else {
verifyDetectorForVisualQueryDetectionLocked(sharedMemory);
}
- if (!verifyProcessSharingLocked()) {
+ if (SYSPROP_VISUAL_QUERY_SERVICE_ENABLED && !verifyProcessSharingLocked()) {
Slog.w(TAG, "Sandboxed detection service not in shared isolated process");
throw new IllegalStateException("VisualQueryDetectionService or HotworDetectionService "
+ "not in a shared isolated process. Please make sure to set "
@@ -914,6 +917,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
if (hotwordInfo == null || visualQueryInfo == null) {
return true;
}
+ // Enforce shared isolated option is used when VisualQueryDetectionservice is enabled
return (hotwordInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0
&& (visualQueryInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0;
}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index a72f7806d3ea..1f32c978fad1 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -412,6 +412,15 @@ public class TelecomManager {
"android.telecom.extra.CALL_CREATED_TIME_MILLIS";
/**
+ * Optional extra for incoming containing a long which specifies the time the
+ * call was answered by user. This value is in milliseconds.
+ * @hide
+ */
+ public static final String EXTRA_CALL_ANSWERED_TIME_MILLIS =
+ "android.telecom.extra.CALL_ANSWERED_TIME_MILLIS";
+
+
+ /**
* Optional extra for incoming and outgoing calls containing a long which specifies the Epoch
* time the call was created.
* @hide
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 66f3bedf697d..7a3fc3c29d2b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4607,12 +4607,12 @@ public class CarrierConfigManager {
*
* <p>Example:
*
- * <pre><code>
+ * <pre>{@code
* <string-array name="carrier_service_name_array" num="2">
* <item value="Police"/>
* <item value="Ambulance"/>
* </string-array>
- * </code></pre>
+ * }</pre>
*/
public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
@@ -4626,18 +4626,18 @@ public class CarrierConfigManager {
*
* <ul>
* <li>The number of items in both the arrays are equal
- * <li>The item added in this key follows a specific format. Either it should be all numbers,
- * or "+" followed by all numbers.
+ * <li>The item should contain dialable characters only which includes 0-9, -, *, #, (, ),
+ * SPACE.
* </ul>
*
* <p>Example:
*
- * <pre><code>
+ * <pre>{@code
* <string-array name="carrier_service_number_array" num="2">
- * <item value="123"/>
- * <item value="+343"/>
+ * <item value="*123"/>
+ * <item value="+ (111) 111-111"/>
* </string-array>
- * </code></pre>
+ * }</pre>
*/
public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY =
"carrier_service_number_array";
diff --git a/tests/CtsSurfaceControlTestsStaging/TEST_MAPPING b/tests/CtsSurfaceControlTestsStaging/TEST_MAPPING
index de0ad5915868..0b75eb35520e 100644
--- a/tests/CtsSurfaceControlTestsStaging/TEST_MAPPING
+++ b/tests/CtsSurfaceControlTestsStaging/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "CtsSurfaceControlTestsStaging"
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index 87231c86ef19..93a5bf5bcb4e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -27,7 +27,11 @@ import android.tools.device.traces.parsers.toFlickerComponent
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.wm.shell.flicker.utils.*
+import com.android.wm.shell.flicker.utils.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
+import com.android.wm.shell.flicker.utils.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.utils.splitScreenDividerBecomesVisible
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
@@ -63,11 +67,12 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) :
.withHomeActivityVisible()
.waitForAndVerify()
startDisplayBounds =
- wmHelper.currentState.layerState.physicalDisplayBounds ?:
- error("Display not found")
+ wmHelper.currentState.layerState.physicalDisplayBounds
+ ?: error("Display not found")
}
transitions {
- SplitScreenUtils.enterSplit(wmHelper, tapl, device, testApp, secondaryApp)
+ SplitScreenUtils.enterSplit(wmHelper, tapl, device, testApp, secondaryApp,
+ flicker.scenario.startRotation)
SplitScreenUtils.waitForSplitComplete(wmHelper, testApp, secondaryApp)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
index 4941eea12129..3cae1c43285b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/quickswitch/scenarios/QuickSwitchBetweenTwoAppsForward.kt
@@ -30,6 +30,7 @@ import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
+import android.tools.device.flicker.rules.ChangeDisplayOrientationRule
@Ignore("Base Test Class")
abstract class QuickSwitchBetweenTwoAppsForward(val rotation: Rotation = Rotation.ROTATION_0) {
@@ -46,7 +47,9 @@ abstract class QuickSwitchBetweenTwoAppsForward(val rotation: Rotation = Rotatio
tapl.setExpectedRotation(rotation.value)
testApp1.launchViaIntent(wmHelper)
+ ChangeDisplayOrientationRule.setRotation(rotation)
testApp2.launchViaIntent(wmHelper)
+ ChangeDisplayOrientationRule.setRotation(rotation)
tapl.launchedAppState.quickSwitchToPreviousApp()
wmHelper
.StateSyncBuilder()
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 96b685dc356a..365e00e2b652 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -26,6 +26,7 @@ android_test {
"androidx.test.runner",
"androidx.test.uiautomator_uiautomator",
"servicestests-utils",
+ "flag-junit",
"frameworks-base-testutils",
"hamcrest-library",
"kotlin-test",
diff --git a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
new file mode 100644
index 000000000000..3a2a3be0690d
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 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.hardware.input
+
+import android.content.ContextWrapper
+import android.graphics.drawable.Drawable
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.hardware.input.Flags
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnitRunner
+
+/**
+ * Tests for Keyboard layout preview
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyboardLayoutPreviewTests
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class KeyboardLayoutPreviewTests {
+
+ companion object {
+ const val WIDTH = 100
+ const val HEIGHT = 100
+ }
+
+ @get:Rule
+ val setFlagsRule = SetFlagsRule()
+
+ private fun createDrawable(): Drawable? {
+ val context = ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext())
+ val inputManager = context.getSystemService(InputManager::class.java)!!
+ return inputManager.getKeyboardLayoutPreview(null, WIDTH, HEIGHT)
+ }
+
+ @Test
+ fun testKeyboardLayoutDrawable_hasCorrectDimensions() {
+ setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
+ val drawable = createDrawable()!!
+ assertEquals(WIDTH, drawable.intrinsicWidth)
+ assertEquals(HEIGHT, drawable.intrinsicHeight)
+ }
+
+ @Test
+ fun testKeyboardLayoutDrawable_isNull_ifFlagOff() {
+ setFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
+ assertNull(createDrawable())
+ }
+} \ No newline at end of file
diff --git a/tests/InputScreenshotTest/Android.bp b/tests/InputScreenshotTest/Android.bp
new file mode 100644
index 000000000000..eee486f99748
--- /dev/null
+++ b/tests/InputScreenshotTest/Android.bp
@@ -0,0 +1,60 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "InputScreenshotTests",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+ static_libs: [
+ "androidx.arch.core_core-testing",
+ "androidx.compose.ui_ui-test-junit4",
+ "androidx.compose.ui_ui-test-manifest",
+ "androidx.lifecycle_lifecycle-runtime-testing",
+ "androidx.compose.animation_animation",
+ "androidx.compose.material3_material3",
+ "androidx.compose.material_material-icons-extended",
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.runtime_runtime-livedata",
+ "androidx.compose.ui_ui-tooling-preview",
+ "androidx.lifecycle_lifecycle-livedata-ktx",
+ "androidx.lifecycle_lifecycle-runtime-compose",
+ "androidx.navigation_navigation-compose",
+ "truth-prebuilt",
+ "androidx.compose.runtime_runtime",
+ "androidx.test.core",
+ "androidx.test.ext.junit",
+ "androidx.test.ext.truth",
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "androidx.test.uiautomator_uiautomator",
+ "servicestests-utils",
+ "frameworks-base-testutils",
+ "platform-screenshot-diff-core",
+ "hamcrest-library",
+ "kotlin-test",
+ "flag-junit",
+ "platform-test-annotations",
+ "services.core.unboosted",
+ "testables",
+ "testng",
+ "truth-prebuilt",
+ ],
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ ],
+ test_suites: ["device-tests"],
+ compile_multilib: "both",
+ use_embedded_native_libs: false,
+ asset_dirs: ["assets"],
+}
diff --git a/tests/InputScreenshotTest/AndroidManifest.xml b/tests/InputScreenshotTest/AndroidManifest.xml
new file mode 100644
index 000000000000..9ffbb3a58ebb
--- /dev/null
+++ b/tests/InputScreenshotTest/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.input.screenshot">
+
+ <uses-sdk android:minSdkVersion="21"/>
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Screenshot tests for Input"
+ android:targetPackage="com.android.input.screenshot">
+ </instrumentation>
+</manifest>
diff --git a/tests/InputScreenshotTest/AndroidTest.xml b/tests/InputScreenshotTest/AndroidTest.xml
new file mode 100644
index 000000000000..cc25fa454f06
--- /dev/null
+++ b/tests/InputScreenshotTest/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<configuration description="Runs Input screendiff tests.">
+ <option name="test-suite-tag" value="apct-instrumentation" />
+ <option name="test-suite-tag" value="apct" />
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="optimized-property-setting" value="true" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="InputScreenshotTests.apk" />
+ </target_preparer>
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys"
+ value="/data/user/0/com.android.input.screenshot/files/input_screenshots" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.input.screenshot" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/InputScreenshotTest/OWNERS b/tests/InputScreenshotTest/OWNERS
new file mode 100644
index 000000000000..3cffce960b1c
--- /dev/null
+++ b/tests/InputScreenshotTest/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 136048
+include /core/java/android/hardware/input/OWNERS
diff --git a/tests/InputScreenshotTest/TEST_MAPPING b/tests/InputScreenshotTest/TEST_MAPPING
new file mode 100644
index 000000000000..727e609d91ac
--- /dev/null
+++ b/tests/InputScreenshotTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "InputScreenshotTests"
+ }
+ ]
+}
diff --git a/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
new file mode 100644
index 000000000000..70e4a7101c7f
--- /dev/null
+++ b/tests/InputScreenshotTest/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
new file mode 100644
index 000000000000..502c1b4499d4
--- /dev/null
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-ansi.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
new file mode 100644
index 000000000000..591b2fa9608e
--- /dev/null
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview-jis.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
new file mode 100644
index 000000000000..0137a853e538
--- /dev/null
+++ b/tests/InputScreenshotTest/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
new file mode 100644
index 000000000000..37a91e1fce53
--- /dev/null
+++ b/tests/InputScreenshotTest/assets/tablet/dark_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/Bitmap.kt b/tests/InputScreenshotTest/src/android/input/screenshot/Bitmap.kt
new file mode 100644
index 000000000000..84c971c750fb
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/Bitmap.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.input.screenshot
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.os.Build
+import android.view.View
+import platform.test.screenshot.matchers.MSSIMMatcher
+import platform.test.screenshot.matchers.PixelPerfectMatcher
+
+/** Draw this [View] into a [Bitmap]. */
+// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their
+// tests.
+fun View.drawIntoBitmap(): Bitmap {
+ val bitmap =
+ Bitmap.createBitmap(
+ measuredWidth,
+ measuredHeight,
+ Bitmap.Config.ARGB_8888,
+ )
+ val canvas = Canvas(bitmap)
+ draw(canvas)
+ return bitmap
+}
+
+/**
+ * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
+ * screenshot *unit* tests.
+ */
+val UnitTestBitmapMatcher =
+ if (Build.CPU_ABI == "x86_64") {
+ // Different CPU architectures can sometimes end up rendering differently, so we can't do
+ // pixel-perfect matching on different architectures using the same golden. Given that our
+ // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the
+ // x86_64 architecture and use the Structural Similarity Index on others.
+ // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can
+ // do pixel perfect matching both at presubmit time and at development time with actual
+ // devices.
+ PixelPerfectMatcher()
+ } else {
+ MSSIMMatcher()
+ }
+
+/**
+ * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
+ * screenshot *unit* tests.
+ *
+ * We use the Structural Similarity Index for integration tests because they usually contain
+ * additional information and noise that shouldn't break the test.
+ */
+val IntegrationTestBitmapMatcher = MSSIMMatcher() \ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/DefaultDeviceEmulationSpec.kt b/tests/InputScreenshotTest/src/android/input/screenshot/DefaultDeviceEmulationSpec.kt
new file mode 100644
index 000000000000..edddc6b41cf7
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/DefaultDeviceEmulationSpec.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.input.screenshot
+
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+
+/**
+ * The emulations specs for all 8 permutations of:
+ * - phone or tablet.
+ * - dark of light mode.
+ * - portrait or landscape.
+ */
+val DeviceEmulationSpec.Companion.PhoneAndTabletFull
+ get() = PhoneAndTabletFullSpec
+
+private val PhoneAndTabletFullSpec =
+ DeviceEmulationSpec.forDisplays(Displays.Phone, Displays.Tablet)
+
+/**
+ * The emulations specs of:
+ * - phone + light mode + portrait.
+ * - phone + light mode + landscape.
+ * - tablet + dark mode + portrait.
+ *
+ * This allows to test the most important permutations of a screen/layout with only 3
+ * configurations.
+ */
+val DeviceEmulationSpec.Companion.PhoneAndTabletMinimal
+ get() = PhoneAndTabletMinimalSpec
+
+private val PhoneAndTabletMinimalSpec =
+ DeviceEmulationSpec.forDisplays(Displays.Phone, isDarkTheme = false) +
+ DeviceEmulationSpec.forDisplays(Displays.Tablet, isDarkTheme = true, isLandscape = false)
+
+/**
+ * This allows to test only single most important configuration.
+ */
+val DeviceEmulationSpec.Companion.PhoneMinimal
+ get() = PhoneMinimalSpec
+
+private val PhoneMinimalSpec =
+ DeviceEmulationSpec.forDisplays(Displays.Phone, isDarkTheme = false, isLandscape = false)
+
+object Displays {
+ val Phone =
+ DisplaySpec(
+ "phone",
+ width = 1440,
+ height = 3120,
+ densityDpi = 560,
+ )
+
+ val Tablet =
+ DisplaySpec(
+ "tablet",
+ width = 2560,
+ height = 1600,
+ densityDpi = 320,
+ )
+} \ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
new file mode 100644
index 000000000000..8faf22440828
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputGoldenImagePathManager.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.input.screenshot
+
+import androidx.test.platform.app.InstrumentationRegistry
+import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.PathConfig
+
+/** A [GoldenImagePathManager] that should be used for all Input screenshot tests. */
+class InputGoldenImagePathManager(
+ pathConfig: PathConfig,
+ assetsPathRelativeToBuildRoot: String
+) :
+ GoldenImagePathManager(
+ appContext = InstrumentationRegistry.getInstrumentation().context,
+ assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
+ deviceLocalPath =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/input_screenshots",
+ pathConfig = pathConfig,
+ ) {
+ override fun toString(): String {
+ // This string is appended to all actual/expected screenshots on the device, so make sure
+ // it is a static value.
+ return "InputGoldenImagePathManager"
+ }
+} \ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
new file mode 100644
index 000000000000..c2c3d5530a00
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.input.screenshot
+
+import android.content.Context
+import android.graphics.Bitmap
+import androidx.activity.ComponentActivity
+import androidx.compose.foundation.Image
+import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.graphics.asImageBitmap
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.MaterialYouColorsRule
+import platform.test.screenshot.ScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** A rule for Input screenshot diff tests. */
+class InputScreenshotTestRule(
+ emulationSpec: DeviceEmulationSpec,
+ assetsPathRelativeToBuildRoot: String
+) : TestRule {
+ private val colorsRule = MaterialYouColorsRule()
+ private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
+ private val screenshotRule =
+ ScreenshotTestRule(
+ InputGoldenImagePathManager(
+ getEmulatedDevicePathConfig(emulationSpec),
+ assetsPathRelativeToBuildRoot
+ )
+ )
+ private val composeRule = createAndroidComposeRule<ComponentActivity>()
+ private val delegateRule =
+ RuleChain.outerRule(colorsRule)
+ .around(deviceEmulationRule)
+ .around(screenshotRule)
+ .around(composeRule)
+ private val matcher = UnitTestBitmapMatcher
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return delegateRule.apply(base, description)
+ }
+
+ /**
+ * Compare [content] with the golden image identified by [goldenIdentifier].
+ */
+ fun screenshotTest(
+ goldenIdentifier: String,
+ content: (Context) -> Bitmap,
+ ) {
+ // Make sure that the activity draws full screen and fits the whole display.
+ val activity = composeRule.activity
+ activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
+
+ // Set the content using the AndroidComposeRule to make sure that the Activity is set up
+ // correctly.
+ composeRule.setContent {
+ Image(
+ bitmap = content(activity).asImageBitmap(),
+ contentDescription = null,
+ )
+ }
+ composeRule.waitForIdle()
+
+ val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
+ screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
+ }
+} \ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt
new file mode 100644
index 000000000000..e85578663764
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.input.screenshot
+
+import android.content.Context
+import android.hardware.input.KeyboardLayout
+import android.os.LocaleList
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.hardware.input.Flags
+import java.util.Locale
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for Keyboard layout preview for Ansi physical layout. */
+@RunWith(Parameterized::class)
+class KeyboardLayoutPreviewAnsiScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getTestSpecs() = DeviceEmulationSpec.PhoneMinimal
+ }
+
+ val setFlagsRule = SetFlagsRule()
+ val screenshotRule = InputScreenshotTestRule(
+ emulationSpec,
+ "frameworks/base/tests/InputScreenshotTest/assets"
+ )
+
+ @get:Rule
+ val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule)
+
+ @Test
+ fun test() {
+ setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
+ screenshotRule.screenshotTest("layout-preview-ansi") {
+ context: Context -> LayoutPreview.createLayoutPreview(
+ context,
+ KeyboardLayout(
+ "descriptor",
+ "layout",
+ /* collection= */null,
+ /* priority= */0,
+ LocaleList(Locale.US),
+ /* layoutType= */0,
+ /* vid= */0,
+ /* pid= */0
+ )
+ )
+ }
+ }
+
+} \ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
new file mode 100644
index 000000000000..8ae6dfd8b63b
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.input.screenshot
+
+import android.content.Context
+import android.hardware.input.KeyboardLayout
+import android.os.LocaleList
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.hardware.input.Flags
+import java.util.Locale
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for Keyboard layout preview for Iso physical layout. */
+@RunWith(Parameterized::class)
+class KeyboardLayoutPreviewIsoScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
+ }
+
+ val setFlagsRule = SetFlagsRule()
+ val screenshotRule = InputScreenshotTestRule(
+ emulationSpec,
+ "frameworks/base/tests/InputScreenshotTest/assets"
+ )
+
+ @get:Rule
+ val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule)
+
+ @Test
+ fun test() {
+ setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
+ screenshotRule.screenshotTest("layout-preview") {
+ context: Context -> LayoutPreview.createLayoutPreview(context, null)
+ }
+ }
+
+} \ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt
new file mode 100644
index 000000000000..5231c14bfc9a
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.input.screenshot
+
+import android.content.Context
+import android.hardware.input.KeyboardLayout
+import android.os.LocaleList
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.hardware.input.Flags
+import java.util.Locale
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for Keyboard layout preview for JIS physical layout. */
+@RunWith(Parameterized::class)
+class KeyboardLayoutPreviewJisScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getTestSpecs() = DeviceEmulationSpec.PhoneMinimal
+ }
+
+ val setFlagsRule = SetFlagsRule()
+ val screenshotRule = InputScreenshotTestRule(
+ emulationSpec,
+ "frameworks/base/tests/InputScreenshotTest/assets"
+ )
+
+ @get:Rule
+ val ruleChain = RuleChain.outerRule(screenshotRule).around(setFlagsRule)
+
+ @Test
+ fun test() {
+ setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG)
+ screenshotRule.screenshotTest("layout-preview-jis") {
+ context: Context -> LayoutPreview.createLayoutPreview(
+ context,
+ KeyboardLayout(
+ "descriptor",
+ "layout",
+ /* collection= */null,
+ /* priority= */0,
+ LocaleList(Locale.JAPAN),
+ /* layoutType= */0,
+ /* vid= */0,
+ /* pid= */0
+ )
+ )
+ }
+ }
+
+} \ No newline at end of file
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/LayoutPreview.kt b/tests/InputScreenshotTest/src/android/input/screenshot/LayoutPreview.kt
new file mode 100644
index 000000000000..76ee3791011b
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/LayoutPreview.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 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.
+ */
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.hardware.input.InputManager
+import android.hardware.input.KeyboardLayout
+import android.util.TypedValue
+import kotlin.math.roundToInt
+
+object LayoutPreview {
+ fun createLayoutPreview(context: Context, layout: KeyboardLayout?): Bitmap {
+ val im = context.getSystemService(InputManager::class.java)!!
+ val width = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ 600.0F, context.getResources().getDisplayMetrics()).roundToInt()
+ val height = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ 200.0F, context.getResources().getDisplayMetrics()).roundToInt()
+ val drawable = im.getKeyboardLayoutPreview(layout, width, height)!!
+ val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight())
+ drawable.draw(canvas)
+ return bitmap
+ }
+} \ No newline at end of file
diff --git a/tools/aapt2/OWNERS b/tools/aapt2/OWNERS
index 4f655e54a7c2..55bab7d8d74f 100644
--- a/tools/aapt2/OWNERS
+++ b/tools/aapt2/OWNERS
@@ -1,4 +1,4 @@
set noparent
-toddke@google.com
zyy@google.com
-patb@google.com \ No newline at end of file
+patb@google.com
+markpun@google.com