summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/src/android/os/DisplayPerfTest.java8
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java60
-rw-r--r--api/Android.bp1
-rw-r--r--api/api.go39
-rw-r--r--boot/Android.bp4
-rw-r--r--core/api/current.txt53
-rw-r--r--core/api/module-lib-current.txt1
-rw-r--r--core/api/system-current.txt9
-rw-r--r--core/java/android/app/Activity.java23
-rw-r--r--core/java/android/app/ActivityClient.java4
-rw-r--r--core/java/android/app/AppOpsManager.java87
-rw-r--r--core/java/android/app/BroadcastOptions.java34
-rw-r--r--core/java/android/app/ComponentOptions.java34
-rw-r--r--core/java/android/app/IActivityClientController.aidl5
-rw-r--r--core/java/android/app/IBackupAgent.aidl13
-rw-r--r--core/java/android/app/IRequestFinishCallback.aidl2
-rw-r--r--core/java/android/app/IntentService.java3
-rw-r--r--core/java/android/app/Service.java57
-rw-r--r--core/java/android/app/StatusBarManager.java2
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java111
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl3
-rw-r--r--core/java/android/app/backup/BackupAgent.java29
-rw-r--r--core/java/android/app/backup/BackupRestoreEventLogger.aidl19
-rw-r--r--core/java/android/app/backup/BackupRestoreEventLogger.java58
-rw-r--r--core/java/android/app/prediction/AppPredictor.java43
-rw-r--r--core/java/android/app/search/SearchSession.java13
-rw-r--r--core/java/android/app/time/DetectorStatusTypes.java227
-rw-r--r--core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl19
-rw-r--r--core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java363
-rw-r--r--core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl19
-rw-r--r--core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java96
-rw-r--r--core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java66
-rw-r--r--core/java/android/app/time/TimeZoneDetectorStatus.aidl19
-rw-r--r--core/java/android/app/time/TimeZoneDetectorStatus.java124
-rw-r--r--core/java/android/app/timezonedetector/TimeZoneDetector.java4
-rw-r--r--core/java/android/companion/virtual/IVirtualDeviceManager.aidl5
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java22
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceParams.java99
-rw-r--r--core/java/android/content/om/FabricatedOverlay.java32
-rw-r--r--core/java/android/content/pm/PackageManager.java12
-rw-r--r--core/java/android/credentials/ui/Entry.java32
-rw-r--r--core/java/android/credentials/ui/ProviderPendingIntentResponse.java79
-rw-r--r--core/java/android/credentials/ui/UserSelectionDialogResult.java20
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java16
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java45
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java36
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java73
-rw-r--r--core/java/android/os/Process.java16
-rw-r--r--core/java/android/provider/DeviceConfig.java43
-rw-r--r--core/java/android/provider/Settings.java109
-rw-r--r--core/java/android/service/credentials/CredentialProviderService.java13
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java2
-rw-r--r--core/java/android/transparency/BinaryTransparencyManager.java13
-rw-r--r--core/java/android/view/SurfaceView.java10
-rw-r--r--core/java/android/view/ViewRootImpl.java101
-rw-r--r--core/java/android/view/inputmethod/TextAppearanceInfo.java517
-rw-r--r--core/java/android/widget/Editor.java41
-rw-r--r--core/java/android/window/IBackAnimationFinishedCallback.aidl2
-rw-r--r--core/java/android/window/SurfaceSyncGroup.java69
-rw-r--r--core/java/android/window/TaskFragmentInfo.java22
-rw-r--r--core/java/android/window/WindowContainerTransaction.java32
-rw-r--r--core/java/android/window/WindowProvider.java9
-rw-r--r--core/java/android/window/WindowProviderService.java48
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java10
-rw-r--r--core/java/com/android/internal/expresslog/OWNERS1
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java2
-rw-r--r--core/java/com/android/internal/os/BatteryStatsHistory.java10
-rw-r--r--core/java/com/android/internal/os/IBinaryTransparencyService.aidl4
-rw-r--r--core/java/com/android/internal/os/ProcLocksReader.java63
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java26
-rw-r--r--core/java/com/android/internal/os/SafeZipPathValidatorCallback.java67
-rw-r--r--core/java/com/android/internal/os/TimeoutRecord.java13
-rw-r--r--core/jni/OWNERS2
-rw-r--r--core/jni/android_view_InputEventReceiver.cpp4
-rw-r--r--core/jni/android_view_MotionEvent.cpp4
-rw-r--r--core/jni/android_view_MotionEvent.h2
-rw-r--r--core/proto/android/app/location_time_zone_manager.proto2
-rw-r--r--core/proto/android/app/time_zone_detector.proto29
-rw-r--r--core/res/AndroidManifest.xml6
-rw-r--r--core/res/res/anim/dock_bottom_enter.xml24
-rw-r--r--core/res/res/anim/dock_bottom_exit.xml24
-rw-r--r--core/res/res/anim/dock_bottom_exit_keyguard.xml22
-rw-r--r--core/res/res/anim/dock_left_enter.xml24
-rw-r--r--core/res/res/anim/dock_left_exit.xml24
-rw-r--r--core/res/res/anim/dock_right_enter.xml24
-rw-r--r--core/res/res/anim/dock_right_exit.xml24
-rw-r--r--core/res/res/anim/dock_top_enter.xml24
-rw-r--r--core/res/res/anim/dock_top_exit.xml24
-rw-r--r--core/res/res/values/config.xml6
-rw-r--r--core/res/res/values/symbols.xml11
-rw-r--r--core/tests/coretests/src/android/app/activity/BroadcastTest.java12
-rw-r--r--core/tests/coretests/src/android/app/backup/BackupAgentTest.java42
-rw-r--r--core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java65
-rw-r--r--core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java103
-rw-r--r--core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java210
-rw-r--r--core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java68
-rw-r--r--core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java105
-rw-r--r--core/tests/coretests/src/android/text/format/DateFormatTest.java4
-rw-r--r--core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java201
-rw-r--r--core/tests/coretests/src/android/text/format/DateUtilsTest.java10
-rw-r--r--core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java60
-rw-r--r--core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java68
-rw-r--r--core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java244
-rw-r--r--core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java49
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java30
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java16
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java25
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java1
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java93
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java87
-rw-r--r--libs/androidfw/AssetManager2.cpp38
-rw-r--r--libs/androidfw/LoadedArsc.cpp85
-rw-r--r--libs/androidfw/LocaleDataTables.cpp11
-rw-r--r--libs/androidfw/ResourceTypes.cpp65
-rw-r--r--libs/androidfw/TypeWrappers.cpp13
-rw-r--r--libs/androidfw/include/androidfw/LoadedArsc.h8
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h102
-rw-r--r--libs/androidfw/tests/TypeWrappers_test.cpp12
-rw-r--r--media/jni/android_media_tv_Tuner.cpp81
-rw-r--r--packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java73
-rw-r--r--packages/CredentialManager/res/values/themes.xml3
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt28
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt8
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt11
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt5
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt68
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt (renamed from packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt)83
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt (renamed from packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt)6
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt4
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt77
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt75
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt11
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt48
-rw-r--r--packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml4
-rw-r--r--packages/InputDevices/res/values-en-rCA/strings.xml2
-rw-r--r--packages/SettingsLib/Spa/spa/Android.bp1
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle2
-rw-r--r--packages/SettingsLib/Spa/testutils/build.gradle2
-rw-r--r--packages/SettingsLib/SpaPrivileged/Android.bp1
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/Android.bp3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java6
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java8
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/Shell/res/values-en-rCA/strings.xml2
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt10
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt7
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt7
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt18
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt8
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt15
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt7
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt7
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt26
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt28
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt26
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt26
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt28
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt30
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt25
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt31
-rw-r--r--packages/SystemUI/proguard.flags6
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rCA/strings.xml8
-rw-r--r--packages/SystemUI/res/drawable/ic_watch.xml29
-rw-r--r--packages/SystemUI/res/layout/keyguard_status_bar.xml25
-rw-r--r--packages/SystemUI/res/layout/status_bar.xml25
-rw-r--r--packages/SystemUI/res/layout/status_bar_user_chip_container.xml40
-rw-r--r--packages/SystemUI/res/layout/super_notification_shade.xml28
-rw-r--r--packages/SystemUI/res/values/bools.xml3
-rw-r--r--packages/SystemUI/res/values/config.xml20
-rw-r--r--packages/SystemUI/res/values/dimens.xml5
-rw-r--r--packages/SystemUI/res/values/strings.xml9
-rw-r--r--packages/SystemUI/res/values/styles.xml6
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt11
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt8
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt8
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt26
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java25
-rw-r--r--packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt6
-rw-r--r--packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt3
-rw-r--r--packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt6
-rw-r--r--packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java43
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java66
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java98
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING16
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt214
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt122
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt147
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt134
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt112
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java194
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt69
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt145
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt429
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt61
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt191
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt339
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt66
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java118
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt212
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt319
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt102
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt306
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java3
-rw-r--r--packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml2
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java2
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java6
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java27
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java878
-rw-r--r--services/core/java/com/android/server/GestureLauncherService.java8
-rw-r--r--services/core/java/com/android/server/SystemServiceManager.java16
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java323
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java109
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerLocal.java25
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java119
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java43
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java17
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java6
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java3
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java2
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java22
-rw-r--r--services/core/java/com/android/server/am/ForegroundServiceDelegation.java40
-rw-r--r--services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java262
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java18
-rw-r--r--services/core/java/com/android/server/biometrics/TEST_MAPPING3
-rw-r--r--services/core/java/com/android/server/biometrics/log/ALSProbe.java13
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java22
-rw-r--r--services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java11
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java9
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java11
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController2.java13
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerProximityStateController.java92
-rw-r--r--services/core/java/com/android/server/display/brightness/BrightnessEvent.java51
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java16
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java2
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java17
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java6
-rw-r--r--services/core/java/com/android/server/pm/AppsFilterImpl.java25
-rw-r--r--services/core/java/com/android/server/pm/DeletePackageHelper.java1
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java32
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java17
-rw-r--r--services/core/java/com/android/server/pm/InstallingSession.java10
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java8
-rw-r--r--services/core/java/com/android/server/pm/PackageMetrics.java41
-rw-r--r--services/core/java/com/android/server/pm/PackageRemovedInfo.java23
-rw-r--r--services/core/java/com/android/server/pm/UserManagerInternal.java4
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java18
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java3
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java12
-rw-r--r--services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java94
-rw-r--r--services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java194
-rw-r--r--services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java12
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java8
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java9
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java7
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java52
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java7
-rw-r--r--services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java248
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java3
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java86
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java28
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java18
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java66
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java52
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java215
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java6
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java74
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java65
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java2
-rw-r--r--services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java8
-rw-r--r--services/core/java/com/android/server/wm/RefreshRatePolicy.java104
-rw-r--r--services/core/java/com/android/server/wm/Task.java49
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java34
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java31
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java33
-rw-r--r--services/core/jni/com_android_server_am_BatteryStatsService.cpp2
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp5
-rw-r--r--services/credentials/java/com/android/server/credentials/CreateRequestSession.java31
-rw-r--r--services/credentials/java/com/android/server/credentials/GetRequestSession.java45
-rw-r--r--services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java69
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderCreateSession.java81
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderGetSession.java173
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderIntentController.java117
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderSession.java43
-rw-r--r--services/credentials/java/com/android/server/credentials/RequestSession.java8
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java96
-rw-r--r--services/java/com/android/server/SystemServer.java19
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/OWNERS1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java407
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java113
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt1
-rw-r--r--services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java25
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java53
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/display/TestUtils.java16
-rw-r--r--services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java49
-rw-r--r--services/tests/servicestests/src/com/android/server/job/JobStoreTest.java109
-rw-r--r--services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java46
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java175
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java21
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java33
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java37
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java531
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java292
-rw-r--r--services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java3
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java56
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java62
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java195
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java40
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java8
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java2
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java321
-rw-r--r--telephony/java/android/telephony/CellIdentity.java2
-rw-r--r--telephony/java/android/telephony/DomainSelectionService.java14
-rw-r--r--telephony/java/android/telephony/RadioAccessSpecifier.java10
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java44
-rw-r--r--telephony/java/android/telephony/WwanSelectorCallback.java5
-rw-r--r--telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl2
-rw-r--r--telephony/java/com/android/internal/telephony/RILConstants.java4
-rw-r--r--tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java131
-rw-r--r--tools/aapt/ResourceTable.cpp8
-rw-r--r--tools/aapt2/Debug.cpp39
-rw-r--r--tools/aapt2/cmd/Link.h3
-rw-r--r--tools/aapt2/format/binary/BinaryResourceParser.cpp15
-rw-r--r--tools/aapt2/format/binary/ResEntryWriter.cpp100
-rw-r--r--tools/aapt2/format/binary/ResEntryWriter.h97
-rw-r--r--tools/aapt2/format/binary/ResEntryWriter_test.cpp107
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp72
-rw-r--r--tools/aapt2/format/binary/TableFlattener.h3
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt2
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt130
-rw-r--r--tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt159
-rw-r--r--tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt77
-rw-r--r--tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt80
456 files changed, 16649 insertions, 4537 deletions
diff --git a/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java b/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
index 0802072ae144..0cce6ad73eda 100644
--- a/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/DisplayPerfTest.java
@@ -18,11 +18,11 @@ package android.os;
import android.content.Context;
import android.hardware.display.DisplayManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
import android.provider.Settings;
import android.view.Display;
-import androidx.benchmark.BenchmarkState;
-import androidx.benchmark.junit4.BenchmarkRule;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -38,7 +38,7 @@ public final class DisplayPerfTest {
private static final float DELTA = 0.001f;
@Rule
- public final BenchmarkRule mBenchmarkRule = new BenchmarkRule();
+ public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
private DisplayManager mDisplayManager;
private Context mContext;
@@ -51,7 +51,7 @@ public final class DisplayPerfTest {
@Test
public void testBrightnessChanges() throws Exception {
- final BenchmarkState state = mBenchmarkRule.getState();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
Settings.System.putInt(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 53f5c6e64129..f9dd0b379cb2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -327,6 +327,7 @@ class JobConcurrencyManager {
private final ArraySet<ContextAssignment> mRecycledIdle = new ArraySet<>();
private final ArrayList<ContextAssignment> mRecycledPreferredUidOnly = new ArrayList<>();
private final ArrayList<ContextAssignment> mRecycledStoppable = new ArrayList<>();
+ private final AssignmentInfo mRecycledAssignmentInfo = new AssignmentInfo();
private final Pools.Pool<ContextAssignment> mContextAssignmentPool =
new Pools.SimplePool<>(MAX_RETAINED_OBJECTS);
@@ -414,7 +415,7 @@ class JobConcurrencyManager {
@VisibleForTesting
JobConcurrencyManager(JobSchedulerService service, Injector injector) {
mService = service;
- mLock = mService.mLock;
+ mLock = mService.getLock();
mContext = service.getTestableContext();
mInjector = injector;
@@ -693,8 +694,9 @@ class JobConcurrencyManager {
return;
}
- final long minPreferredUidOnlyWaitingTimeMs = prepareForAssignmentDeterminationLocked(
- mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+ prepareForAssignmentDeterminationLocked(
+ mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
+ mRecycledAssignmentInfo);
if (DEBUG) {
Slog.d(TAG, printAssignments("running jobs initial",
@@ -703,7 +705,7 @@ class JobConcurrencyManager {
determineAssignmentsLocked(
mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
- minPreferredUidOnlyWaitingTimeMs);
+ mRecycledAssignmentInfo);
if (DEBUG) {
Slog.d(TAG, printAssignments("running jobs final",
@@ -715,17 +717,18 @@ class JobConcurrencyManager {
carryOutAssignmentChangesLocked(mRecycledChanged);
cleanUpAfterAssignmentChangesLocked(
- mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable);
+ mRecycledChanged, mRecycledIdle, mRecycledPreferredUidOnly, mRecycledStoppable,
+ mRecycledAssignmentInfo);
noteConcurrency();
}
- /** @return the minimum remaining execution time for preferred UID only JobServiceContexts. */
@VisibleForTesting
@GuardedBy("mLock")
- long prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
+ void prepareForAssignmentDeterminationLocked(final ArraySet<ContextAssignment> idle,
final List<ContextAssignment> preferredUidOnly,
- final List<ContextAssignment> stoppable) {
+ final List<ContextAssignment> stoppable,
+ final AssignmentInfo info) {
final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
final List<JobServiceContext> activeServices = mActiveServices;
@@ -755,6 +758,9 @@ class JobConcurrencyManager {
if (js != null) {
mWorkCountTracker.incrementRunningJobCount(jsc.getRunningJobWorkType());
assignment.workType = jsc.getRunningJobWorkType();
+ if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
+ info.numRunningTopEj++;
+ }
}
assignment.preferredUid = jsc.getPreferredUid();
@@ -789,10 +795,11 @@ class JobConcurrencyManager {
}
mWorkCountTracker.onCountDone();
- // Return 0 if there were no preferred UID only contexts to indicate no waiting time due
+ // Set 0 if there were no preferred UID only contexts to indicate no waiting time due
// to such jobs.
- return minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE
- ? 0 : minPreferredUidOnlyWaitingTimeMs;
+ info.minPreferredUidOnlyWaitingTimeMs =
+ minPreferredUidOnlyWaitingTimeMs == Long.MAX_VALUE
+ ? 0 : minPreferredUidOnlyWaitingTimeMs;
}
@VisibleForTesting
@@ -801,7 +808,7 @@ class JobConcurrencyManager {
final ArraySet<ContextAssignment> idle,
final List<ContextAssignment> preferredUidOnly,
final List<ContextAssignment> stoppable,
- long minPreferredUidOnlyWaitingTimeMs) {
+ @NonNull AssignmentInfo info) {
final PendingJobQueue pendingJobQueue = mService.getPendingJobQueue();
final List<JobServiceContext> activeServices = mActiveServices;
pendingJobQueue.resetIterator();
@@ -832,7 +839,7 @@ class JobConcurrencyManager {
// pending jobs that could be designated as waiting too long, and those other jobs
// would only have to wait for the new slots to become available.
final long minWaitingTimeMs =
- Math.min(minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs);
+ Math.min(info.minPreferredUidOnlyWaitingTimeMs, minChangedWaitingTimeMs);
// Find an available slot for nextPending. The context should be one of the following:
// 1. Unused
@@ -861,13 +868,6 @@ class JobConcurrencyManager {
}
}
if (selectedContext == null && stoppable.size() > 0) {
- int topEjCount = 0;
- for (int r = mRunningJobs.size() - 1; r >= 0; --r) {
- JobStatus js = mRunningJobs.valueAt(r);
- if (js.startedAsExpeditedJob && js.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
- topEjCount++;
- }
- }
for (int s = stoppable.size() - 1; s >= 0; --s) {
final ContextAssignment assignment = stoppable.get(s);
final JobStatus runningJob = assignment.context.getRunningJobLocked();
@@ -888,7 +888,8 @@ class JobConcurrencyManager {
final int currentJobBias = mService.evaluateJobBiasLocked(runningJob);
canReplace = runningJob.lastEvaluatedBias < JobInfo.BIAS_TOP_APP // Case 2
|| currentJobBias < JobInfo.BIAS_TOP_APP // Case 3
- || topEjCount > .5 * mWorkTypeConfig.getMaxTotal(); // Case 4
+ // Case 4
+ || info.numRunningTopEj > .5 * mWorkTypeConfig.getMaxTotal();
}
if (!canReplace && mMaxWaitTimeBypassEnabled) { // Case 5
if (nextPending.shouldTreatAsExpeditedJob()) {
@@ -955,7 +956,7 @@ class JobConcurrencyManager {
if (selectedContext != null) {
selectedContext.newJob = nextPending;
preferredUidOnly.remove(selectedContext);
- minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
+ info.minPreferredUidOnlyWaitingTimeMs = newMinPreferredUidOnlyWaitingTimeMs;
}
}
// Make sure to run EJs for the TOP app immediately.
@@ -1072,7 +1073,8 @@ class JobConcurrencyManager {
private void cleanUpAfterAssignmentChangesLocked(final ArraySet<ContextAssignment> changed,
final ArraySet<ContextAssignment> idle,
final List<ContextAssignment> preferredUidOnly,
- final List<ContextAssignment> stoppable) {
+ final List<ContextAssignment> stoppable,
+ final AssignmentInfo assignmentInfo) {
for (int s = stoppable.size() - 1; s >= 0; --s) {
final ContextAssignment assignment = stoppable.get(s);
assignment.clear();
@@ -1093,6 +1095,7 @@ class JobConcurrencyManager {
idle.clear();
stoppable.clear();
preferredUidOnly.clear();
+ assignmentInfo.clear();
mWorkCountTracker.resetStagingCount();
mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
}
@@ -2540,6 +2543,17 @@ class JobConcurrencyManager {
}
}
+ @VisibleForTesting
+ static final class AssignmentInfo {
+ public long minPreferredUidOnlyWaitingTimeMs;
+ public int numRunningTopEj;
+
+ void clear() {
+ minPreferredUidOnlyWaitingTimeMs = 0;
+ numRunningTopEj = 0;
+ }
+ }
+
// TESTING HELPERS
@VisibleForTesting
diff --git a/api/Android.bp b/api/Android.bp
index a3e64a565422..37b5d4c5f47e 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -113,6 +113,7 @@ combined_apis {
"framework-sdksandbox",
"framework-tethering",
"framework-uwb",
+ "framework-virtualization",
"framework-wifi",
"i18n.module.public.api",
],
diff --git a/api/api.go b/api/api.go
index 6a6c493e041a..ba0fdc18d23e 100644
--- a/api/api.go
+++ b/api/api.go
@@ -27,8 +27,16 @@ import (
const art = "art.module.public.api"
const conscrypt = "conscrypt.module.public.api"
const i18n = "i18n.module.public.api"
+const virtualization = "framework-virtualization"
var core_libraries_modules = []string{art, conscrypt, i18n}
+// List of modules that are not yet updatable, and hence they can still compile
+// against hidden APIs. These modules are filtered out when building the
+// updatable-framework-module-impl (because updatable-framework-module-impl is
+// built against module_current SDK). Instead they are directly statically
+// linked into the all-framework-module-lib, which is building against hidden
+// APIs.
+var non_updatable_modules = []string{virtualization}
// The intention behind this soong plugin is to generate a number of "merged"
// API-related modules that would otherwise require a large amount of very
@@ -249,12 +257,31 @@ func createMergedSystemStubs(ctx android.LoadHookContext, modules []string) {
func createMergedFrameworkImpl(ctx android.LoadHookContext, modules []string) {
// This module is for the "framework-all" module, which should not include the core libraries.
modules = removeAll(modules, core_libraries_modules)
- props := libraryProps{}
- props.Name = proptools.StringPtr("all-framework-module-impl")
- props.Static_libs = transformArray(modules, "", ".impl")
- props.Sdk_version = proptools.StringPtr("module_current")
- props.Visibility = []string{"//frameworks/base"}
- ctx.CreateModule(java.LibraryFactory, &props)
+ // Remove the modules that belong to non-updatable APEXes since those are allowed to compile
+ // against unstable APIs.
+ modules = removeAll(modules, non_updatable_modules)
+ // First create updatable-framework-module-impl, which contains all updatable modules.
+ // This module compiles against module_lib SDK.
+ {
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("updatable-framework-module-impl")
+ props.Static_libs = transformArray(modules, "", ".impl")
+ props.Sdk_version = proptools.StringPtr("module_current")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
+
+ // Now create all-framework-module-impl, which contains updatable-framework-module-impl
+ // and all non-updatable modules. This module compiles against hidden APIs.
+ {
+ props := libraryProps{}
+ props.Name = proptools.StringPtr("all-framework-module-impl")
+ props.Static_libs = transformArray(non_updatable_modules, "", ".impl")
+ props.Static_libs = append(props.Static_libs, "updatable-framework-module-impl")
+ props.Sdk_version = proptools.StringPtr("core_platform")
+ props.Visibility = []string{"//frameworks/base"}
+ ctx.CreateModule(java.LibraryFactory, &props)
+ }
}
func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules []string) {
diff --git a/boot/Android.bp b/boot/Android.bp
index 7839918d6a54..6e5291408ffa 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -136,6 +136,10 @@ platform_bootclasspath {
apex: "com.android.car.framework",
module: "com.android.car.framework-bootclasspath-fragment",
},
+ {
+ apex: "com.android.virt",
+ module: "com.android.virt-bootclasspath-fragment",
+ },
],
// Additional information needed by hidden api processing.
diff --git a/core/api/current.txt b/core/api/current.txt
index 6a875b83674c..f80c09596fb1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -17651,6 +17651,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Rational> CONTROL_AE_COMPENSATION_STEP;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AE_LOCK_AVAILABLE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AF_AVAILABLE_MODES;
+ field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AUTOFRAMING_AVAILABLE;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_EFFECTS;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.Capability[]> CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_CAPABILITIES;
field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_MODES;
@@ -17955,6 +17956,12 @@ package android.hardware.camera2 {
field public static final int CONTROL_AF_TRIGGER_CANCEL = 2; // 0x2
field public static final int CONTROL_AF_TRIGGER_IDLE = 0; // 0x0
field public static final int CONTROL_AF_TRIGGER_START = 1; // 0x1
+ field public static final int CONTROL_AUTOFRAMING_AUTO = 2; // 0x2
+ field public static final int CONTROL_AUTOFRAMING_OFF = 0; // 0x0
+ field public static final int CONTROL_AUTOFRAMING_ON = 1; // 0x1
+ field public static final int CONTROL_AUTOFRAMING_STATE_CONVERGED = 2; // 0x2
+ field public static final int CONTROL_AUTOFRAMING_STATE_FRAMING = 1; // 0x1
+ field public static final int CONTROL_AUTOFRAMING_STATE_INACTIVE = 0; // 0x0
field public static final int CONTROL_AWB_MODE_AUTO = 1; // 0x1
field public static final int CONTROL_AWB_MODE_CLOUDY_DAYLIGHT = 6; // 0x6
field public static final int CONTROL_AWB_MODE_DAYLIGHT = 5; // 0x5
@@ -18202,6 +18209,7 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AF_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AF_REGIONS;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AF_TRIGGER;
+ field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AUTOFRAMING;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> CONTROL_AWB_LOCK;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> CONTROL_AWB_MODE;
field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
@@ -18292,6 +18300,8 @@ package android.hardware.camera2 {
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AF_SCENE_CHANGE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AF_STATE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AF_TRIGGER;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AUTOFRAMING;
+ field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AUTOFRAMING_STATE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_AWB_LOCK;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_AWB_MODE;
field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.MeteringRectangle[]> CONTROL_AWB_REGIONS;
@@ -32315,6 +32325,7 @@ package android.os {
method public static final boolean is64Bit();
method public static boolean isApplicationUid(int);
method public static final boolean isIsolated();
+ method public static final boolean isIsolatedUid(int);
method public static final boolean isSdkSandbox();
method public static final void killProcess(int);
method public static final int myPid();
@@ -44029,6 +44040,8 @@ package android.telephony {
field public static final int APPTYPE_USIM = 2; // 0x2
field public static final int AUTHTYPE_EAP_AKA = 129; // 0x81
field public static final int AUTHTYPE_EAP_SIM = 128; // 0x80
+ field public static final int AUTHTYPE_GBA_BOOTSTRAP = 132; // 0x84
+ field public static final int AUTHTYPE_GBA_NAF_KEY_EXTERNAL = 133; // 0x85
field public static final int CALL_COMPOSER_STATUS_OFF = 0; // 0x0
field public static final int CALL_COMPOSER_STATUS_ON = 1; // 0x1
field public static final int CALL_STATE_IDLE = 0; // 0x0
@@ -54030,23 +54043,22 @@ package android.view.inputmethod {
}
public final class TextAppearanceInfo implements android.os.Parcelable {
- ctor public TextAppearanceInfo(@NonNull android.widget.TextView);
method public int describeContents();
- method @Nullable public String getFontFamilyName();
method @Nullable public String getFontFeatureSettings();
method @Nullable public String getFontVariationSettings();
+ method @ColorInt public int getHighlightTextColor();
+ method @ColorInt public int getHintTextColor();
method public float getLetterSpacing();
method public int getLineBreakStyle();
method public int getLineBreakWordStyle();
- method public int getMaxLength();
+ method @ColorInt public int getLinkTextColor();
+ method @ColorInt public int getShadowColor();
method @Px public float getShadowDx();
method @Px public float getShadowDy();
method @Px public float getShadowRadius();
+ method @Nullable public String getSystemFontFamilyName();
method @ColorInt public int getTextColor();
- method @ColorInt public int getTextColorHighlight();
- method @ColorInt public int getTextColorHint();
- method @Nullable public android.content.res.ColorStateList getTextColorLink();
- method @IntRange(from=0xffffffff, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) public int getTextFontWeight();
+ method @IntRange(from=android.graphics.fonts.FontStyle.FONT_WEIGHT_UNSPECIFIED, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) public int getTextFontWeight();
method @NonNull public android.os.LocaleList getTextLocales();
method public float getTextScaleX();
method @Px public float getTextSize();
@@ -54058,6 +54070,33 @@ package android.view.inputmethod {
field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextAppearanceInfo> CREATOR;
}
+ public static final class TextAppearanceInfo.Builder {
+ ctor public TextAppearanceInfo.Builder();
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo build();
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setAllCaps(boolean);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setElegantTextHeight(boolean);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFallbackLineSpacing(boolean);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFontFeatureSettings(@Nullable String);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setFontVariationSettings(@Nullable String);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setHighlightTextColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setHintTextColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLetterSpacing(float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLineBreakStyle(int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLineBreakWordStyle(int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setLinkTextColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowDx(@Px float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowDy(@Px float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setShadowRadius(@Px float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setSystemFontFamilyName(@Nullable String);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextColor(@ColorInt int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextFontWeight(@IntRange(from=android.graphics.fonts.FontStyle.FONT_WEIGHT_UNSPECIFIED, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) int);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextLocales(@NonNull android.os.LocaleList);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextScaleX(float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextSize(@Px float);
+ method @NonNull public android.view.inputmethod.TextAppearanceInfo.Builder setTextStyle(int);
+ }
+
public final class TextAttribute implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.os.PersistableBundle getExtras();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index e928eb5f7731..e6ddf9f6daf0 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -413,6 +413,7 @@ package android.provider {
public final class DeviceConfig {
field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
+ field public static final String NAMESPACE_APP_CLONING = "app_cloning";
field public static final String NAMESPACE_APP_STANDBY = "app_standby";
field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ce1eff143185..cb9b76dd5c22 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1109,6 +1109,7 @@ package android.app.admin {
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void finalizeWorkProfileProvisioning(@NonNull android.os.UserHandle, @Nullable android.accounts.Account);
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public java.util.Set<java.lang.Integer> getApplicationExemptions(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
@@ -1134,6 +1135,7 @@ package android.app.admin {
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS, android.Manifest.permission.PROVISION_DEMO_DEVICE}) public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
method @RequiresPermission(android.Manifest.permission.TRIGGER_LOST_MODE) public void sendLostModeLocationUpdate(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
+ method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean);
method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
@@ -1155,6 +1157,7 @@ package android.app.admin {
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
field @RequiresPermission(android.Manifest.permission.LAUNCH_DEVICE_MANAGER_SETUP) public static final String ACTION_UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_POLICY_MANAGEMENT_ROLE_HOLDER";
+ field public static final int EXEMPT_FROM_APP_STANDBY = 0; // 0x0
field public static final String EXTRA_FORCE_UPDATE_ROLE_HOLDER = "android.app.extra.FORCE_UPDATE_ROLE_HOLDER";
field public static final String EXTRA_LOST_MODE_LOCATION = "android.app.extra.LOST_MODE_LOCATION";
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
@@ -2909,6 +2912,7 @@ package android.companion.virtual {
method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
method public int getDefaultActivityPolicy();
method public int getDefaultNavigationPolicy();
+ method public int getDevicePolicy(int);
method public int getLockState();
method @Nullable public String getName();
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
@@ -2916,14 +2920,18 @@ package android.companion.virtual {
field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.VirtualDeviceParams> CREATOR;
+ field public static final int DEVICE_POLICY_CUSTOM = 1; // 0x1
+ field public static final int DEVICE_POLICY_DEFAULT = 0; // 0x0
field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1
field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+ field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
}
public static final class VirtualDeviceParams.Builder {
ctor public VirtualDeviceParams.Builder();
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int);
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
@@ -3502,6 +3510,7 @@ package android.content.pm {
field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg";
+ field public static final String FEATURE_VIRTUALIZATION_FRAMEWORK = "android.software.virtualization_framework";
field public static final int FLAGS_PERMISSION_RESERVED_PERMISSION_CONTROLLER = -268435456; // 0xf0000000
field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
field public static final int FLAG_PERMISSION_AUTO_REVOKED = 131072; // 0x20000
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 501b136726d3..30ff05284f3a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1685,7 +1685,7 @@ public class Activity extends ContextThemeWrapper
.isOnBackInvokedCallbackEnabled(this);
if (aheadOfTimeBack) {
// Add onBackPressed as default back behavior.
- mDefaultBackCallback = this::navigateBack;
+ mDefaultBackCallback = this::onBackInvoked;
getOnBackInvokedDispatcher().registerSystemOnBackInvokedCallback(mDefaultBackCallback);
}
}
@@ -4003,22 +4003,19 @@ public class Activity extends ContextThemeWrapper
if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) {
return;
}
- navigateBack();
+ onBackInvoked();
}
- private void navigateBack() {
- if (!isTaskRoot()) {
- // If the activity is not the root of the task, allow finish to proceed normally.
- finishAfterTransition();
- return;
- }
- // Inform activity task manager that the activity received a back press while at the
- // root of the task. This call allows ActivityTaskManager to intercept or move the task
- // to the back.
- ActivityClient.getInstance().onBackPressedOnTaskRoot(mToken,
+ private void onBackInvoked() {
+ // Inform activity task manager that the activity received a back press.
+ // This call allows ActivityTaskManager to intercept or move the task
+ // to the back when needed.
+ ActivityClient.getInstance().onBackPressed(mToken,
new RequestFinishCallback(new WeakReference<>(this)));
- getAutofillClientController().onActivityBackPressed(mIntent);
+ if (isTaskRoot()) {
+ getAutofillClientController().onActivityBackPressed(mIntent);
+ }
}
/**
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index d1e6780e3618..4cf48abc2ed3 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -525,9 +525,9 @@ public class ActivityClient {
}
}
- void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) {
+ void onBackPressed(IBinder token, IRequestFinishCallback callback) {
try {
- getActivityClientController().onBackPressedOnTaskRoot(token, callback);
+ getActivityClientController().onBackPressed(token, callback);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1b3282e752f4..d5879fb523ce 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1407,9 +1407,41 @@ public class AppOpsManager {
public static final int OP_FOREGROUND_SERVICE_SPECIAL_USE =
AppProtoEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
+ /**
+ * Exempt from start foreground service from background restriction.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION =
+ AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION;
+
+ /**
+ * Exempt from start foreground service from background with while in user permission
+ * restriction.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION =
+ AppProtoEnums
+ .APP_OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION;
+
+ /**
+ * Hide foreground service stop button in quick settings.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final int OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
+ AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 128;
+ public static final int _NUM_OP = 131;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1947,6 +1979,38 @@ public class AppOpsManager {
public static final String OPSTR_FOREGROUND_SERVICE_SPECIAL_USE =
"android:foreground_service_special_use";
+ /**
+ * Exempt from start foreground service from background restriction.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION =
+ "android:system_exempt_from_fgs_bg_start_restriction";
+
+ /**
+ * Exempt from start foreground service from background with while in user permission
+ * restriction.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION =
+ "android:system_exempt_from_fgs_bg_start_while_in_use_permission_restriction";
+
+ /**
+ * Hide foreground service stop button in quick settings.
+ *
+ * Only to be used by the system.
+ *
+ * @hide
+ */
+ public static final String OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON =
+ "android:system_exempt_from_fgs_stop_button";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2441,6 +2505,17 @@ public class AppOpsManager {
new AppOpInfo.Builder(OP_FOREGROUND_SERVICE_SPECIAL_USE,
OPSTR_FOREGROUND_SERVICE_SPECIAL_USE, "FOREGROUND_SERVICE_SPECIAL_USE")
.setPermission(Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE).build(),
+ new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION,
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION,
+ "SYSTEM_EXEMPT_FROM_FGS_BG_START_RESTRICTION").build(),
+ new AppOpInfo.Builder(
+ OP_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION,
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION,
+ "SYSTEM_EXEMPT_FROM_FGS_BG_START_WHILE_IN_USE_PERMISSION_RESTRICTION")
+ .build(),
+ new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
+ OPSTR_SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON,
+ "SYSTEM_EXEMPT_FROM_FGS_STOP_BUTTON").build()
};
// The number of longs needed to form a full bitmask of app ops
@@ -2498,12 +2573,6 @@ public class AppOpsManager {
sPermToOp.put(sAppOpInfos[op].permission, op);
}
}
-
- if ((_NUM_OP + Long.SIZE - 1) / Long.SIZE != 2) {
- // The code currently assumes that the length of sAppOpsNotedInThisBinderTransaction is
- // two longs
- throw new IllegalStateException("notedAppOps collection code assumes < 128 appops");
- }
}
/** Config used to control app ops access messages sampling */
@@ -2603,8 +2672,8 @@ public class AppOpsManager {
if (boxedOpCode != null) {
return boxedOpCode;
}
- if (HealthConnectManager.isHealthPermission(ActivityThread.currentApplication(),
- permission)) {
+ if (permission != null && HealthConnectManager.isHealthPermission(
+ ActivityThread.currentApplication(), permission)) {
return OP_READ_WRITE_HEALTH_DATA;
}
return OP_NONE;
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 45d44589b2d8..1777f37202c2 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -63,7 +63,6 @@ public class BroadcastOptions extends ComponentOptions {
private long mRequireCompatChangeId = CHANGE_INVALID;
private boolean mRequireCompatChangeEnabled = true;
private boolean mIsAlarmBroadcast = false;
- private boolean mIsInteractiveBroadcast = false;
private long mIdForResponseEvent;
private @Nullable IntentFilter mRemoveMatchingFilter;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
@@ -171,13 +170,6 @@ public class BroadcastOptions extends ComponentOptions {
"android:broadcast.is_alarm";
/**
- * Corresponds to {@link #setInteractiveBroadcast(boolean)}
- * @hide
- */
- public static final String KEY_INTERACTIVE_BROADCAST =
- "android:broadcast.is_interactive";
-
- /**
* @hide
* @deprecated Use {@link android.os.PowerExemptionManager#
* TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead.
@@ -308,7 +300,6 @@ public class BroadcastOptions extends ComponentOptions {
mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
- mIsInteractiveBroadcast = opts.getBoolean(KEY_INTERACTIVE_BROADCAST, false);
mRemoveMatchingFilter = opts.getParcelable(KEY_REMOVE_MATCHING_FILTER,
IntentFilter.class);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
@@ -629,28 +620,6 @@ public class BroadcastOptions extends ComponentOptions {
}
/**
- * When set, this broadcast will be understood as having originated from
- * some direct interaction by the user such as a notification tap or button
- * press. Only the OS itself may use this option.
- * @hide
- * @param broadcastIsInteractive
- * @see #isInteractiveBroadcast()
- */
- @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE)
- public void setInteractiveBroadcast(boolean broadcastIsInteractive) {
- mIsInteractiveBroadcast = broadcastIsInteractive;
- }
-
- /**
- * Did this broadcast originate with a direct user interaction?
- * @return true if this broadcast is the result of an interaction, false otherwise
- * @hide
- */
- public boolean isInteractiveBroadcast() {
- return mIsInteractiveBroadcast;
- }
-
- /**
* Did this broadcast originate from a push message from the server?
*
* @return true if this broadcast is a push message, false otherwise.
@@ -837,9 +806,6 @@ public class BroadcastOptions extends ComponentOptions {
if (mIsAlarmBroadcast) {
b.putBoolean(KEY_ALARM_BROADCAST, true);
}
- if (mIsInteractiveBroadcast) {
- b.putBoolean(KEY_INTERACTIVE_BROADCAST, true);
- }
if (mMinManifestReceiverApiLevel != 0) {
b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
}
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index 4e5e384a2798..74db39f63830 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.RequiresPermission;
import android.os.Bundle;
/**
@@ -45,8 +46,15 @@ public class ComponentOptions {
public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION =
"android.pendingIntent.backgroundActivityAllowedByPermission";
+ /**
+ * Corresponds to {@link #setInteractive(boolean)}
+ * @hide
+ */
+ public static final String KEY_INTERACTIVE = "android:component.isInteractive";
+
private boolean mPendingIntentBalAllowed = PENDING_INTENT_BAL_ALLOWED_DEFAULT;
private boolean mPendingIntentBalAllowedByPermission = false;
+ private boolean mIsInteractive = false;
ComponentOptions() {
}
@@ -61,6 +69,29 @@ public class ComponentOptions {
setPendingIntentBackgroundActivityLaunchAllowedByPermission(
opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
false));
+ mIsInteractive = opts.getBoolean(KEY_INTERACTIVE, false);
+ }
+
+ /**
+ * When set, a broadcast will be understood as having originated from
+ * some direct interaction by the user such as a notification tap or button
+ * press. Only the OS itself may use this option.
+ * @hide
+ * @param interactive
+ * @see #isInteractive()
+ */
+ @RequiresPermission(android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE)
+ public void setInteractive(boolean interactive) {
+ mIsInteractive = interactive;
+ }
+
+ /**
+ * Did this PendingIntent send originate with a direct user interaction?
+ * @return true if this is the result of an interaction, false otherwise
+ * @hide
+ */
+ public boolean isInteractive() {
+ return mIsInteractive;
}
/**
@@ -103,6 +134,9 @@ public class ComponentOptions {
b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
mPendingIntentBalAllowedByPermission);
}
+ if (mIsInteractive) {
+ b.putBoolean(KEY_INTERACTIVE, true);
+ }
return b;
}
}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 9aa67bc51182..62481ba8f251 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -145,10 +145,9 @@ interface IActivityClientController {
void unregisterRemoteAnimations(in IBinder token);
/**
- * Reports that an Activity received a back key press when there were no additional activities
- * on the back stack.
+ * Reports that an Activity received a back key press.
*/
- oneway void onBackPressedOnTaskRoot(in IBinder activityToken,
+ oneway void onBackPressed(in IBinder activityToken,
in IRequestFinishCallback callback);
/** Reports that the splash screen view has attached to activity. */
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 37c5cabc2376..811118479ef8 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -16,9 +16,12 @@
package android.app;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.IBackupCallback;
import android.app.backup.IBackupManager;
import android.os.ParcelFileDescriptor;
+
+import com.android.internal.infra.AndroidFuture;
/**
* Interface presented by applications being asked to participate in the
@@ -193,4 +196,14 @@ oneway interface IBackupAgent {
* @param message The message to be passed to the agent's application in an exception.
*/
void fail(String message);
+
+ /**
+ * Provides the logging results that were accumulated in the BackupAgent during a backup or
+ * restore operation. This method should be called after the agent completes its backup or
+ * restore.
+ *
+ * @param resultsFuture a future that is completed with the logging results.
+ */
+ void getLoggerResults(
+ in AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> resultsFuture);
}
diff --git a/core/java/android/app/IRequestFinishCallback.aidl b/core/java/android/app/IRequestFinishCallback.aidl
index 22c20c840e99..72426df84b75 100644
--- a/core/java/android/app/IRequestFinishCallback.aidl
+++ b/core/java/android/app/IRequestFinishCallback.aidl
@@ -18,7 +18,7 @@ package android.app;
/**
* This callback allows ActivityTaskManager to ask the calling Activity
- * to finish in response to a call to onBackPressedOnTaskRoot.
+ * to finish in response to a call to onBackPressed.
*
* {@hide}
*/
diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java
index 2e833084641c..99f864c32803 100644
--- a/core/java/android/app/IntentService.java
+++ b/core/java/android/app/IntentService.java
@@ -57,8 +57,7 @@ import android.os.Message;
* @deprecated IntentService is subject to all the
* <a href="{@docRoot}about/versions/oreo/background.html">background execution limits</a>
* imposed with Android 8.0 (API level 26). Consider using {@link androidx.work.WorkManager}
- * or {@link androidx.core.app.JobIntentService}, which uses jobs
- * instead of services when running on Android 8.0 or higher.
+ * instead.
*/
@Deprecated
public abstract class IntentService extends Service {
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 01e4b150fd7c..7167d4f17a6d 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -17,6 +17,8 @@
package android.app;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.text.TextUtils.formatSimple;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -34,6 +36,7 @@ import android.content.res.Configuration;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.ArrayMap;
import android.util.Log;
import android.view.contentcapture.ContentCaptureManager;
@@ -763,10 +766,12 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
*/
public final void startForeground(int id, Notification notification) {
try {
+ final ComponentName comp = new ComponentName(this, mClassName);
mActivityManager.setServiceForeground(
- new ComponentName(this, mClassName), mToken, id,
+ comp, mToken, id,
notification, 0, FOREGROUND_SERVICE_TYPE_MANIFEST);
clearStartForegroundServiceStackTrace();
+ logForegroundServiceStart(comp, FOREGROUND_SERVICE_TYPE_MANIFEST);
} catch (RemoteException ex) {
}
}
@@ -843,10 +848,12 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
public final void startForeground(int id, @NonNull Notification notification,
@RequiresPermission @ForegroundServiceType int foregroundServiceType) {
try {
+ final ComponentName comp = new ComponentName(this, mClassName);
mActivityManager.setServiceForeground(
- new ComponentName(this, mClassName), mToken, id,
+ comp, mToken, id,
notification, 0, foregroundServiceType);
clearStartForegroundServiceStackTrace();
+ logForegroundServiceStart(comp, foregroundServiceType);
} catch (RemoteException ex) {
}
}
@@ -897,6 +904,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
mActivityManager.setServiceForeground(
new ComponentName(this, mClassName), mToken, 0, null,
notificationBehavior, 0);
+ logForegroundServiceStopIfNecessary();
} catch (RemoteException ex) {
}
}
@@ -992,6 +1000,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
*/
public final void detachAndCleanUp() {
mToken = null;
+ logForegroundServiceStopIfNecessary();
}
final String getClassName() {
@@ -1025,6 +1034,50 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
private boolean mStartCompatibility = false;
/**
+ * This will be set to the title of the system trace when this service is started as
+ * a foreground service, and will be set to null when it's no longer in foreground
+ * service state.
+ */
+ @GuardedBy("mForegroundServiceTraceTitleLock")
+ private @Nullable String mForegroundServiceTraceTitle = null;
+
+ private final Object mForegroundServiceTraceTitleLock = new Object();
+
+ private static final String TRACE_TRACK_NAME_FOREGROUND_SERVICE = "FGS";
+
+ private void logForegroundServiceStart(ComponentName comp,
+ @ForegroundServiceType int foregroundServiceType) {
+ synchronized (mForegroundServiceTraceTitleLock) {
+ if (mForegroundServiceTraceTitle == null) {
+ mForegroundServiceTraceTitle = formatSimple("comp=%s type=%s",
+ comp.toShortString(), Integer.toHexString(foregroundServiceType));
+ // The service is not in foreground state, emit a start event.
+ Trace.asyncTraceForTrackBegin(TRACE_TAG_ACTIVITY_MANAGER,
+ TRACE_TRACK_NAME_FOREGROUND_SERVICE,
+ mForegroundServiceTraceTitle,
+ System.identityHashCode(this));
+ } else {
+ // The service is already in foreground state, emit an one-off event.
+ Trace.instantForTrack(TRACE_TAG_ACTIVITY_MANAGER,
+ TRACE_TRACK_NAME_FOREGROUND_SERVICE,
+ mForegroundServiceTraceTitle);
+ }
+ }
+ }
+
+ private void logForegroundServiceStopIfNecessary() {
+ synchronized (mForegroundServiceTraceTitleLock) {
+ if (mForegroundServiceTraceTitle != null) {
+ Trace.asyncTraceForTrackEnd(TRACE_TAG_ACTIVITY_MANAGER,
+ TRACE_TRACK_NAME_FOREGROUND_SERVICE,
+ mForegroundServiceTraceTitle,
+ System.identityHashCode(this));
+ mForegroundServiceTraceTitle = null;
+ }
+ }
+ }
+
+ /**
* This keeps track of the stacktrace where Context.startForegroundService() was called
* for each service class. We use that when we crash the app for not calling
* {@link #startForeground} in time, in {@link ActivityThread#throwRemoteServiceException}.
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 6d289726baf5..a035375f2f82 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -229,6 +229,8 @@ public class StatusBarManager {
public static final int CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = 1;
/** @hide */
public static final int CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = 2;
+ /** @hide */
+ public static final int CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE = 3;
/**
* Session flag for {@link #registerSessionListener} indicating the listener
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 6fedb41884ec..be4df9d24c25 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -103,6 +103,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.net.NetworkUtilsInternal;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.org.conscrypt.TrustedCertificateStore;
@@ -3823,6 +3824,27 @@ public class DevicePolicyManager {
public static final int OPERATION_SAFETY_REASON_DRIVING_DISTRACTION = 1;
/**
+ * Prevent an app from being placed into app standby buckets, such that it will not be subject
+ * to device resources restrictions as a result of app standby buckets.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int EXEMPT_FROM_APP_STANDBY = 0;
+
+ /**
+ * Exemptions to platform restrictions, given to an application through
+ * {@link #setApplicationExemptions(String, Set)}.
+ *
+ * @hide
+ */
+ @IntDef(prefix = { "EXEMPT_FROM_"}, value = {
+ EXEMPT_FROM_APP_STANDBY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ApplicationExemptionConstants {}
+
+ /**
* Broadcast action: notify system apps (e.g. settings, SysUI, etc) that the device management
* resources with IDs {@link #EXTRA_RESOURCE_IDS} has been updated, the updated resources can be
* retrieved using {@link DevicePolicyResourcesManager#getDrawable} and
@@ -14727,6 +14749,95 @@ public class DevicePolicyManager {
}
/**
+ * Service-specific error code used in {@link #setApplicationExemptions(String, Set)} and
+ * {@link #getApplicationExemptions(String)}.
+ * @hide
+ */
+ public static final int ERROR_PACKAGE_NAME_NOT_FOUND = 1;
+
+ /**
+ * Called by an application with the
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS} permission, to
+ * grant platform restriction exemptions to a given application.
+ *
+ * @param packageName The package name of the application to be exempt.
+ * @param exemptions The set of exemptions to be applied.
+ * @throws SecurityException If the caller does not have
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS}
+ * @throws NameNotFoundException If either the package is not installed or the package is not
+ * visible to the caller.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS)
+ public void setApplicationExemptions(@NonNull String packageName,
+ @NonNull @ApplicationExemptionConstants Set<Integer> exemptions)
+ throws NameNotFoundException {
+ throwIfParentInstance("setApplicationExemptions");
+ if (mService != null) {
+ try {
+ mService.setApplicationExemptions(packageName,
+ ArrayUtils.convertToIntArray(new ArraySet<>(exemptions)));
+ } catch (ServiceSpecificException e) {
+ switch (e.errorCode) {
+ case ERROR_PACKAGE_NAME_NOT_FOUND:
+ throw new NameNotFoundException(e.getMessage());
+ default:
+ throw new RuntimeException(
+ "Unknown error setting application exemptions: " + e.errorCode, e);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns all the platform restriction exemptions currently applied to an application. Called
+ * by an application with the
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS} permission.
+ *
+ * @param packageName The package name to check.
+ * @return A set of platform restrictions an application is exempt from.
+ * @throws SecurityException If the caller does not have
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APP_EXEMPTIONS}
+ * @throws NameNotFoundException If either the package is not installed or the package is not
+ * visible to the caller.
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS)
+ public Set<Integer> getApplicationExemptions(@NonNull String packageName)
+ throws NameNotFoundException {
+ throwIfParentInstance("getApplicationExemptions");
+ if (mService == null) {
+ return Collections.emptySet();
+ }
+ try {
+ return intArrayToSet(mService.getApplicationExemptions(packageName));
+ } catch (ServiceSpecificException e) {
+ switch (e.errorCode) {
+ case ERROR_PACKAGE_NAME_NOT_FOUND:
+ throw new NameNotFoundException(e.getMessage());
+ default:
+ throw new RuntimeException(
+ "Unknown error getting application exemptions: " + e.errorCode, e);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private Set<Integer> intArrayToSet(int[] array) {
+ Set<Integer> set = new ArraySet<>();
+ for (int item : array) {
+ set.add(item);
+ }
+ return set;
+ }
+
+ /**
* Called by a device owner or a profile owner to disable user control over apps. User will not
* be able to clear app data or force-stop packages. When called by a device owner, applies to
* all users on the device. Starting from Android 13, packages with user control disabled are
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 6c27dd7b771b..8a4026539267 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -568,4 +568,7 @@ interface IDevicePolicyManager {
boolean shouldAllowBypassingDevicePolicyManagementRoleQualification();
List<UserHandle> getPolicyManagedProfiles(in UserHandle userHandle);
+
+ void setApplicationExemptions(String packageName, in int[]exemptions);
+ int[] getApplicationExemptions(String packageName);
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index b1b59b0e39b1..a4f612d7faee 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -41,6 +41,7 @@ import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import libcore.io.IoUtils;
@@ -202,6 +203,7 @@ public abstract class BackupAgent extends ContextWrapper {
Handler mHandler = null;
+ @Nullable private volatile BackupRestoreEventLogger mLogger = null;
@Nullable private UserHandle mUser;
// This field is written from the main thread (in onCreate), and read in a Binder thread (in
// onFullBackup that is called from system_server via Binder).
@@ -234,6 +236,20 @@ public abstract class BackupAgent extends ContextWrapper {
} catch (InterruptedException e) { /* ignored */ }
}
+ /**
+ * Get a logger to record app-specific backup and restore events that are happening during a
+ * backup or restore operation.
+ *
+ * <p>The logger instance had been created by the system with the correct {@link
+ * BackupRestoreEventLogger.OperationType} that corresponds to the operation the {@code
+ * BackupAgent} is currently handling.
+ *
+ * @hide
+ */
+ @Nullable
+ public BackupRestoreEventLogger getBackupRestoreEventLogger() {
+ return mLogger;
+ }
public BackupAgent() {
super(null);
@@ -264,6 +280,9 @@ public abstract class BackupAgent extends ContextWrapper {
* @hide
*/
public void onCreate(UserHandle user, @OperationType int operationType) {
+ // TODO: Instantiate with the correct type using a parameter.
+ mLogger = new BackupRestoreEventLogger(BackupRestoreEventLogger.OperationType.BACKUP);
+
onCreate();
mUser = user;
@@ -1305,6 +1324,16 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
}
+
+ @Override
+ public void getLoggerResults(
+ AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> in) {
+ if (mLogger != null) {
+ in.complete(mLogger.getLoggingResults());
+ } else {
+ in.complete(Collections.emptyList());
+ }
+ }
}
static class FailRunnable implements Runnable {
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.aidl b/core/java/android/app/backup/BackupRestoreEventLogger.aidl
new file mode 100644
index 000000000000..d6ef4e64258d
--- /dev/null
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+parcelable BackupRestoreEventLogger.DataTypeResult; \ No newline at end of file
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index 6f62c8a03078..68740cb3c086 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -19,6 +19,10 @@ package android.app.backup;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
import android.util.Slog;
import java.lang.annotation.Retention;
@@ -312,7 +316,7 @@ public class BackupRestoreEventLogger {
/**
* Encapsulate logging results for a single data type.
*/
- public static class DataTypeResult {
+ public static class DataTypeResult implements Parcelable {
@BackupRestoreDataType
private final String mDataType;
private int mSuccessCount;
@@ -362,5 +366,57 @@ public class BackupRestoreEventLogger {
public byte[] getMetadataHash() {
return mMetadataHash;
}
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mDataType);
+
+ dest.writeInt(mSuccessCount);
+
+ dest.writeInt(mFailCount);
+
+ Bundle errorsBundle = new Bundle();
+ for (Map.Entry<String, Integer> e : mErrors.entrySet()) {
+ errorsBundle.putInt(e.getKey(), e.getValue());
+ }
+ dest.writeBundle(errorsBundle);
+
+ dest.writeByteArray(mMetadataHash);
+ }
+
+ public static final Parcelable.Creator<DataTypeResult> CREATOR =
+ new Parcelable.Creator<>() {
+ public DataTypeResult createFromParcel(Parcel in) {
+ String dataType = in.readString();
+
+ int successCount = in.readInt();
+
+ int failCount = in.readInt();
+
+ Map<String, Integer> errors = new ArrayMap<>();
+ Bundle errorsBundle = in.readBundle(getClass().getClassLoader());
+ for (String key : errorsBundle.keySet()) {
+ errors.put(key, errorsBundle.getInt(key));
+ }
+
+ byte[] metadataHash = in.createByteArray();
+
+ DataTypeResult result = new DataTypeResult(dataType);
+ result.mSuccessCount = successCount;
+ result.mFailCount = failCount;
+ result.mErrors.putAll(errors);
+ result.mMetadataHash = metadataHash;
+ return result;
+ }
+
+ public DataTypeResult[] newArray(int size) {
+ return new DataTypeResult[size];
+ }
+ };
}
}
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index 2581daa2f68b..d628b7f92c6c 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -30,6 +30,8 @@ import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
+
import dalvik.system.CloseGuard;
import java.util.List;
@@ -79,6 +81,7 @@ public final class AppPredictor {
private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
private final AppPredictionSessionId mSessionId;
+ @GuardedBy("itself")
private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
/**
@@ -94,7 +97,7 @@ public final class AppPredictor {
IBinder b = ServiceManager.getService(Context.APP_PREDICTION_SERVICE);
mPredictionManager = IPredictionManager.Stub.asInterface(b);
mSessionId = new AppPredictionSessionId(
- context.getPackageName() + ":" + UUID.randomUUID().toString(), context.getUserId());
+ context.getPackageName() + ":" + UUID.randomUUID(), context.getUserId());
try {
mPredictionManager.createPredictionSession(predictionContext, mSessionId, getToken());
} catch (RemoteException e) {
@@ -155,6 +158,15 @@ public final class AppPredictor {
*/
public void registerPredictionUpdates(@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull AppPredictor.Callback callback) {
+ synchronized (mRegisteredCallbacks) {
+ registerPredictionUpdatesLocked(callbackExecutor, callback);
+ }
+ }
+
+ @GuardedBy("mRegisteredCallbacks")
+ private void registerPredictionUpdatesLocked(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull AppPredictor.Callback callback) {
if (mIsClosed.get()) {
throw new IllegalStateException("This client has already been destroyed.");
}
@@ -183,6 +195,13 @@ public final class AppPredictor {
* @param callback The callback to be unregistered.
*/
public void unregisterPredictionUpdates(@NonNull AppPredictor.Callback callback) {
+ synchronized (mRegisteredCallbacks) {
+ unregisterPredictionUpdatesLocked(callback);
+ }
+ }
+
+ @GuardedBy("mRegisteredCallbacks")
+ private void unregisterPredictionUpdatesLocked(@NonNull AppPredictor.Callback callback) {
if (mIsClosed.get()) {
throw new IllegalStateException("This client has already been destroyed.");
}
@@ -235,7 +254,7 @@ public final class AppPredictor {
}
try {
- mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice(targets),
+ mPredictionManager.sortAppTargets(mSessionId, new ParceledListSlice<>(targets),
new CallbackWrapper(callbackExecutor, callback));
} catch (RemoteException e) {
Log.e(TAG, "Failed to sort targets", e);
@@ -251,19 +270,25 @@ public final class AppPredictor {
if (!mIsClosed.getAndSet(true)) {
mCloseGuard.close();
- // Do destroy;
- try {
- mPredictionManager.onDestroyPredictionSession(mSessionId);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to notify app target event", e);
- e.rethrowAsRuntimeException();
+ synchronized (mRegisteredCallbacks) {
+ destroySessionLocked();
}
- mRegisteredCallbacks.clear();
} else {
throw new IllegalStateException("This client has already been destroyed.");
}
}
+ @GuardedBy("mRegisteredCallbacks")
+ private void destroySessionLocked() {
+ try {
+ mPredictionManager.onDestroyPredictionSession(mSessionId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to notify app target event", e);
+ e.rethrowAsRuntimeException();
+ }
+ mRegisteredCallbacks.clear();
+ }
+
@Override
protected void finalize() throws Throwable {
try {
diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java
index 2cd1d96190b0..10db3376adc5 100644
--- a/core/java/android/app/search/SearchSession.java
+++ b/core/java/android/app/search/SearchSession.java
@@ -23,9 +23,11 @@ import android.app.search.ISearchCallback.Stub;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.util.Log;
import dalvik.system.CloseGuard;
@@ -70,7 +72,7 @@ import java.util.function.Consumer;
* @hide
*/
@SystemApi
-public final class SearchSession implements AutoCloseable{
+public final class SearchSession implements AutoCloseable {
private static final String TAG = SearchSession.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -229,7 +231,14 @@ public final class SearchSession implements AutoCloseable{
if (DEBUG) {
Log.d(TAG, "CallbackWrapper.onResult result=" + result.getList());
}
- mExecutor.execute(() -> mCallback.accept(result.getList()));
+ List<SearchTarget> list = result.getList();
+ if (list.size() > 0) {
+ Bundle bundle = list.get(0).getExtras();
+ if (bundle != null) {
+ bundle.putLong("key_ipc_start", SystemClock.elapsedRealtime());
+ }
+ }
+ mExecutor.execute(() -> mCallback.accept(list));
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/core/java/android/app/time/DetectorStatusTypes.java b/core/java/android/app/time/DetectorStatusTypes.java
new file mode 100644
index 000000000000..3643fc9a7d86
--- /dev/null
+++ b/core/java/android/app/time/DetectorStatusTypes.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.time;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A set of constants that can relate to time or time zone detector status.
+ *
+ * <ul>
+ * <li>Detector status - the status of the overall detector.</li>
+ * <li>Detection algorithm status - the status of an algorithm that a detector can use.
+ * Each detector is expected to have one or more known algorithms to detect its chosen property,
+ * e.g. for time zone devices can have a "location" detection algorithm, where the device's
+ * location is used to detect the time zone.</li>
+ * </ul>
+ *
+ * @hide
+ */
+public final class DetectorStatusTypes {
+
+ /** A status code for a detector. */
+ @IntDef(prefix = "DETECTOR_STATUS_", value = {
+ DETECTOR_STATUS_UNKNOWN,
+ DETECTOR_STATUS_NOT_SUPPORTED,
+ DETECTOR_STATUS_NOT_RUNNING,
+ DETECTOR_STATUS_RUNNING,
+ })
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DetectorStatus {}
+
+ /**
+ * The detector status is unknown. Expected only for use as a placeholder before the actual
+ * status is known.
+ */
+ public static final @DetectorStatus int DETECTOR_STATUS_UNKNOWN = 0;
+
+ /** The detector is not supported on this device. */
+ public static final @DetectorStatus int DETECTOR_STATUS_NOT_SUPPORTED = 1;
+
+ /** The detector is supported but is not running. */
+ public static final @DetectorStatus int DETECTOR_STATUS_NOT_RUNNING = 2;
+
+ /** The detector is supported and is running. */
+ public static final @DetectorStatus int DETECTOR_STATUS_RUNNING = 3;
+
+ private DetectorStatusTypes() {}
+
+ /**
+ * A status code for a detection algorithm.
+ */
+ @IntDef(prefix = "DETECTION_ALGORITHM_STATUS_", value = {
+ DETECTION_ALGORITHM_STATUS_UNKNOWN,
+ DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED,
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ })
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DetectionAlgorithmStatus {}
+
+ /**
+ * The detection algorithm status is unknown. Expected only for use as a placeholder before the
+ * actual status is known.
+ */
+ public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_UNKNOWN = 0;
+
+ /** The detection algorithm is not supported on this device. */
+ public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED = 1;
+
+ /** The detection algorithm supported but is not running. */
+ public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_NOT_RUNNING = 2;
+
+ /** The detection algorithm supported and is running. */
+ public static final @DetectionAlgorithmStatus int DETECTION_ALGORITHM_STATUS_RUNNING = 3;
+
+ /**
+ * Validates the supplied value is one of the known {@code DETECTOR_STATUS_} constants and
+ * returns it if it is valid. {@link #DETECTOR_STATUS_UNKNOWN} is considered valid.
+ *
+ * @throws IllegalArgumentException if the value is not recognized
+ */
+ public static @DetectorStatus int requireValidDetectorStatus(
+ @DetectorStatus int detectorStatus) {
+ if (detectorStatus < DETECTOR_STATUS_UNKNOWN || detectorStatus > DETECTOR_STATUS_RUNNING) {
+ throw new IllegalArgumentException("Invalid detector status: " + detectorStatus);
+ }
+ return detectorStatus;
+ }
+
+ /**
+ * Returns a string for each {@code DETECTOR_STATUS_} constant. See also
+ * {@link #detectorStatusFromString(String)}.
+ *
+ * @throws IllegalArgumentException if the value is not recognized
+ */
+ @NonNull
+ public static String detectorStatusToString(@DetectorStatus int detectorStatus) {
+ switch (detectorStatus) {
+ case DETECTOR_STATUS_UNKNOWN:
+ return "UNKNOWN";
+ case DETECTOR_STATUS_NOT_SUPPORTED:
+ return "NOT_SUPPORTED";
+ case DETECTOR_STATUS_NOT_RUNNING:
+ return "NOT_RUNNING";
+ case DETECTOR_STATUS_RUNNING:
+ return "RUNNING";
+ default:
+ throw new IllegalArgumentException("Unknown status: " + detectorStatus);
+ }
+ }
+
+ /**
+ * Returns {@code DETECTOR_STATUS_} constant value from a string. See also
+ * {@link #detectorStatusToString(int)}.
+ *
+ * @throws IllegalArgumentException if the value is not recognized or is invalid
+ */
+ public static @DetectorStatus int detectorStatusFromString(
+ @Nullable String detectorStatusString) {
+ if (TextUtils.isEmpty(detectorStatusString)) {
+ throw new IllegalArgumentException("Empty status: " + detectorStatusString);
+ }
+
+ switch (detectorStatusString) {
+ case "UNKNOWN":
+ return DETECTOR_STATUS_UNKNOWN;
+ case "NOT_SUPPORTED":
+ return DETECTOR_STATUS_NOT_SUPPORTED;
+ case "NOT_RUNNING":
+ return DETECTOR_STATUS_NOT_RUNNING;
+ case "RUNNING":
+ return DETECTOR_STATUS_RUNNING;
+ default:
+ throw new IllegalArgumentException("Unknown status: " + detectorStatusString);
+ }
+ }
+
+ /**
+ * Validates the supplied value is one of the known {@code DETECTION_ALGORITHM_} constants and
+ * returns it if it is valid. {@link #DETECTION_ALGORITHM_STATUS_UNKNOWN} is considered valid.
+ *
+ * @throws IllegalArgumentException if the value is not recognized
+ */
+ public static @DetectionAlgorithmStatus int requireValidDetectionAlgorithmStatus(
+ @DetectionAlgorithmStatus int detectionAlgorithmStatus) {
+ if (detectionAlgorithmStatus < DETECTION_ALGORITHM_STATUS_UNKNOWN
+ || detectionAlgorithmStatus > DETECTION_ALGORITHM_STATUS_RUNNING) {
+ throw new IllegalArgumentException(
+ "Invalid detection algorithm: " + detectionAlgorithmStatus);
+ }
+ return detectionAlgorithmStatus;
+ }
+
+ /**
+ * Returns a string for each {@code DETECTION_ALGORITHM_} constant. See also
+ * {@link #detectionAlgorithmStatusFromString(String)}
+ *
+ * @throws IllegalArgumentException if the value is not recognized
+ */
+ @NonNull
+ public static String detectionAlgorithmStatusToString(
+ @DetectionAlgorithmStatus int detectorAlgorithmStatus) {
+ switch (detectorAlgorithmStatus) {
+ case DETECTION_ALGORITHM_STATUS_UNKNOWN:
+ return "UNKNOWN";
+ case DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED:
+ return "NOT_SUPPORTED";
+ case DETECTION_ALGORITHM_STATUS_NOT_RUNNING:
+ return "NOT_RUNNING";
+ case DETECTION_ALGORITHM_STATUS_RUNNING:
+ return "RUNNING";
+ default:
+ throw new IllegalArgumentException("Unknown status: " + detectorAlgorithmStatus);
+ }
+ }
+
+ /**
+ * Returns {@code DETECTION_ALGORITHM_} constant value from a string. See also
+ * {@link #detectionAlgorithmStatusToString(int)} (String)}
+ *
+ * @throws IllegalArgumentException if the value is not recognized or is invalid
+ */
+ public static @DetectionAlgorithmStatus int detectionAlgorithmStatusFromString(
+ @Nullable String detectorAlgorithmStatusString) {
+
+ if (TextUtils.isEmpty(detectorAlgorithmStatusString)) {
+ throw new IllegalArgumentException("Empty status: " + detectorAlgorithmStatusString);
+ }
+
+ switch (detectorAlgorithmStatusString) {
+ case "UNKNOWN":
+ return DETECTION_ALGORITHM_STATUS_UNKNOWN;
+ case "NOT_SUPPORTED":
+ return DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+ case "NOT_RUNNING":
+ return DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+ case "RUNNING":
+ return DETECTION_ALGORITHM_STATUS_RUNNING;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown status: " + detectorAlgorithmStatusString);
+ }
+ }
+}
diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl
new file mode 100644
index 000000000000..7184b123af1c
--- /dev/null
+++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+parcelable LocationTimeZoneAlgorithmStatus;
diff --git a/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
new file mode 100644
index 000000000000..710b8c40cefe
--- /dev/null
+++ b/core/java/android/app/time/LocationTimeZoneAlgorithmStatus.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusFromString;
+import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString;
+import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.timezone.TimeZoneProviderStatus;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Information about the status of the location-based time zone detection algorithm.
+ *
+ * @hide
+ */
+public final class LocationTimeZoneAlgorithmStatus implements Parcelable {
+
+ /**
+ * An enum that describes a location time zone provider's status.
+ *
+ * @hide
+ */
+ @IntDef(prefix = "PROVIDER_STATUS_", value = {
+ PROVIDER_STATUS_NOT_PRESENT,
+ PROVIDER_STATUS_NOT_READY,
+ PROVIDER_STATUS_IS_CERTAIN,
+ PROVIDER_STATUS_IS_UNCERTAIN,
+ })
+ @Target(ElementType.TYPE_USE)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProviderStatus {}
+
+ /**
+ * Indicates a provider is not present because it has not been configured, the configuration
+ * is bad, or the provider has reported a permanent failure.
+ */
+ public static final @ProviderStatus int PROVIDER_STATUS_NOT_PRESENT = 1;
+
+ /**
+ * Indicates a provider has not reported it is certain or uncertain. This may be because it has
+ * just started running, or it has been stopped.
+ */
+ public static final @ProviderStatus int PROVIDER_STATUS_NOT_READY = 2;
+
+ /**
+ * Indicates a provider last reported it is certain.
+ */
+ public static final @ProviderStatus int PROVIDER_STATUS_IS_CERTAIN = 3;
+
+ /**
+ * Indicates a provider last reported it is uncertain.
+ */
+ public static final @ProviderStatus int PROVIDER_STATUS_IS_UNCERTAIN = 4;
+
+ /**
+ * An instance that provides no information about algorithm status because the algorithm has not
+ * yet reported. Effectively a "null" status placeholder.
+ */
+ @NonNull
+ public static final LocationTimeZoneAlgorithmStatus UNKNOWN =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_UNKNOWN,
+ PROVIDER_STATUS_NOT_READY, null, PROVIDER_STATUS_NOT_READY, null);
+
+ private final @DetectionAlgorithmStatus int mStatus;
+ private final @ProviderStatus int mPrimaryProviderStatus;
+ // May be populated when mPrimaryProviderReportedStatus == PROVIDER_STATUS_IS_CERTAIN
+ // or PROVIDER_STATUS_IS_UNCERTAIN
+ @Nullable private final TimeZoneProviderStatus mPrimaryProviderReportedStatus;
+
+ private final @ProviderStatus int mSecondaryProviderStatus;
+ // May be populated when mSecondaryProviderReportedStatus == PROVIDER_STATUS_IS_CERTAIN
+ // or PROVIDER_STATUS_IS_UNCERTAIN
+ @Nullable private final TimeZoneProviderStatus mSecondaryProviderReportedStatus;
+
+ public LocationTimeZoneAlgorithmStatus(
+ @DetectionAlgorithmStatus int status,
+ @ProviderStatus int primaryProviderStatus,
+ @Nullable TimeZoneProviderStatus primaryProviderReportedStatus,
+ @ProviderStatus int secondaryProviderStatus,
+ @Nullable TimeZoneProviderStatus secondaryProviderReportedStatus) {
+
+ mStatus = requireValidDetectionAlgorithmStatus(status);
+ mPrimaryProviderStatus = requireValidProviderStatus(primaryProviderStatus);
+ mPrimaryProviderReportedStatus = primaryProviderReportedStatus;
+ mSecondaryProviderStatus = requireValidProviderStatus(secondaryProviderStatus);
+ mSecondaryProviderReportedStatus = secondaryProviderReportedStatus;
+
+ boolean primaryProviderHasReported = hasProviderReported(primaryProviderStatus);
+ boolean primaryProviderReportedStatusPresent = primaryProviderReportedStatus != null;
+ if (!primaryProviderHasReported && primaryProviderReportedStatusPresent) {
+ throw new IllegalArgumentException(
+ "primaryProviderReportedStatus=" + primaryProviderReportedStatus
+ + ", primaryProviderStatus="
+ + providerStatusToString(primaryProviderStatus));
+ }
+
+ boolean secondaryProviderHasReported = hasProviderReported(secondaryProviderStatus);
+ boolean secondaryProviderReportedStatusPresent = secondaryProviderReportedStatus != null;
+ if (!secondaryProviderHasReported && secondaryProviderReportedStatusPresent) {
+ throw new IllegalArgumentException(
+ "secondaryProviderReportedStatus=" + secondaryProviderReportedStatus
+ + ", secondaryProviderStatus="
+ + providerStatusToString(secondaryProviderStatus));
+ }
+
+ // If the algorithm isn't running, providers can't report.
+ if (status != DETECTION_ALGORITHM_STATUS_RUNNING
+ && (primaryProviderHasReported || secondaryProviderHasReported)) {
+ throw new IllegalArgumentException(
+ "algorithmStatus=" + detectionAlgorithmStatusToString(status)
+ + ", primaryProviderReportedStatus=" + primaryProviderReportedStatus
+ + ", secondaryProviderReportedStatus="
+ + secondaryProviderReportedStatus);
+ }
+ }
+
+ /**
+ * Returns the status value of the detection algorithm.
+ */
+ public @DetectionAlgorithmStatus int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Returns the status of the primary location time zone provider as categorized by the detection
+ * algorithm.
+ */
+ public @ProviderStatus int getPrimaryProviderStatus() {
+ return mPrimaryProviderStatus;
+ }
+
+ /**
+ * Returns the status of the primary location time zone provider as reported by the provider
+ * itself. Can be {@code null} when the provider hasn't reported, or omitted when it has.
+ */
+ @Nullable
+ public TimeZoneProviderStatus getPrimaryProviderReportedStatus() {
+ return mPrimaryProviderReportedStatus;
+ }
+
+ /**
+ * Returns the status of the secondary location time zone provider as categorized by the
+ * detection algorithm.
+ */
+ public @ProviderStatus int getSecondaryProviderStatus() {
+ return mSecondaryProviderStatus;
+ }
+
+ /**
+ * Returns the status of the secondary location time zone provider as reported by the provider
+ * itself. Can be {@code null} when the provider hasn't reported, or omitted when it has.
+ */
+ @Nullable
+ public TimeZoneProviderStatus getSecondaryProviderReportedStatus() {
+ return mSecondaryProviderReportedStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "LocationTimeZoneAlgorithmStatus{"
+ + "mAlgorithmStatus=" + detectionAlgorithmStatusToString(mStatus)
+ + ", mPrimaryProviderStatus=" + providerStatusToString(mPrimaryProviderStatus)
+ + ", mPrimaryProviderReportedStatus=" + mPrimaryProviderReportedStatus
+ + ", mSecondaryProviderStatus=" + providerStatusToString(mSecondaryProviderStatus)
+ + ", mSecondaryProviderReportedStatus=" + mSecondaryProviderReportedStatus
+ + '}';
+ }
+
+ /**
+ * Parses a {@link LocationTimeZoneAlgorithmStatus} from a toString() string for manual
+ * command-line testing.
+ */
+ @NonNull
+ public static LocationTimeZoneAlgorithmStatus parseCommandlineArg(@NonNull String arg) {
+ // Note: "}" has to be escaped on Android with "\\}" because the regexp library is not based
+ // on OpenJDK code.
+ Pattern pattern = Pattern.compile("LocationTimeZoneAlgorithmStatus\\{"
+ + "mAlgorithmStatus=(.+)"
+ + ", mPrimaryProviderStatus=([^,]+)"
+ + ", mPrimaryProviderReportedStatus=(null|TimeZoneProviderStatus\\{[^}]+\\})"
+ + ", mSecondaryProviderStatus=([^,]+)"
+ + ", mSecondaryProviderReportedStatus=(null|TimeZoneProviderStatus\\{[^}]+\\})"
+ + "\\}"
+ );
+ Matcher matcher = pattern.matcher(arg);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Unable to parse algorithm status arg: " + arg);
+ }
+ @DetectionAlgorithmStatus int algorithmStatus =
+ detectionAlgorithmStatusFromString(matcher.group(1));
+ @ProviderStatus int primaryProviderStatus = providerStatusFromString(matcher.group(2));
+ TimeZoneProviderStatus primaryProviderReportedStatus =
+ parseTimeZoneProviderStatusOrNull(matcher.group(3));
+ @ProviderStatus int secondaryProviderStatus = providerStatusFromString(matcher.group(4));
+ TimeZoneProviderStatus secondaryProviderReportedStatus =
+ parseTimeZoneProviderStatusOrNull(matcher.group(5));
+ return new LocationTimeZoneAlgorithmStatus(
+ algorithmStatus, primaryProviderStatus, primaryProviderReportedStatus,
+ secondaryProviderStatus, secondaryProviderReportedStatus);
+ }
+
+ @Nullable
+ private static TimeZoneProviderStatus parseTimeZoneProviderStatusOrNull(
+ String providerReportedStatusString) {
+ TimeZoneProviderStatus providerReportedStatus;
+ if ("null".equals(providerReportedStatusString)) {
+ providerReportedStatus = null;
+ } else {
+ providerReportedStatus =
+ TimeZoneProviderStatus.parseProviderStatus(providerReportedStatusString);
+ }
+ return providerReportedStatus;
+ }
+
+ @NonNull
+ public static final Creator<LocationTimeZoneAlgorithmStatus> CREATOR = new Creator<>() {
+ @Override
+ public LocationTimeZoneAlgorithmStatus createFromParcel(Parcel in) {
+ @DetectionAlgorithmStatus int algorithmStatus = in.readInt();
+ @ProviderStatus int primaryProviderStatus = in.readInt();
+ TimeZoneProviderStatus primaryProviderReportedStatus =
+ in.readParcelable(getClass().getClassLoader(), TimeZoneProviderStatus.class);
+ @ProviderStatus int secondaryProviderStatus = in.readInt();
+ TimeZoneProviderStatus secondaryProviderReportedStatus =
+ in.readParcelable(getClass().getClassLoader(), TimeZoneProviderStatus.class);
+ return new LocationTimeZoneAlgorithmStatus(
+ algorithmStatus, primaryProviderStatus, primaryProviderReportedStatus,
+ secondaryProviderStatus, secondaryProviderReportedStatus);
+ }
+
+ @Override
+ public LocationTimeZoneAlgorithmStatus[] newArray(int size) {
+ return new LocationTimeZoneAlgorithmStatus[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mStatus);
+ parcel.writeInt(mPrimaryProviderStatus);
+ parcel.writeParcelable(mPrimaryProviderReportedStatus, flags);
+ parcel.writeInt(mSecondaryProviderStatus);
+ parcel.writeParcelable(mSecondaryProviderReportedStatus, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LocationTimeZoneAlgorithmStatus that = (LocationTimeZoneAlgorithmStatus) o;
+ return mStatus == that.mStatus
+ && mPrimaryProviderStatus == that.mPrimaryProviderStatus
+ && Objects.equals(
+ mPrimaryProviderReportedStatus, that.mPrimaryProviderReportedStatus)
+ && mSecondaryProviderStatus == that.mSecondaryProviderStatus
+ && Objects.equals(
+ mSecondaryProviderReportedStatus, that.mSecondaryProviderReportedStatus);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mStatus,
+ mPrimaryProviderStatus, mPrimaryProviderReportedStatus,
+ mSecondaryProviderStatus, mSecondaryProviderReportedStatus);
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ @NonNull
+ public static String providerStatusToString(@ProviderStatus int providerStatus) {
+ switch (providerStatus) {
+ case PROVIDER_STATUS_NOT_PRESENT:
+ return "NOT_PRESENT";
+ case PROVIDER_STATUS_NOT_READY:
+ return "NOT_READY";
+ case PROVIDER_STATUS_IS_CERTAIN:
+ return "IS_CERTAIN";
+ case PROVIDER_STATUS_IS_UNCERTAIN:
+ return "IS_UNCERTAIN";
+ default:
+ throw new IllegalArgumentException("Unknown status: " + providerStatus);
+ }
+ }
+
+ /** @hide */
+ @VisibleForTesting public static @ProviderStatus int providerStatusFromString(
+ @Nullable String providerStatusString) {
+ if (TextUtils.isEmpty(providerStatusString)) {
+ throw new IllegalArgumentException("Empty status: " + providerStatusString);
+ }
+
+ switch (providerStatusString) {
+ case "NOT_PRESENT":
+ return PROVIDER_STATUS_NOT_PRESENT;
+ case "NOT_READY":
+ return PROVIDER_STATUS_NOT_READY;
+ case "IS_CERTAIN":
+ return PROVIDER_STATUS_IS_CERTAIN;
+ case "IS_UNCERTAIN":
+ return PROVIDER_STATUS_IS_UNCERTAIN;
+ default:
+ throw new IllegalArgumentException("Unknown status: " + providerStatusString);
+ }
+ }
+
+ private static boolean hasProviderReported(@ProviderStatus int providerStatus) {
+ return providerStatus == PROVIDER_STATUS_IS_CERTAIN
+ || providerStatus == PROVIDER_STATUS_IS_UNCERTAIN;
+ }
+
+ /** @hide */
+ @VisibleForTesting public static @ProviderStatus int requireValidProviderStatus(
+ @ProviderStatus int providerStatus) {
+ if (providerStatus < PROVIDER_STATUS_NOT_PRESENT
+ || providerStatus > PROVIDER_STATUS_IS_UNCERTAIN) {
+ throw new IllegalArgumentException(
+ "Invalid provider status: " + providerStatus);
+ }
+ return providerStatus;
+ }
+}
diff --git a/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl
new file mode 100644
index 000000000000..0eb5b63b7ffb
--- /dev/null
+++ b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+parcelable TelephonyTimeZoneAlgorithmStatus;
diff --git a/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java
new file mode 100644
index 000000000000..95240c00fa3f
--- /dev/null
+++ b/core/java/android/app/time/TelephonyTimeZoneAlgorithmStatus.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import static android.app.time.DetectorStatusTypes.detectionAlgorithmStatusToString;
+import static android.app.time.DetectorStatusTypes.requireValidDetectionAlgorithmStatus;
+
+import android.annotation.NonNull;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Information about the status of the telephony-based time zone detection algorithm.
+ *
+ * @hide
+ */
+public final class TelephonyTimeZoneAlgorithmStatus implements Parcelable {
+
+ private final @DetectionAlgorithmStatus int mAlgorithmStatus;
+
+ public TelephonyTimeZoneAlgorithmStatus(@DetectionAlgorithmStatus int algorithmStatus) {
+ mAlgorithmStatus = requireValidDetectionAlgorithmStatus(algorithmStatus);
+ }
+
+ /**
+ * Returns the status of the detection algorithm.
+ */
+ public @DetectionAlgorithmStatus int getAlgorithmStatus() {
+ return mAlgorithmStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "TelephonyTimeZoneAlgorithmStatus{"
+ + "mAlgorithmStatus=" + detectionAlgorithmStatusToString(mAlgorithmStatus)
+ + '}';
+ }
+
+ @NonNull
+ public static final Creator<TelephonyTimeZoneAlgorithmStatus> CREATOR = new Creator<>() {
+ @Override
+ public TelephonyTimeZoneAlgorithmStatus createFromParcel(Parcel in) {
+ @DetectionAlgorithmStatus int algorithmStatus = in.readInt();
+ return new TelephonyTimeZoneAlgorithmStatus(algorithmStatus);
+ }
+
+ @Override
+ public TelephonyTimeZoneAlgorithmStatus[] newArray(int size) {
+ return new TelephonyTimeZoneAlgorithmStatus[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mAlgorithmStatus);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TelephonyTimeZoneAlgorithmStatus that = (TelephonyTimeZoneAlgorithmStatus) o;
+ return mAlgorithmStatus == that.mAlgorithmStatus;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAlgorithmStatus);
+ }
+}
diff --git a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
index cd91b0431b28..4684c6ad811c 100644
--- a/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
+++ b/core/java/android/app/time/TimeZoneCapabilitiesAndConfig.java
@@ -23,27 +23,40 @@ import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
+import java.util.concurrent.Executor;
/**
- * A pair containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}.
+ * An object containing a user's {@link TimeZoneCapabilities} and {@link TimeZoneConfiguration}.
*
* @hide
*/
@SystemApi
public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
- public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR =
- new Creator<TimeZoneCapabilitiesAndConfig>() {
- public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
- return TimeZoneCapabilitiesAndConfig.createFromParcel(in);
- }
-
- public TimeZoneCapabilitiesAndConfig[] newArray(int size) {
- return new TimeZoneCapabilitiesAndConfig[size];
- }
- };
+ public static final @NonNull Creator<TimeZoneCapabilitiesAndConfig> CREATOR = new Creator<>() {
+ public TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
+ return TimeZoneCapabilitiesAndConfig.createFromParcel(in);
+ }
+ public TimeZoneCapabilitiesAndConfig[] newArray(int size) {
+ return new TimeZoneCapabilitiesAndConfig[size];
+ }
+ };
+ /**
+ * The time zone detector status.
+ *
+ * Implementation note for future platform engineers: This field is only needed by SettingsUI
+ * initially and so it has not been added to the SDK API. {@link TimeZoneDetectorStatus}
+ * contains details about the internals of the time zone detector so thought should be given to
+ * abstraction / exposing a lightweight version if something unbundled needs access to detector
+ * details. Also, that could be good time to add separate APIs for bundled components, or add
+ * new APIs that return something more extensible and generic like a Bundle or a less
+ * constraining name. See also {@link
+ * TimeManager#addTimeZoneDetectorListener(Executor, TimeManager.TimeZoneDetectorListener)},
+ * which notified of changes to any fields in this class, including the detector status.
+ */
+ @NonNull private final TimeZoneDetectorStatus mDetectorStatus;
@NonNull private final TimeZoneCapabilities mCapabilities;
@NonNull private final TimeZoneConfiguration mConfiguration;
@@ -53,26 +66,41 @@ public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
* @hide
*/
public TimeZoneCapabilitiesAndConfig(
+ @NonNull TimeZoneDetectorStatus detectorStatus,
@NonNull TimeZoneCapabilities capabilities,
@NonNull TimeZoneConfiguration configuration) {
- this.mCapabilities = Objects.requireNonNull(capabilities);
- this.mConfiguration = Objects.requireNonNull(configuration);
+ mDetectorStatus = Objects.requireNonNull(detectorStatus);
+ mCapabilities = Objects.requireNonNull(capabilities);
+ mConfiguration = Objects.requireNonNull(configuration);
}
@NonNull
private static TimeZoneCapabilitiesAndConfig createFromParcel(Parcel in) {
- TimeZoneCapabilities capabilities = in.readParcelable(null, android.app.time.TimeZoneCapabilities.class);
- TimeZoneConfiguration configuration = in.readParcelable(null, android.app.time.TimeZoneConfiguration.class);
- return new TimeZoneCapabilitiesAndConfig(capabilities, configuration);
+ TimeZoneDetectorStatus detectorStatus =
+ in.readParcelable(null, TimeZoneDetectorStatus.class);
+ TimeZoneCapabilities capabilities = in.readParcelable(null, TimeZoneCapabilities.class);
+ TimeZoneConfiguration configuration = in.readParcelable(null, TimeZoneConfiguration.class);
+ return new TimeZoneCapabilitiesAndConfig(detectorStatus, capabilities, configuration);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mDetectorStatus, flags);
dest.writeParcelable(mCapabilities, flags);
dest.writeParcelable(mConfiguration, flags);
}
/**
+ * Returns the time zone detector's status.
+ *
+ * @hide
+ */
+ @NonNull
+ public TimeZoneDetectorStatus getDetectorStatus() {
+ return mDetectorStatus;
+ }
+
+ /**
* Returns the user's time zone behavior capabilities.
*/
@NonNull
@@ -102,7 +130,8 @@ public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
return false;
}
TimeZoneCapabilitiesAndConfig that = (TimeZoneCapabilitiesAndConfig) o;
- return mCapabilities.equals(that.mCapabilities)
+ return mDetectorStatus.equals(that.mDetectorStatus)
+ && mCapabilities.equals(that.mCapabilities)
&& mConfiguration.equals(that.mConfiguration);
}
@@ -114,7 +143,8 @@ public final class TimeZoneCapabilitiesAndConfig implements Parcelable {
@Override
public String toString() {
return "TimeZoneCapabilitiesAndConfig{"
- + "mCapabilities=" + mCapabilities
+ + "mDetectorStatus=" + mDetectorStatus
+ + ", mCapabilities=" + mCapabilities
+ ", mConfiguration=" + mConfiguration
+ '}';
}
diff --git a/core/java/android/app/time/TimeZoneDetectorStatus.aidl b/core/java/android/app/time/TimeZoneDetectorStatus.aidl
new file mode 100644
index 000000000000..32204df6d698
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneDetectorStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+parcelable TimeZoneDetectorStatus;
diff --git a/core/java/android/app/time/TimeZoneDetectorStatus.java b/core/java/android/app/time/TimeZoneDetectorStatus.java
new file mode 100644
index 000000000000..16374639b77c
--- /dev/null
+++ b/core/java/android/app/time/TimeZoneDetectorStatus.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.time;
+
+import static android.app.time.DetectorStatusTypes.DetectorStatus;
+import static android.app.time.DetectorStatusTypes.requireValidDetectorStatus;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Information about the status of the automatic time zone detector. Used by SettingsUI to display
+ * status information to the user.
+ *
+ * @hide
+ */
+public final class TimeZoneDetectorStatus implements Parcelable {
+
+ private final @DetectorStatus int mDetectorStatus;
+ @NonNull private final TelephonyTimeZoneAlgorithmStatus mTelephonyTimeZoneAlgorithmStatus;
+ @NonNull private final LocationTimeZoneAlgorithmStatus mLocationTimeZoneAlgorithmStatus;
+
+ public TimeZoneDetectorStatus(
+ @DetectorStatus int detectorStatus,
+ @NonNull TelephonyTimeZoneAlgorithmStatus telephonyTimeZoneAlgorithmStatus,
+ @NonNull LocationTimeZoneAlgorithmStatus locationTimeZoneAlgorithmStatus) {
+ mDetectorStatus = requireValidDetectorStatus(detectorStatus);
+ mTelephonyTimeZoneAlgorithmStatus =
+ Objects.requireNonNull(telephonyTimeZoneAlgorithmStatus);
+ mLocationTimeZoneAlgorithmStatus = Objects.requireNonNull(locationTimeZoneAlgorithmStatus);
+ }
+
+ public @DetectorStatus int getDetectorStatus() {
+ return mDetectorStatus;
+ }
+
+ @NonNull
+ public TelephonyTimeZoneAlgorithmStatus getTelephonyTimeZoneAlgorithmStatus() {
+ return mTelephonyTimeZoneAlgorithmStatus;
+ }
+
+ @NonNull
+ public LocationTimeZoneAlgorithmStatus getLocationTimeZoneAlgorithmStatus() {
+ return mLocationTimeZoneAlgorithmStatus;
+ }
+
+ @Override
+ public String toString() {
+ return "TimeZoneDetectorStatus{"
+ + "mDetectorStatus=" + DetectorStatusTypes.detectorStatusToString(mDetectorStatus)
+ + ", mTelephonyTimeZoneAlgorithmStatus=" + mTelephonyTimeZoneAlgorithmStatus
+ + ", mLocationTimeZoneAlgorithmStatus=" + mLocationTimeZoneAlgorithmStatus
+ + '}';
+ }
+
+ public static final @NonNull Creator<TimeZoneDetectorStatus> CREATOR = new Creator<>() {
+ @Override
+ public TimeZoneDetectorStatus createFromParcel(Parcel in) {
+ @DetectorStatus int detectorStatus = in.readInt();
+ TelephonyTimeZoneAlgorithmStatus telephonyTimeZoneAlgorithmStatus =
+ in.readParcelable(getClass().getClassLoader(),
+ TelephonyTimeZoneAlgorithmStatus.class);
+ LocationTimeZoneAlgorithmStatus locationTimeZoneAlgorithmStatus =
+ in.readParcelable(getClass().getClassLoader(),
+ LocationTimeZoneAlgorithmStatus.class);
+ return new TimeZoneDetectorStatus(detectorStatus,
+ telephonyTimeZoneAlgorithmStatus, locationTimeZoneAlgorithmStatus);
+ }
+
+ @Override
+ public TimeZoneDetectorStatus[] newArray(int size) {
+ return new TimeZoneDetectorStatus[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mDetectorStatus);
+ parcel.writeParcelable(mTelephonyTimeZoneAlgorithmStatus, flags);
+ parcel.writeParcelable(mLocationTimeZoneAlgorithmStatus, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeZoneDetectorStatus that = (TimeZoneDetectorStatus) o;
+ return mDetectorStatus == that.mDetectorStatus
+ && mTelephonyTimeZoneAlgorithmStatus.equals(that.mTelephonyTimeZoneAlgorithmStatus)
+ && mLocationTimeZoneAlgorithmStatus.equals(that.mLocationTimeZoneAlgorithmStatus);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDetectorStatus, mTelephonyTimeZoneAlgorithmStatus,
+ mLocationTimeZoneAlgorithmStatus);
+ }
+}
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index 0e9e28be8818..f357fb243fe1 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -81,11 +81,11 @@ public interface TimeZoneDetector {
String SHELL_COMMAND_SET_GEO_DETECTION_ENABLED = "set_geo_detection_enabled";
/**
- * A shell command that injects a geolocation time zone suggestion (as if from the
+ * A shell command that injects a location algorithm event (as if from the
* location_time_zone_manager).
* @hide
*/
- String SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE = "suggest_geo_location_time_zone";
+ String SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT = "handle_location_algorithm_event";
/**
* A shell command that injects a manual time zone suggestion (as if from the SettingsUI or
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 82d7534c84d9..7d6336a225bd 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -52,6 +52,11 @@ interface IVirtualDeviceManager {
List<VirtualDevice> getVirtualDevices();
/**
+ * Returns the device policy for the given virtual device and policy type.
+ */
+ int getDevicePolicy(int deviceId, int policyType);
+
+ /**
* Creates a virtual display owned by a particular virtual device.
*
* @param virtualDisplayConfig The configuration used in creating the display
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 0bb86fbf00f8..c14bb1beb025 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -182,6 +182,28 @@ public final class VirtualDeviceManager {
}
/**
+ * Returns the device policy for the given virtual device and policy type.
+ *
+ * <p>In case the virtual device identifier is not valid, or there's no explicitly specified
+ * policy for that device and policy type, then
+ * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
+ *
+ * @hide
+ */
+ public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
+ int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to retrieve device policy; no virtual device manager service.");
+ return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+ }
+ try {
+ return mService.getDevicePolicy(deviceId, policyType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* A virtual device has its own virtual display, audio output, microphone, and camera etc. The
* creator of a virtual device can take the output from the virtual display and stream it over
* to another device, and inject input events that are received from the remote device.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index d40c9d63039d..c6e6f8324cff 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -28,6 +28,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.util.SparseIntArray;
import com.android.internal.util.Preconditions;
@@ -103,6 +104,47 @@ public final class VirtualDeviceParams implements Parcelable {
*/
public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1;
+ /** @hide */
+ @IntDef(prefix = "DEVICE_POLICY_", value = {DEVICE_POLICY_DEFAULT, DEVICE_POLICY_CUSTOM})
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface DevicePolicy {}
+
+ /**
+ * Indicates that there is no special logic for this virtual device and it should be treated
+ * the same way as the default device, keeping the default behavior unchanged.
+ */
+ public static final int DEVICE_POLICY_DEFAULT = 0;
+
+ /**
+ * Indicates that there is custom logic, specific to this virtual device, which should be
+ * triggered instead of the default behavior.
+ */
+ public static final int DEVICE_POLICY_CUSTOM = 1;
+
+ /**
+ * Any relevant component must be able to interpret the correct meaning of a custom policy for
+ * a given policy type.
+ * @hide
+ */
+ @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS})
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ public @interface PolicyType {}
+
+ /**
+ * Tells the sensor framework how to handle sensor requests from contexts associated with this
+ * virtual device, namely the sensors returned by
+ * {@link android.hardware.SensorManager#getSensorList}:
+ *
+ * <ul>
+ * <li>{@link #DEVICE_POLICY_DEFAULT}: Return the sensors of the default device.
+ * <li>{@link #DEVICE_POLICY_CUSTOM}: Return the sensors of the virtual device. Note that if
+ * the virtual device did not create any virtual sensors, then an empty list is returned.
+ * </ul>
+ */
+ public static final int POLICY_TYPE_SENSORS = 0;
+
private final int mLockState;
@NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
@NonNull private final ArraySet<ComponentName> mAllowedCrossTaskNavigations;
@@ -114,6 +156,8 @@ public final class VirtualDeviceParams implements Parcelable {
@ActivityPolicy
private final int mDefaultActivityPolicy;
@Nullable private final String mName;
+ // Mapping of @PolicyType to @DevicePolicy
+ @NonNull private final SparseIntArray mDevicePolicies;
private VirtualDeviceParams(
@LockState int lockState,
@@ -124,12 +168,14 @@ public final class VirtualDeviceParams implements Parcelable {
@NonNull Set<ComponentName> allowedActivities,
@NonNull Set<ComponentName> blockedActivities,
@ActivityPolicy int defaultActivityPolicy,
- @Nullable String name) {
+ @Nullable String name,
+ @NonNull SparseIntArray devicePolicies) {
Preconditions.checkNotNull(usersWithMatchingAccounts);
Preconditions.checkNotNull(allowedCrossTaskNavigations);
Preconditions.checkNotNull(blockedCrossTaskNavigations);
Preconditions.checkNotNull(allowedActivities);
Preconditions.checkNotNull(blockedActivities);
+ Preconditions.checkNotNull(devicePolicies);
mLockState = lockState;
mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
@@ -140,6 +186,7 @@ public final class VirtualDeviceParams implements Parcelable {
mBlockedActivities = new ArraySet<>(blockedActivities);
mDefaultActivityPolicy = defaultActivityPolicy;
mName = name;
+ mDevicePolicies = devicePolicies;
}
@SuppressWarnings("unchecked")
@@ -153,6 +200,7 @@ public final class VirtualDeviceParams implements Parcelable {
mBlockedActivities = (ArraySet<ComponentName>) parcel.readArraySet(null);
mDefaultActivityPolicy = parcel.readInt();
mName = parcel.readString8();
+ mDevicePolicies = parcel.readSparseIntArray();
}
/**
@@ -258,6 +306,16 @@ public final class VirtualDeviceParams implements Parcelable {
return mName;
}
+ /**
+ * Returns the policy specified for this policy type, or {@link #DEVICE_POLICY_DEFAULT} if no
+ * policy for this type has been explicitly specified.
+ *
+ * @see Builder#addDevicePolicy
+ */
+ public @DevicePolicy int getDevicePolicy(@PolicyType int policyType) {
+ return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
+ }
+
@Override
public int describeContents() {
return 0;
@@ -274,6 +332,7 @@ public final class VirtualDeviceParams implements Parcelable {
dest.writeArraySet(mBlockedActivities);
dest.writeInt(mDefaultActivityPolicy);
dest.writeString8(mName);
+ dest.writeSparseIntArray(mDevicePolicies);
}
@Override
@@ -285,6 +344,18 @@ public final class VirtualDeviceParams implements Parcelable {
return false;
}
VirtualDeviceParams that = (VirtualDeviceParams) o;
+ final int devicePoliciesCount = mDevicePolicies.size();
+ if (devicePoliciesCount != that.mDevicePolicies.size()) {
+ return false;
+ }
+ for (int i = 0; i < devicePoliciesCount; i++) {
+ if (mDevicePolicies.keyAt(i) != that.mDevicePolicies.keyAt(i)) {
+ return false;
+ }
+ if (mDevicePolicies.valueAt(i) != that.mDevicePolicies.valueAt(i)) {
+ return false;
+ }
+ }
return mLockState == that.mLockState
&& mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts)
&& Objects.equals(mAllowedCrossTaskNavigations, that.mAllowedCrossTaskNavigations)
@@ -298,10 +369,15 @@ public final class VirtualDeviceParams implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(
+ int hashCode = Objects.hash(
mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations,
mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities,
- mBlockedActivities, mDefaultActivityPolicy, mName);
+ mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies);
+ for (int i = 0; i < mDevicePolicies.size(); i++) {
+ hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
+ hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
+ }
+ return hashCode;
}
@Override
@@ -317,6 +393,7 @@ public final class VirtualDeviceParams implements Parcelable {
+ " mBlockedActivities=" + mBlockedActivities
+ " mDefaultActivityPolicy=" + mDefaultActivityPolicy
+ " mName=" + mName
+ + " mDevicePolicies=" + mDevicePolicies
+ ")";
}
@@ -350,6 +427,7 @@ public final class VirtualDeviceParams implements Parcelable {
private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
private boolean mDefaultActivityPolicyConfigured = false;
@Nullable private String mName;
+ @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
/**
* Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -528,6 +606,18 @@ public final class VirtualDeviceParams implements Parcelable {
}
/**
+ * Specifies a policy for this virtual device.
+ *
+ * @param policyType the type of policy, i.e. which behavior to specify a policy for.
+ * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
+ */
+ @NonNull
+ public Builder addDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) {
+ mDevicePolicies.put(policyType, devicePolicy);
+ return this;
+ }
+
+ /**
* Builds the {@link VirtualDeviceParams} instance.
*/
@NonNull
@@ -541,7 +631,8 @@ public final class VirtualDeviceParams implements Parcelable {
mAllowedActivities,
mBlockedActivities,
mDefaultActivityPolicy,
- mName);
+ mName,
+ mDevicePolicies);
}
}
}
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index dbefa65f5c68..cc7977a267a5 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -26,6 +26,7 @@ import android.text.TextUtils;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
+import java.util.Objects;
/**
* Fabricated Runtime Resource Overlays (FRROs) are overlays generated ar runtime.
@@ -89,6 +90,27 @@ public class FabricatedOverlay {
}
/**
+ * Ensure the resource name is in the form [package]:type/entry.
+ *
+ * @param name name of the target resource to overlay (in the form [package]:type/entry)
+ * @return the valid name
+ */
+ private static String ensureValidResourceName(@NonNull String name) {
+ Objects.requireNonNull(name);
+ final int slashIndex = name.indexOf('/'); /* must contain '/' */
+ final int colonIndex = name.indexOf(':'); /* ':' should before '/' if ':' exist */
+
+ // The minimum length of resource type is "id".
+ Preconditions.checkArgument(
+ slashIndex >= 0 /* It must contain the type name */
+ && colonIndex != 0 /* 0 means the package name is empty */
+ && (slashIndex - colonIndex) > 2 /* The shortest length of type is "id" */,
+ "\"%s\" is invalid resource name",
+ name);
+ return name;
+ }
+
+ /**
* Sets the value of the fabricated overlay
*
* @param resourceName name of the target resource to overlay (in the form
@@ -99,6 +121,8 @@ public class FabricatedOverlay {
* @see android.util.TypedValue#type
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
@@ -120,6 +144,8 @@ public class FabricatedOverlay {
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, int value,
String configuration) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
@@ -140,6 +166,8 @@ public class FabricatedOverlay {
* @see android.util.TypedValue#type
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, String value) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
@@ -161,6 +189,8 @@ public class FabricatedOverlay {
*/
public Builder setResourceValue(@NonNull String resourceName, int dataType, String value,
String configuration) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.dataType = dataType;
@@ -180,6 +210,8 @@ public class FabricatedOverlay {
*/
public Builder setResourceValue(@NonNull String resourceName, ParcelFileDescriptor value,
String configuration) {
+ ensureValidResourceName(resourceName);
+
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
entry.binaryData = value;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 6c1d84be6c9f..485d04db82d5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2959,6 +2959,18 @@ public abstract class PackageManager {
public static final String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
/**
+ * Feature for {@link #getSystemAvailableFeatures()} and {@link #hasSystemFeature(String)}.
+ * This feature indicates whether device supports
+ * <a href="https://source.android.com/docs/core/virtualization">Android Virtualization Framework</a>.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_VIRTUALIZATION_FRAMEWORK =
+ "android.software.virtualization_framework";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature(String, int)}: If this feature is supported, the Vulkan
* implementation on this device is hardware accelerated, and the Vulkan native API will
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
index 6fa331bbdc54..5c07e6e94131 100644
--- a/core/java/android/credentials/ui/Entry.java
+++ b/core/java/android/credentials/ui/Entry.java
@@ -17,7 +17,10 @@
package android.credentials.ui;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.app.slice.Slice;
+import android.content.Intent;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -85,6 +88,8 @@ public class Entry implements Parcelable {
@NonNull private final String mKey;
@NonNull private final String mSubkey;
+ @Nullable private PendingIntent mPendingIntent;
+ @Nullable private Intent mFrameworkExtrasIntent;
@NonNull
private final Slice mSlice;
@@ -100,14 +105,29 @@ public class Entry implements Parcelable {
AnnotationValidations.validate(NonNull.class, null, mSubkey);
mSlice = slice;
AnnotationValidations.validate(NonNull.class, null, mSlice);
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
}
+ /** Constructor to be used for an entry that does not require further activities
+ * to be invoked when selected.
+ */
public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice) {
mKey = key;
mSubkey = subkey;
mSlice = slice;
}
+ /** Constructor to be used for an entry that requires a pending intent to be invoked
+ * when clicked.
+ */
+ public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
+ @NonNull PendingIntent pendingIntent, @Nullable Intent intent) {
+ this(key, subkey, slice);
+ mPendingIntent = pendingIntent;
+ mFrameworkExtrasIntent = intent;
+ }
+
/**
* Returns the identifier of this entry that's unique within the context of the CredentialManager
* request.
@@ -133,11 +153,23 @@ public class Entry implements Parcelable {
return mSlice;
}
+ @Nullable
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ @Nullable
+ public Intent getFrameworkExtrasIntent() {
+ return mFrameworkExtrasIntent;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mKey);
dest.writeString8(mSubkey);
mSlice.writeToParcel(dest, flags);
+ mPendingIntent.writeToParcel(dest, flags);
+ mFrameworkExtrasIntent.writeToParcel(dest, flags);
}
@Override
diff --git a/core/java/android/credentials/ui/ProviderPendingIntentResponse.java b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
new file mode 100644
index 000000000000..420956f69b7f
--- /dev/null
+++ b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Response from a provider's pending intent
+ *
+ * @hide
+ */
+public final class ProviderPendingIntentResponse implements Parcelable {
+ private final int mResultCode;
+ @Nullable
+ private final Intent mResultData;
+
+ public ProviderPendingIntentResponse(int resultCode, @Nullable Intent resultData) {
+ mResultCode = resultCode;
+ mResultData = resultData;
+ }
+
+ protected ProviderPendingIntentResponse(Parcel in) {
+ mResultCode = in.readInt();
+ mResultData = in.readTypedObject(Intent.CREATOR);
+ }
+
+ public static final Creator<ProviderPendingIntentResponse> CREATOR =
+ new Creator<ProviderPendingIntentResponse>() {
+ @Override
+ public ProviderPendingIntentResponse createFromParcel(Parcel in) {
+ return new ProviderPendingIntentResponse(in);
+ }
+
+ @Override
+ public ProviderPendingIntentResponse[] newArray(int size) {
+ return new ProviderPendingIntentResponse[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeSerializable(mResultCode);
+ dest.writeTypedObject(mResultData, flags);
+ }
+
+ /** Returns the result code associated with this pending intent activity result. */
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ /** Returns the result data associated with this pending intent activity result. */
+ @NonNull public Intent getResultData() {
+ return mResultData;
+ }
+}
diff --git a/core/java/android/credentials/ui/UserSelectionDialogResult.java b/core/java/android/credentials/ui/UserSelectionDialogResult.java
index 6025d78d1bae..0e8e7b6f06a5 100644
--- a/core/java/android/credentials/ui/UserSelectionDialogResult.java
+++ b/core/java/android/credentials/ui/UserSelectionDialogResult.java
@@ -57,6 +57,7 @@ public class UserSelectionDialogResult extends BaseDialogResult implements Parce
@NonNull private final String mProviderId;
@NonNull private final String mEntryKey;
@NonNull private final String mEntrySubkey;
+ @Nullable private ProviderPendingIntentResponse mProviderPendingIntentResponse;
public UserSelectionDialogResult(
@NonNull IBinder requestToken, @NonNull String providerId,
@@ -67,6 +68,17 @@ public class UserSelectionDialogResult extends BaseDialogResult implements Parce
mEntrySubkey = entrySubkey;
}
+ public UserSelectionDialogResult(
+ @NonNull IBinder requestToken, @NonNull String providerId,
+ @NonNull String entryKey, @NonNull String entrySubkey,
+ @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
+ super(requestToken);
+ mProviderId = providerId;
+ mEntryKey = entryKey;
+ mEntrySubkey = entrySubkey;
+ mProviderPendingIntentResponse = providerPendingIntentResponse;
+ }
+
/** Returns provider package name whose entry was selected by the user. */
@NonNull
public String getProviderId() {
@@ -85,6 +97,12 @@ public class UserSelectionDialogResult extends BaseDialogResult implements Parce
return mEntrySubkey;
}
+ /** Returns the pending intent response from the provider. */
+ @Nullable
+ public ProviderPendingIntentResponse getPendingIntentProviderResponse() {
+ return mProviderPendingIntentResponse;
+ }
+
protected UserSelectionDialogResult(@NonNull Parcel in) {
super(in);
String providerId = in.readString8();
@@ -97,6 +115,7 @@ public class UserSelectionDialogResult extends BaseDialogResult implements Parce
AnnotationValidations.validate(NonNull.class, null, mEntryKey);
mEntrySubkey = entrySubkey;
AnnotationValidations.validate(NonNull.class, null, mEntrySubkey);
+ mProviderPendingIntentResponse = in.readTypedObject(ProviderPendingIntentResponse.CREATOR);
}
@Override
@@ -105,6 +124,7 @@ public class UserSelectionDialogResult extends BaseDialogResult implements Parce
dest.writeString8(mProviderId);
dest.writeString8(mEntryKey);
dest.writeString8(mEntrySubkey);
+ dest.writeTypedObject(mProviderPendingIntentResponse, flags);
}
@Override
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index d3cb59dbb034..f561278bb25a 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1310,6 +1310,22 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<int[]>("android.control.availableSettingsOverrides", int[].class);
/**
+ * <p>Whether the camera device supports {@link CaptureRequest#CONTROL_AUTOFRAMING android.control.autoframing}.</p>
+ * <p>Will be <code>false</code> if auto-framing is not available.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CaptureRequest#CONTROL_AUTOFRAMING
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Boolean> CONTROL_AUTOFRAMING_AVAILABLE =
+ new Key<Boolean>("android.control.autoframingAvailable", boolean.class);
+
+ /**
* <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera
* device.</p>
* <p>Full-capability camera devices must always support OFF; camera devices that support
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 545aa8f2e80d..44f8b1bfda07 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -3244,6 +3244,28 @@ public abstract class CameraMetadata<TKey> {
public static final int CONTROL_SETTINGS_OVERRIDE_VENDOR_START = 0x4000;
//
+ // Enumeration values for CaptureRequest#CONTROL_AUTOFRAMING
+ //
+
+ /**
+ * <p>Disable autoframing.</p>
+ * @see CaptureRequest#CONTROL_AUTOFRAMING
+ */
+ public static final int CONTROL_AUTOFRAMING_OFF = 0;
+
+ /**
+ * <p>Enable autoframing to keep people in the frame's field of view.</p>
+ * @see CaptureRequest#CONTROL_AUTOFRAMING
+ */
+ public static final int CONTROL_AUTOFRAMING_ON = 1;
+
+ /**
+ * <p>Automatically select ON or OFF based on the system level preferences.</p>
+ * @see CaptureRequest#CONTROL_AUTOFRAMING
+ */
+ public static final int CONTROL_AUTOFRAMING_AUTO = 2;
+
+ //
// Enumeration values for CaptureRequest#EDGE_MODE
//
@@ -3997,6 +4019,29 @@ public abstract class CameraMetadata<TKey> {
public static final int CONTROL_AF_SCENE_CHANGE_DETECTED = 1;
//
+ // Enumeration values for CaptureResult#CONTROL_AUTOFRAMING_STATE
+ //
+
+ /**
+ * <p>Auto-framing is inactive.</p>
+ * @see CaptureResult#CONTROL_AUTOFRAMING_STATE
+ */
+ public static final int CONTROL_AUTOFRAMING_STATE_INACTIVE = 0;
+
+ /**
+ * <p>Auto-framing is in process - either zooming in, zooming out or pan is taking place.</p>
+ * @see CaptureResult#CONTROL_AUTOFRAMING_STATE
+ */
+ public static final int CONTROL_AUTOFRAMING_STATE_FRAMING = 1;
+
+ /**
+ * <p>Auto-framing has reached a stable state (frame/fov is not being adjusted). The state
+ * may transition back to FRAMING if the scene changes.</p>
+ * @see CaptureResult#CONTROL_AUTOFRAMING_STATE
+ */
+ public static final int CONTROL_AUTOFRAMING_STATE_CONVERGED = 2;
+
+ //
// Enumeration values for CaptureResult#FLASH_STATE
//
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 407ea07838de..8bb6fa5a1a14 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2513,6 +2513,42 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
new Key<Integer>("android.control.settingsOverride", int.class);
/**
+ * <p>Automatic crop, pan and zoom to keep objects in the center of the frame.</p>
+ * <p>Auto-framing is a special mode provided by the camera device to dynamically crop, zoom
+ * or pan the camera feed to try to ensure that the people in a scene occupy a reasonable
+ * portion of the viewport. It is primarily designed to support video calling in
+ * situations where the user isn't directly in front of the device, especially for
+ * wide-angle cameras.
+ * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} and {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} in CaptureResult will be used
+ * to denote the coordinates of the auto-framed region.
+ * Zoom and video stabilization controls are disabled when auto-framing is enabled. The 3A
+ * regions must map the screen coordinates into the scaler crop returned from the capture
+ * result instead of using the active array sensor.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_AUTOFRAMING_OFF OFF}</li>
+ * <li>{@link #CONTROL_AUTOFRAMING_ON ON}</li>
+ * <li>{@link #CONTROL_AUTOFRAMING_AUTO AUTO}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see #CONTROL_AUTOFRAMING_OFF
+ * @see #CONTROL_AUTOFRAMING_ON
+ * @see #CONTROL_AUTOFRAMING_AUTO
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> CONTROL_AUTOFRAMING =
+ new Key<Integer>("android.control.autoframing", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index c4f0cabc5f78..c5246b52a6cf 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2717,6 +2717,79 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
new Key<Integer>("android.control.settingsOverride", int.class);
/**
+ * <p>Automatic crop, pan and zoom to keep objects in the center of the frame.</p>
+ * <p>Auto-framing is a special mode provided by the camera device to dynamically crop, zoom
+ * or pan the camera feed to try to ensure that the people in a scene occupy a reasonable
+ * portion of the viewport. It is primarily designed to support video calling in
+ * situations where the user isn't directly in front of the device, especially for
+ * wide-angle cameras.
+ * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} and {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} in CaptureResult will be used
+ * to denote the coordinates of the auto-framed region.
+ * Zoom and video stabilization controls are disabled when auto-framing is enabled. The 3A
+ * regions must map the screen coordinates into the scaler crop returned from the capture
+ * result instead of using the active array sensor.</p>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_AUTOFRAMING_OFF OFF}</li>
+ * <li>{@link #CONTROL_AUTOFRAMING_ON ON}</li>
+ * <li>{@link #CONTROL_AUTOFRAMING_AUTO AUTO}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SCALER_CROP_REGION
+ * @see #CONTROL_AUTOFRAMING_OFF
+ * @see #CONTROL_AUTOFRAMING_ON
+ * @see #CONTROL_AUTOFRAMING_AUTO
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> CONTROL_AUTOFRAMING =
+ new Key<Integer>("android.control.autoframing", int.class);
+
+ /**
+ * <p>Current state of auto-framing.</p>
+ * <p>When the camera doesn't have auto-framing available (i.e
+ * <code>{@link CameraCharacteristics#CONTROL_AUTOFRAMING_AVAILABLE android.control.autoframingAvailable}</code> == false) or it is not enabled (i.e
+ * <code>{@link CaptureRequest#CONTROL_AUTOFRAMING android.control.autoframing}</code> == OFF), the state will always be INACTIVE.
+ * Other states indicate the current auto-framing state:</p>
+ * <ul>
+ * <li>When <code>{@link CaptureRequest#CONTROL_AUTOFRAMING android.control.autoframing}</code> is set to ON, auto-framing will take
+ * place. While the frame is aligning itself to center the object (doing things like
+ * zooming in, zooming out or pan), the state will be FRAMING.</li>
+ * <li>When field of view is not being adjusted anymore and has reached a stable state, the
+ * state will be CONVERGED.</li>
+ * </ul>
+ * <p><b>Possible values:</b></p>
+ * <ul>
+ * <li>{@link #CONTROL_AUTOFRAMING_STATE_INACTIVE INACTIVE}</li>
+ * <li>{@link #CONTROL_AUTOFRAMING_STATE_FRAMING FRAMING}</li>
+ * <li>{@link #CONTROL_AUTOFRAMING_STATE_CONVERGED CONVERGED}</li>
+ * </ul>
+ *
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CaptureRequest#CONTROL_AUTOFRAMING
+ * @see CameraCharacteristics#CONTROL_AUTOFRAMING_AVAILABLE
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see #CONTROL_AUTOFRAMING_STATE_INACTIVE
+ * @see #CONTROL_AUTOFRAMING_STATE_FRAMING
+ * @see #CONTROL_AUTOFRAMING_STATE_CONVERGED
+ */
+ @PublicKey
+ @NonNull
+ public static final Key<Integer> CONTROL_AUTOFRAMING_STATE =
+ new Key<Integer>("android.control.autoframingState", int.class);
+
+ /**
* <p>Operation mode for edge
* enhancement.</p>
* <p>Edge enhancement improves sharpness and details in the captured image. OFF means
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index e483328feb04..ac1583aec376 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -895,9 +895,21 @@ public class Process {
return isIsolated(myUid());
}
- /** {@hide} */
- @UnsupportedAppUsage
+ /**
+ * @deprecated Use {@link #isIsolatedUid(int)} instead.
+ * {@hide}
+ */
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+ publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
public static final boolean isIsolated(int uid) {
+ return isIsolatedUid(uid);
+ }
+
+ /**
+ * Returns whether the process with the given {@code uid} is an isolated sandbox.
+ */
+ public static final boolean isIsolatedUid(int uid) {
uid = UserHandle.getAppId(uid);
return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
|| (uid >= FIRST_APP_ZYGOTE_ISOLATED_UID && uid <= LAST_APP_ZYGOTE_ISOLATED_UID);
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 7095d1b20d84..8a09cd77efdb 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -25,9 +25,6 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.app.ActivityThread;
-import android.content.ContentResolver;
-import android.content.Context;
import android.content.pm.PackageManager;
import android.database.ContentObserver;
import android.net.Uri;
@@ -131,6 +128,13 @@ public final class DeviceConfig {
public static final String NAMESPACE_APP_STANDBY = "app_standby";
/**
+ * Namespace for all App Cloning related features.
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final String NAMESPACE_APP_CLONING = "app_cloning";
+
+ /**
* Namespace for AttentionManagerService related features.
*
* @hide
@@ -875,9 +879,8 @@ public final class DeviceConfig {
@NonNull
@RequiresPermission(READ_DEVICE_CONFIG)
public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
return new Properties(namespace,
- Settings.Config.getStrings(contentResolver, namespace, Arrays.asList(names)));
+ Settings.Config.getStrings(namespace, Arrays.asList(names)));
}
/**
@@ -1016,8 +1019,7 @@ public final class DeviceConfig {
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static boolean setProperty(@NonNull String namespace, @NonNull String name,
@Nullable String value, boolean makeDefault) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- return Settings.Config.putString(contentResolver, namespace, name, value, makeDefault);
+ return Settings.Config.putString(namespace, name, value, makeDefault);
}
/**
@@ -1038,8 +1040,7 @@ public final class DeviceConfig {
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static boolean setProperties(@NonNull Properties properties) throws BadConfigException {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- return Settings.Config.setStrings(contentResolver, properties.getNamespace(),
+ return Settings.Config.setStrings(properties.getNamespace(),
properties.mMap);
}
@@ -1055,8 +1056,7 @@ public final class DeviceConfig {
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static boolean deleteProperty(@NonNull String namespace, @NonNull String name) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- return Settings.Config.deleteString(contentResolver, namespace, name);
+ return Settings.Config.deleteString(namespace, name);
}
/**
@@ -1087,8 +1087,7 @@ public final class DeviceConfig {
@SystemApi
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static void resetToDefaults(@ResetMode int resetMode, @Nullable String namespace) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- Settings.Config.resetToDefaults(contentResolver, resetMode, namespace);
+ Settings.Config.resetToDefaults(resetMode, namespace);
}
/**
@@ -1105,8 +1104,7 @@ public final class DeviceConfig {
*/
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static void setSyncDisabledMode(@SyncDisabledMode int syncDisabledMode) {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- Settings.Config.setSyncDisabledMode(contentResolver, syncDisabledMode);
+ Settings.Config.setSyncDisabledMode(syncDisabledMode);
}
/**
@@ -1117,8 +1115,7 @@ public final class DeviceConfig {
*/
@RequiresPermission(WRITE_DEVICE_CONFIG)
public static @SyncDisabledMode int getSyncDisabledMode() {
- ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- return Settings.Config.getSyncDisabledMode(contentResolver);
+ return Settings.Config.getSyncDisabledMode();
}
/**
@@ -1141,8 +1138,7 @@ public final class DeviceConfig {
@NonNull String namespace,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnPropertiesChangedListener onPropertiesChangedListener) {
- enforceReadPermission(ActivityThread.currentApplication().getApplicationContext(),
- namespace);
+ enforceReadPermission(namespace);
synchronized (sLock) {
Pair<String, Executor> oldNamespace = sListeners.get(onPropertiesChangedListener);
if (oldNamespace == null) {
@@ -1209,7 +1205,7 @@ public final class DeviceConfig {
}
}
};
- ActivityThread.currentApplication().getContentResolver()
+ Settings.Config
.registerContentObserver(createNamespaceUri(namespace), true, contentObserver);
sNamespaces.put(namespace, new Pair<>(contentObserver, 1));
}
@@ -1233,8 +1229,7 @@ public final class DeviceConfig {
sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second - 1));
} else {
// Decrementing a namespace to zero means we no longer need its ContentObserver.
- ActivityThread.currentApplication().getContentResolver()
- .unregisterContentObserver(namespaceCount.first);
+ Settings.Config.unregisterContentObserver(namespaceCount.first);
sNamespaces.remove(namespace);
}
}
@@ -1274,8 +1269,8 @@ public final class DeviceConfig {
* Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
* @hide
*/
- public static void enforceReadPermission(Context context, String namespace) {
- if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
+ public static void enforceReadPermission(String namespace) {
+ if (Settings.Config.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
!= PackageManager.PERMISSION_GRANTED) {
if (!PUBLIC_NAMESPACES.contains(namespace)) {
throw new SecurityException("Permission denial: reading from settings requires:"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 52b1adb876fc..ef448f5dbd1b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -47,9 +47,11 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PermissionName;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.database.Cursor;
import android.database.SQLException;
import android.location.ILocationManager;
@@ -3344,7 +3346,7 @@ public final class Settings {
public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
List<String> names) {
String namespace = prefix.substring(0, prefix.length() - 1);
- DeviceConfig.enforceReadPermission(ActivityThread.currentApplication(), namespace);
+ DeviceConfig.enforceReadPermission(namespace);
ArrayMap<String, String> keyValues = new ArrayMap<>();
int currentGeneration = -1;
@@ -18002,20 +18004,36 @@ public final class Settings {
/**
* Look up a name in the database.
- * @param resolver to access the database with
* @param name to look up in the table
* @return the corresponding value, or null if not present
*
* @hide
*/
@RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
- static String getString(ContentResolver resolver, String name) {
+ static String getString(String name) {
+ ContentResolver resolver = getContentResolver();
return sNameValueCache.getStringForUser(resolver, name, resolver.getUserId());
}
/**
* Look up a list of names in the database, within the specified namespace.
*
+ * @param namespace to which the names belong
+ * @param names to look up in the table
+ * @return a non null, but possibly empty, map from name to value for any of the names that
+ * were found during lookup.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ public static Map<String, String> getStrings(@NonNull String namespace,
+ @NonNull List<String> names) {
+ return getStrings(getContentResolver(), namespace, names);
+ }
+
+ /**
+ * Look up a list of names in the database, within the specified namespace.
+ *
* @param resolver to access the database with
* @param namespace to which the names belong
* @param names to look up in the table
@@ -18053,7 +18071,6 @@ public final class Settings {
* <strong>not</strong> be set as the default.
* </p>
*
- * @param resolver to access the database with.
* @param namespace to store the name/value pair in.
* @param name to store.
* @param value to associate with the name.
@@ -18065,8 +18082,9 @@ public final class Settings {
* @hide
*/
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static boolean putString(@NonNull ContentResolver resolver, @NonNull String namespace,
+ public static boolean putString(@NonNull String namespace,
@NonNull String name, @Nullable String value, boolean makeDefault) {
+ ContentResolver resolver = getContentResolver();
return sNameValueCache.putStringForUser(resolver, createCompositeName(namespace, name),
value, null, makeDefault, resolver.getUserId(),
DEFAULT_OVERRIDEABLE_BY_RESTORE);
@@ -18076,6 +18094,23 @@ public final class Settings {
* Clear all name/value pairs for the provided namespace and save new name/value pairs in
* their place.
*
+ * @param namespace to which the names should be set.
+ * @param keyValues map of key names (without the prefix) to values.
+ * @return true if the name/value pairs were set, false if setting was blocked
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
+ public static boolean setStrings(@NonNull String namespace,
+ @NonNull Map<String, String> keyValues)
+ throws DeviceConfig.BadConfigException {
+ return setStrings(getContentResolver(), namespace, keyValues);
+ }
+
+ /**
+ * Clear all name/value pairs for the provided namespace and save new name/value pairs in
+ * their place.
+ *
* @param resolver to access the database with.
* @param namespace to which the names should be set.
* @param keyValues map of key names (without the prefix) to values.
@@ -18106,7 +18141,6 @@ public final class Settings {
/**
* Delete a name/value pair from the database for the specified namespace.
*
- * @param resolver to access the database with.
* @param namespace to delete the name/value pair from.
* @param name to delete.
* @return true if the value was deleted, false on database errors. If the name/value pair
@@ -18117,8 +18151,9 @@ public final class Settings {
* @hide
*/
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static boolean deleteString(@NonNull ContentResolver resolver, @NonNull String namespace,
+ static boolean deleteString(@NonNull String namespace,
@NonNull String name) {
+ ContentResolver resolver = getContentResolver();
return sNameValueCache.deleteStringForUser(resolver,
createCompositeName(namespace, name), resolver.getUserId());
}
@@ -18129,7 +18164,6 @@ public final class Settings {
* The method accepts an optional prefix parameter. If provided, only pairs with a name that
* starts with the exact prefix will be reset. Otherwise all will be reset.
*
- * @param resolver Handle to the content resolver.
* @param resetMode The reset mode to use.
* @param namespace Optionally, to limit which which namespace is reset.
*
@@ -18138,9 +18172,10 @@ public final class Settings {
* @hide
*/
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static void resetToDefaults(@NonNull ContentResolver resolver, @ResetMode int resetMode,
+ static void resetToDefaults(@ResetMode int resetMode,
@Nullable String namespace) {
try {
+ ContentResolver resolver = getContentResolver();
Bundle arg = new Bundle();
arg.putInt(CALL_METHOD_USER_KEY, resolver.getUserId());
arg.putInt(CALL_METHOD_RESET_MODE_KEY, resetMode);
@@ -18163,9 +18198,9 @@ public final class Settings {
*/
@SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static void setSyncDisabledMode(
- @NonNull ContentResolver resolver, @SyncDisabledMode int disableSyncMode) {
+ static void setSyncDisabledMode(@SyncDisabledMode int disableSyncMode) {
try {
+ ContentResolver resolver = getContentResolver();
Bundle args = new Bundle();
args.putInt(CALL_METHOD_SYNC_DISABLED_MODE_KEY, disableSyncMode);
IContentProvider cp = sProviderHolder.getProvider(resolver);
@@ -18184,8 +18219,9 @@ public final class Settings {
*/
@SuppressLint("AndroidFrameworkRequiresPermission")
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
- static int getSyncDisabledMode(@NonNull ContentResolver resolver) {
+ static int getSyncDisabledMode() {
try {
+ ContentResolver resolver = getContentResolver();
Bundle args = Bundle.EMPTY;
IContentProvider cp = sProviderHolder.getProvider(resolver);
Bundle bundle = cp.call(resolver.getAttributionSource(),
@@ -18202,7 +18238,6 @@ public final class Settings {
/**
* Register callback for monitoring Config table.
*
- * @param resolver Handle to the content resolver.
* @param callback callback to register
*
* @hide
@@ -18213,6 +18248,50 @@ public final class Settings {
registerMonitorCallbackAsUser(resolver, resolver.getUserId(), callback);
}
+
+ /**
+ * Register a content observer
+ *
+ * @hide
+ */
+ public static void registerContentObserver(@NonNull Uri uri, boolean notifyForDescendants,
+ @NonNull ContentObserver observer) {
+ ActivityThread.currentApplication().getContentResolver()
+ .registerContentObserver(uri, notifyForDescendants, observer);
+ }
+
+ /**
+ * Unregister a content observer
+ *
+ * @hide
+ */
+ public static void unregisterContentObserver(@NonNull ContentObserver observer) {
+ ActivityThread.currentApplication().getContentResolver()
+ .unregisterContentObserver(observer);
+ }
+
+ /**
+ * Determine whether the calling process of an IPC <em>or you</em> have been
+ * granted a particular permission. This is the same as
+ * {@link #checkCallingPermission}, except it grants your own permissions
+ * if you are not currently processing an IPC. Use with care!
+ *
+ * @param permission The name of the permission being checked.
+ *
+ * @return {@link PackageManager#PERMISSION_GRANTED} if the calling
+ * pid/uid is allowed that permission, or
+ * {@link PackageManager#PERMISSION_DENIED} if it is not.
+ *
+ * @see PackageManager#checkPermission(String, String)
+ * @see #checkPermission
+ * @see #checkCallingPermission
+ * @hide
+ */
+ public static int checkCallingOrSelfPermission(@NonNull @PermissionName String permission) {
+ return ActivityThread.currentApplication()
+ .getApplicationContext().checkCallingOrSelfPermission(permission);
+ }
+
private static void registerMonitorCallbackAsUser(
@NonNull ContentResolver resolver, @UserIdInt int userHandle,
@NonNull RemoteCallback callback) {
@@ -18245,6 +18324,10 @@ public final class Settings {
Preconditions.checkNotNull(namespace);
return namespace + "/";
}
+
+ private static ContentResolver getContentResolver() {
+ return ActivityThread.currentApplication().getContentResolver();
+ }
}
/**
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 6f3e786ffc4e..24b7c3c439d4 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -43,8 +43,13 @@ import java.util.Objects;
public abstract class CredentialProviderService extends Service {
/** Extra to be used by provider to populate the credential when ending the activity started
* through the {@code pendingIntent} on the selected {@link SaveEntry}. **/
- public static final String EXTRA_SAVE_CREDENTIAL =
- "android.service.credentials.extra.SAVE_CREDENTIAL";
+ public static final String EXTRA_CREATE_CREDENTIAL_RESPONSE =
+ "android.service.credentials.extra.CREATE_CREDENTIAL_RESPONSE";
+
+ /** Extra to be used by provider to populate the {@link CredentialsDisplayContent} when
+ * an authentication action entry is selected. **/
+ public static final String EXTRA_GET_CREDENTIALS_DISPLAY_CONTENT =
+ "android.service.credentials.extra.GET_CREDENTIALS_DISPLAY_CONTENT";
/**
* Provider must read the value against this extra to receive the complete create credential
@@ -53,6 +58,10 @@ public abstract class CredentialProviderService extends Service {
public static final String EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS =
"android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST_PARAMS";
+ /** Extra to be used by the provider when setting the credential result. */
+ public static final String EXTRA_GET_CREDENTIAL =
+ "android.service.credentials.extra.GET_CREDENTIAL";
+
private static final String TAG = "CredProviderService";
public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index cfc79e4fef66..e821af1ab313 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -2365,6 +2365,7 @@ public abstract class NotificationListenerService extends Service {
UserHandle user= (UserHandle) args.arg2;
NotificationChannel channel = (NotificationChannel) args.arg3;
int modificationType = (int) args.arg4;
+ args.recycle();
onNotificationChannelModified(pkgName, user, channel, modificationType);
} break;
@@ -2374,6 +2375,7 @@ public abstract class NotificationListenerService extends Service {
UserHandle user = (UserHandle) args.arg2;
NotificationChannelGroup group = (NotificationChannelGroup) args.arg3;
int modificationType = (int) args.arg4;
+ args.recycle();
onNotificationChannelGroupModified(pkgName, user, group, modificationType);
} break;
diff --git a/core/java/android/transparency/BinaryTransparencyManager.java b/core/java/android/transparency/BinaryTransparencyManager.java
index 18783f507085..f6d7c611e9d9 100644
--- a/core/java/android/transparency/BinaryTransparencyManager.java
+++ b/core/java/android/transparency/BinaryTransparencyManager.java
@@ -24,7 +24,7 @@ import android.util.Slog;
import com.android.internal.os.IBinaryTransparencyService;
-import java.util.Map;
+import java.util.List;
/**
* BinaryTransparencyManager defines a number of system interfaces that other system apps or
@@ -66,12 +66,15 @@ public class BinaryTransparencyManager {
}
/**
- * Returns a map of all installed APEXs consisting of package name to SHA256 hash of the
- * package.
- * @return A Map with the following entries: {apex package name : sha256 digest of package}
+ * Gets binary measurements of all installed APEXs, each packed in a Bundle.
+ * @return A List of {@link android.os.Bundle}s with the following keys:
+ * {@link com.android.server.BinaryTransparencyService#BUNDLE_PACKAGE_INFO}
+ * {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST_ALGORITHM}
+ * {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST}
*/
+ // TODO(b/259422958): Fix static constants referenced here - should be defined here
@NonNull
- public Map getApexInfo() {
+ public List getApexInfo() {
try {
Slog.d(TAG, "Calling backend's getApexInfo()");
return mService.getApexInfo();
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index b24303b41abd..720813ad81ef 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1073,7 +1073,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
private void handleSyncBufferCallback(SurfaceHolder.Callback[] callbacks,
SyncBufferTransactionCallback syncBufferTransactionCallback) {
- getViewRootImpl().addToSync(syncBufferCallback ->
+ getViewRootImpl().addToSync((parentSyncGroup, syncBufferCallback) ->
redrawNeededAsync(callbacks, () -> {
Transaction t = null;
if (mBlastBufferQueue != null) {
@@ -1081,7 +1081,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
t = syncBufferTransactionCallback.waitForTransaction();
}
- syncBufferCallback.onBufferReady(t);
+ syncBufferCallback.onTransactionReady(t);
onDrawFinished();
}));
}
@@ -1092,9 +1092,9 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mSyncGroups.add(syncGroup);
}
- syncGroup.addToSync(syncBufferCallback -> redrawNeededAsync(callbacks,
- () -> {
- syncBufferCallback.onBufferReady(null);
+ syncGroup.addToSync((parentSyncGroup, syncBufferCallback) ->
+ redrawNeededAsync(callbacks, () -> {
+ syncBufferCallback.onTransactionReady(null);
onDrawFinished();
synchronized (mSyncGroups) {
mSyncGroups.remove(syncGroup);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7e8ebd7fe076..5e1dc340a7a7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -231,6 +231,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
/**
* The top of a view hierarchy, implementing the needed protocol between View
@@ -851,7 +852,7 @@ public final class ViewRootImpl implements ViewParent,
}
private SurfaceSyncGroup mSyncGroup;
- private SurfaceSyncGroup.SyncBufferCallback mSyncBufferCallback;
+ private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
private int mNumSyncsInProgress = 0;
private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
@@ -3610,8 +3611,8 @@ public final class ViewRootImpl implements ViewParent,
mPendingTransitions.clear();
}
- if (mSyncBufferCallback != null) {
- mSyncBufferCallback.onBufferReady(null);
+ if (mTransactionReadyCallback != null) {
+ mTransactionReadyCallback.onTransactionReady(null);
}
} else if (cancelAndRedraw) {
mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
@@ -3626,8 +3627,8 @@ public final class ViewRootImpl implements ViewParent,
}
mPendingTransitions.clear();
}
- if (!performDraw() && mSyncBufferCallback != null) {
- mSyncBufferCallback.onBufferReady(null);
+ if (!performDraw() && mTransactionReadyCallback != null) {
+ mTransactionReadyCallback.onTransactionReady(null);
}
}
@@ -3641,7 +3642,7 @@ public final class ViewRootImpl implements ViewParent,
if (!cancelAndRedraw) {
mReportNextDraw = false;
mLastReportNextDrawReason = null;
- mSyncBufferCallback = null;
+ mTransactionReadyCallback = null;
mSyncBuffer = false;
if (isInLocalSync()) {
mSyncGroup.markSyncReady();
@@ -4388,7 +4389,7 @@ public final class ViewRootImpl implements ViewParent,
return false;
}
- final boolean fullRedrawNeeded = mFullRedrawNeeded || mSyncBufferCallback != null;
+ final boolean fullRedrawNeeded = mFullRedrawNeeded || mTransactionReadyCallback != null;
mFullRedrawNeeded = false;
mIsDrawing = true;
@@ -4396,9 +4397,9 @@ public final class ViewRootImpl implements ViewParent,
addFrameCommitCallbackIfNeeded();
- boolean usingAsyncReport = isHardwareEnabled() && mSyncBufferCallback != null;
+ boolean usingAsyncReport = isHardwareEnabled() && mTransactionReadyCallback != null;
if (usingAsyncReport) {
- registerCallbacksForSync(mSyncBuffer, mSyncBufferCallback);
+ registerCallbacksForSync(mSyncBuffer, mTransactionReadyCallback);
} else if (mHasPendingTransactions) {
// These callbacks are only needed if there's no sync involved and there were calls to
// applyTransactionOnDraw. These callbacks check if the draw failed for any reason and
@@ -4449,10 +4450,11 @@ public final class ViewRootImpl implements ViewParent,
}
if (mSurfaceHolder != null && mSurface.isValid()) {
- final SurfaceSyncGroup.SyncBufferCallback syncBufferCallback = mSyncBufferCallback;
+ final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback =
+ mTransactionReadyCallback;
SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() ->
- mHandler.post(() -> syncBufferCallback.onBufferReady(null)));
- mSyncBufferCallback = null;
+ mHandler.post(() -> transactionReadyCallback.onTransactionReady(null)));
+ mTransactionReadyCallback = null;
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
@@ -4463,8 +4465,8 @@ public final class ViewRootImpl implements ViewParent,
}
}
}
- if (mSyncBufferCallback != null && !usingAsyncReport) {
- mSyncBufferCallback.onBufferReady(null);
+ if (mTransactionReadyCallback != null && !usingAsyncReport) {
+ mTransactionReadyCallback.onTransactionReady(null);
}
if (mPerformContentCapture) {
performContentCaptureInitialReport();
@@ -11134,7 +11136,7 @@ public final class ViewRootImpl implements ViewParent,
}
private void registerCallbacksForSync(boolean syncBuffer,
- final SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
+ final SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
if (!isHardwareEnabled()) {
return;
}
@@ -11161,7 +11163,7 @@ public final class ViewRootImpl implements ViewParent,
// pendingDrawFinished.
if ((syncResult
& (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
- syncBufferCallback.onBufferReady(
+ transactionReadyCallback.onTransactionReady(
mBlastBufferQueue.gatherPendingTransactions(frame));
return null;
}
@@ -11171,7 +11173,8 @@ public final class ViewRootImpl implements ViewParent,
}
if (syncBuffer) {
- mBlastBufferQueue.syncNextTransaction(syncBufferCallback::onBufferReady);
+ mBlastBufferQueue.syncNextTransaction(
+ transactionReadyCallback::onTransactionReady);
}
return didProduceBuffer -> {
@@ -11191,7 +11194,7 @@ public final class ViewRootImpl implements ViewParent,
// since the frame didn't draw on this vsync. It's possible the frame will
// draw later, but it's better to not be sync than to block on a frame that
// may never come.
- syncBufferCallback.onBufferReady(
+ transactionReadyCallback.onTransactionReady(
mBlastBufferQueue.gatherPendingTransactions(frame));
return;
}
@@ -11200,22 +11203,49 @@ public final class ViewRootImpl implements ViewParent,
// syncNextTransaction callback. Instead, just report back to the Syncer so it
// knows that this sync request is complete.
if (!syncBuffer) {
- syncBufferCallback.onBufferReady(null);
+ transactionReadyCallback.onTransactionReady(null);
}
};
}
});
}
- public final SurfaceSyncGroup.SyncTarget mSyncTarget = new SurfaceSyncGroup.SyncTarget() {
+ private final Executor mPostAtFrontExecutor = new Executor() {
@Override
- public void onReadyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
- readyToSync(syncBufferCallback);
+ public void execute(Runnable command) {
+ mHandler.postAtFrontOfQueue(command);
}
+ };
+ public final SurfaceSyncGroup.SyncTarget mSyncTarget = new SurfaceSyncGroup.SyncTarget() {
@Override
- public void onSyncComplete() {
- mHandler.postAtFrontOfQueue(() -> {
+ public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+ SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
+ updateSyncInProgressCount(parentSyncGroup);
+ if (!isInLocalSync()) {
+ // Always sync the buffer if the sync request did not come from VRI.
+ mSyncBuffer = true;
+ }
+ if (mAttachInfo.mThreadedRenderer != null) {
+ HardwareRenderer.setRtAnimationsEnabled(false);
+ }
+
+ if (mTransactionReadyCallback != null) {
+ Log.d(mTag, "Already set sync for the next draw.");
+ mTransactionReadyCallback.onTransactionReady(null);
+ }
+ if (DEBUG_BLAST) {
+ Log.d(mTag, "Setting syncFrameCallback");
+ }
+ mTransactionReadyCallback = transactionReadyCallback;
+ if (!mIsInTraversal && !mTraversalScheduled) {
+ scheduleTraversals();
+ }
+ }
+
+ private void updateSyncInProgressCount(SurfaceSyncGroup parentSyncGroup) {
+ mNumSyncsInProgress++;
+ parentSyncGroup.addSyncCompleteCallback(mPostAtFrontExecutor, () -> {
if (--mNumSyncsInProgress == 0 && mAttachInfo.mThreadedRenderer != null) {
HardwareRenderer.setRtAnimationsEnabled(true);
}
@@ -11228,29 +11258,6 @@ public final class ViewRootImpl implements ViewParent,
return mSyncTarget;
}
- private void readyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
- mNumSyncsInProgress++;
- if (!isInLocalSync()) {
- // Always sync the buffer if the sync request did not come from VRI.
- mSyncBuffer = true;
- }
- if (mAttachInfo.mThreadedRenderer != null) {
- HardwareRenderer.setRtAnimationsEnabled(false);
- }
-
- if (mSyncBufferCallback != null) {
- Log.d(mTag, "Already set sync for the next draw.");
- mSyncBufferCallback.onBufferReady(null);
- }
- if (DEBUG_BLAST) {
- Log.d(mTag, "Setting syncFrameCallback");
- }
- mSyncBufferCallback = syncBufferCallback;
- if (!mIsInTraversal && !mTraversalScheduled) {
- scheduleTraversals();
- }
- }
-
void mergeSync(SurfaceSyncGroup otherSyncGroup) {
if (!isInLocalSync()) {
return;
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 1df4fc54089e..500c41cd1fed 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -16,12 +16,13 @@
package android.view.inputmethod;
+import static android.graphics.Typeface.NORMAL;
+
import android.annotation.ColorInt;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
-import android.content.res.ColorStateList;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.fonts.FontStyle;
@@ -30,7 +31,7 @@ import android.inputmethodservice.InputMethodService;
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.InputFilter;
+import android.text.method.TransformationMethod;
import android.widget.TextView;
import java.util.Objects;
@@ -38,7 +39,6 @@ import java.util.Objects;
/**
* Information about text appearance in an editor, passed through
* {@link CursorAnchorInfo} for use by {@link InputMethodService}.
- *
* @see TextView
* @see Paint
* @see CursorAnchorInfo.Builder#setTextAppearanceInfo(TextAppearanceInfo)
@@ -46,12 +46,12 @@ import java.util.Objects;
*/
public final class TextAppearanceInfo implements Parcelable {
/**
- * The text size (in pixels) for current {@link TextView}.
+ * The text size (in pixels) for current editor.
*/
private final @Px float mTextSize;
/**
- * The LocaleList of the text.
+ * The {@link LocaleList} of the text.
*/
@NonNull private final LocaleList mTextLocales;
@@ -64,7 +64,8 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* The weight of the text.
*/
- private final @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int mTextFontWeight;
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ private final int mTextFontWeight;
/**
* The style (normal, bold, italic, bold|italic) of the text, see {@link Typeface}.
@@ -72,8 +73,7 @@ public final class TextAppearanceInfo implements Parcelable {
private final @Typeface.Style int mTextStyle;
/**
- * Whether the transformation method applied to the current {@link TextView} is set to
- * ALL CAPS.
+ * Whether the transformation method applied to the current editor is set to all caps.
*/
private final boolean mAllCaps;
@@ -93,6 +93,11 @@ public final class TextAppearanceInfo implements Parcelable {
private final @Px float mShadowRadius;
/**
+ * The shadow color of the text shadow.
+ */
+ private final @ColorInt int mShadowColor;
+
+ /**
* The elegant text height, especially for less compacted complex script text.
*/
private final boolean mElegantTextHeight;
@@ -135,67 +140,46 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* The color of the text selection highlight.
*/
- private final @ColorInt int mTextColorHighlight;
+ private final @ColorInt int mHighlightTextColor;
/**
- * The current text color.
+ * The current text color of the editor.
*/
private final @ColorInt int mTextColor;
/**
- * The current color of the hint text.
- */
- private final @ColorInt int mTextColorHint;
-
- /**
- * The text color for links.
- */
- @Nullable private final ColorStateList mTextColorLink;
-
- /**
- * The max length of text.
- */
- private final int mMaxLength;
-
-
- public TextAppearanceInfo(@NonNull TextView textView) {
- mTextSize = textView.getTextSize();
- mTextLocales = textView.getTextLocales();
- Typeface typeface = textView.getPaint().getTypeface();
- String systemFontFamilyName = null;
- int textFontWeight = -1;
- if (typeface != null) {
- systemFontFamilyName = typeface.getSystemFontFamilyName();
- textFontWeight = typeface.getWeight();
- }
- mSystemFontFamilyName = systemFontFamilyName;
- mTextFontWeight = textFontWeight;
- mTextStyle = textView.getTypefaceStyle();
- mAllCaps = textView.isAllCaps();
- mShadowRadius = textView.getShadowRadius();
- mShadowDx = textView.getShadowDx();
- mShadowDy = textView.getShadowDy();
- mElegantTextHeight = textView.isElegantTextHeight();
- mFallbackLineSpacing = textView.isFallbackLineSpacing();
- mLetterSpacing = textView.getLetterSpacing();
- mFontFeatureSettings = textView.getFontFeatureSettings();
- mFontVariationSettings = textView.getFontVariationSettings();
- mLineBreakStyle = textView.getLineBreakStyle();
- mLineBreakWordStyle = textView.getLineBreakWordStyle();
- mTextScaleX = textView.getTextScaleX();
- mTextColorHighlight = textView.getHighlightColor();
- mTextColor = textView.getCurrentTextColor();
- mTextColorHint = textView.getCurrentHintTextColor();
- mTextColorLink = textView.getLinkTextColors();
- int maxLength = -1;
- for (InputFilter filter: textView.getFilters()) {
- if (filter instanceof InputFilter.LengthFilter) {
- maxLength = ((InputFilter.LengthFilter) filter).getMax();
- // There is at most one LengthFilter.
- break;
- }
- }
- mMaxLength = maxLength;
+ * The current color of the hint text.
+ */
+ private final @ColorInt int mHintTextColor;
+
+ /**
+ * The text color used to paint the links in the editor.
+ */
+ private final @ColorInt int mLinkTextColor;
+
+ private TextAppearanceInfo(@NonNull final TextAppearanceInfo.Builder builder) {
+ mTextSize = builder.mTextSize;
+ mTextLocales = builder.mTextLocales;
+ mSystemFontFamilyName = builder.mSystemFontFamilyName;
+ mTextFontWeight = builder.mTextFontWeight;
+ mTextStyle = builder.mTextStyle;
+ mAllCaps = builder.mAllCaps;
+ mShadowDx = builder.mShadowDx;
+ mShadowDy = builder.mShadowDy;
+ mShadowRadius = builder.mShadowRadius;
+ mShadowColor = builder.mShadowColor;
+ mElegantTextHeight = builder.mElegantTextHeight;
+ mFallbackLineSpacing = builder.mFallbackLineSpacing;
+ mLetterSpacing = builder.mLetterSpacing;
+ mFontFeatureSettings = builder.mFontFeatureSettings;
+ mFontVariationSettings = builder.mFontVariationSettings;
+ mLineBreakStyle = builder.mLineBreakStyle;
+ mLineBreakWordStyle = builder.mLineBreakWordStyle;
+ mTextScaleX = builder.mTextScaleX;
+ mHighlightTextColor = builder.mHighlightTextColor;
+ mTextColor = builder.mTextColor;
+ mHintTextColor = builder.mHintTextColor;
+ mLinkTextColor = builder.mLinkTextColor;
}
@Override
@@ -214,6 +198,7 @@ public final class TextAppearanceInfo implements Parcelable {
dest.writeFloat(mShadowDx);
dest.writeFloat(mShadowDy);
dest.writeFloat(mShadowRadius);
+ dest.writeInt(mShadowColor);
dest.writeBoolean(mElegantTextHeight);
dest.writeBoolean(mFallbackLineSpacing);
dest.writeFloat(mLetterSpacing);
@@ -222,14 +207,13 @@ public final class TextAppearanceInfo implements Parcelable {
dest.writeInt(mLineBreakStyle);
dest.writeInt(mLineBreakWordStyle);
dest.writeFloat(mTextScaleX);
- dest.writeInt(mTextColorHighlight);
+ dest.writeInt(mHighlightTextColor);
dest.writeInt(mTextColor);
- dest.writeInt(mTextColorHint);
- dest.writeTypedObject(mTextColorLink, flags);
- dest.writeInt(mMaxLength);
+ dest.writeInt(mHintTextColor);
+ dest.writeInt(mLinkTextColor);
}
- private TextAppearanceInfo(@NonNull Parcel in) {
+ TextAppearanceInfo(@NonNull Parcel in) {
mTextSize = in.readFloat();
mTextLocales = LocaleList.CREATOR.createFromParcel(in);
mAllCaps = in.readBoolean();
@@ -239,6 +223,7 @@ public final class TextAppearanceInfo implements Parcelable {
mShadowDx = in.readFloat();
mShadowDy = in.readFloat();
mShadowRadius = in.readFloat();
+ mShadowColor = in.readInt();
mElegantTextHeight = in.readBoolean();
mFallbackLineSpacing = in.readBoolean();
mLetterSpacing = in.readFloat();
@@ -247,11 +232,10 @@ public final class TextAppearanceInfo implements Parcelable {
mLineBreakStyle = in.readInt();
mLineBreakWordStyle = in.readInt();
mTextScaleX = in.readFloat();
- mTextColorHighlight = in.readInt();
+ mHighlightTextColor = in.readInt();
mTextColor = in.readInt();
- mTextColorHint = in.readInt();
- mTextColorLink = in.readTypedObject(ColorStateList.CREATOR);
- mMaxLength = in.readInt();
+ mHintTextColor = in.readInt();
+ mLinkTextColor = in.readInt();
}
@NonNull
@@ -268,14 +252,14 @@ public final class TextAppearanceInfo implements Parcelable {
};
/**
- * Returns the text size (in pixels) for current {@link TextView}.
+ * Returns the text size (in pixels) for current editor.
*/
public @Px float getTextSize() {
return mTextSize;
}
/**
- * Returns the LocaleList of the text.
+ * Returns the {@link LocaleList} of the text.
*/
@NonNull
public LocaleList getTextLocales() {
@@ -286,31 +270,38 @@ public final class TextAppearanceInfo implements Parcelable {
* Returns the font family name if the {@link Typeface} of the text is created from a
* system font family. Returns null if no {@link Typeface} is specified, or it is not created
* from a system font family.
+ *
+ * @see Typeface#getSystemFontFamilyName()
*/
@Nullable
- public String getFontFamilyName() {
+ public String getSystemFontFamilyName() {
return mSystemFontFamilyName;
}
/**
- * Returns the weight of the text. Returns -1 when no {@link Typeface} is specified.
+ * Returns the weight of the text, or {@code FontStyle#FONT_WEIGHT_UNSPECIFIED}
+ * when no {@link Typeface} is specified.
*/
- public @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int getTextFontWeight() {
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ public int getTextFontWeight() {
return mTextFontWeight;
}
/**
* Returns the style (normal, bold, italic, bold|italic) of the text. Returns
- * {@link Typeface#NORMAL} when no {@link Typeface} is specified. See {@link Typeface} for
- * more information.
+ * {@link Typeface#NORMAL} when no {@link Typeface} is specified.
+ *
+ * @see Typeface
*/
public @Typeface.Style int getTextStyle() {
return mTextStyle;
}
/**
- * Returns whether the transformation method applied to the current {@link TextView} is set to
- * ALL CAPS.
+ * Returns whether the transformation method applied to the current editor is set to all caps.
+ *
+ * @see TextView#setAllCaps(boolean)
+ * @see TextView#setTransformationMethod(TransformationMethod)
*/
public boolean isAllCaps() {
return mAllCaps;
@@ -318,6 +309,8 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* Returns the horizontal offset (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
*/
public @Px float getShadowDx() {
return mShadowDx;
@@ -325,6 +318,8 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* Returns the vertical offset (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
*/
public @Px float getShadowDy() {
return mShadowDy;
@@ -332,15 +327,28 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* Returns the blur radius (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
*/
public @Px float getShadowRadius() {
return mShadowRadius;
}
/**
+ * Returns the color of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ public @ColorInt int getShadowColor() {
+ return mShadowColor;
+ }
+
+ /**
* Returns {@code true} if the elegant height metrics flag is set. This setting selects font
* variants that have not been compacted to fit Latin-based vertical metrics, and also increases
* top and bottom bounds to provide more space.
+ *
+ * @see Paint#isElegantTextHeight()
*/
public boolean isElegantTextHeight() {
return mElegantTextHeight;
@@ -411,13 +419,17 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* Returns the color of the text selection highlight.
+ *
+ * @see TextView#getHighlightColor()
*/
- public @ColorInt int getTextColorHighlight() {
- return mTextColorHighlight;
+ public @ColorInt int getHighlightTextColor() {
+ return mHighlightTextColor;
}
/**
- * Returns the current text color.
+ * Returns the current text color of the editor.
+ *
+ * @see TextView#getCurrentTextColor()
*/
public @ColorInt int getTextColor() {
return mTextColor;
@@ -425,27 +437,22 @@ public final class TextAppearanceInfo implements Parcelable {
/**
* Returns the current color of the hint text.
+ *
+ * @see TextView#getCurrentHintTextColor()
*/
- public @ColorInt int getTextColorHint() {
- return mTextColorHint;
+ public @ColorInt int getHintTextColor() {
+ return mHintTextColor;
}
/**
- * Returns the text color for links.
+ * Returns the text color used to paint the links in the editor.
+ *
+ * @see TextView#getLinkTextColors()
*/
- @Nullable
- public ColorStateList getTextColorLink() {
- return mTextColorLink;
+ public @ColorInt int getLinkTextColor() {
+ return mLinkTextColor;
}
- /**
- * Returns the max length of text, which is used to set an input filter to constrain the text
- * length to the specified number. Returns -1 when there is no {@link InputFilter.LengthFilter}
- * in the Editor.
- */
- public int getMaxLength() {
- return mMaxLength;
- }
@Override
public boolean equals(Object o) {
@@ -456,27 +463,29 @@ public final class TextAppearanceInfo implements Parcelable {
&& mTextFontWeight == that.mTextFontWeight && mTextStyle == that.mTextStyle
&& mAllCaps == that.mAllCaps && Float.compare(that.mShadowDx, mShadowDx) == 0
&& Float.compare(that.mShadowDy, mShadowDy) == 0 && Float.compare(
- that.mShadowRadius, mShadowRadius) == 0 && mMaxLength == that.mMaxLength
+ that.mShadowRadius, mShadowRadius) == 0 && that.mShadowColor == mShadowColor
&& mElegantTextHeight == that.mElegantTextHeight
&& mFallbackLineSpacing == that.mFallbackLineSpacing && Float.compare(
that.mLetterSpacing, mLetterSpacing) == 0 && mLineBreakStyle == that.mLineBreakStyle
&& mLineBreakWordStyle == that.mLineBreakWordStyle
- && mTextColorHighlight == that.mTextColorHighlight && mTextColor == that.mTextColor
- && mTextColorLink.getDefaultColor() == that.mTextColorLink.getDefaultColor()
- && mTextColorHint == that.mTextColorHint && Objects.equals(
- mTextLocales, that.mTextLocales) && Objects.equals(mSystemFontFamilyName,
- that.mSystemFontFamilyName) && Objects.equals(mFontFeatureSettings,
- that.mFontFeatureSettings) && Objects.equals(mFontVariationSettings,
- that.mFontVariationSettings) && Float.compare(that.mTextScaleX, mTextScaleX) == 0;
+ && mHighlightTextColor == that.mHighlightTextColor
+ && mTextColor == that.mTextColor
+ && mLinkTextColor == that.mLinkTextColor
+ && mHintTextColor == that.mHintTextColor
+ && Objects.equals(mTextLocales, that.mTextLocales)
+ && Objects.equals(mSystemFontFamilyName, that.mSystemFontFamilyName)
+ && Objects.equals(mFontFeatureSettings, that.mFontFeatureSettings)
+ && Objects.equals(mFontVariationSettings, that.mFontVariationSettings)
+ && Float.compare(that.mTextScaleX, mTextScaleX) == 0;
}
@Override
public int hashCode() {
return Objects.hash(mTextSize, mTextLocales, mSystemFontFamilyName, mTextFontWeight,
- mTextStyle, mAllCaps, mShadowDx, mShadowDy, mShadowRadius, mElegantTextHeight,
- mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings, mFontVariationSettings,
- mLineBreakStyle, mLineBreakWordStyle, mTextScaleX, mTextColorHighlight, mTextColor,
- mTextColorHint, mTextColorLink, mMaxLength);
+ mTextStyle, mAllCaps, mShadowDx, mShadowDy, mShadowRadius, mShadowColor,
+ mElegantTextHeight, mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings,
+ mFontVariationSettings, mLineBreakStyle, mLineBreakWordStyle, mTextScaleX,
+ mHighlightTextColor, mTextColor, mHintTextColor, mLinkTextColor);
}
@Override
@@ -491,6 +500,7 @@ public final class TextAppearanceInfo implements Parcelable {
+ ", mShadowDx=" + mShadowDx
+ ", mShadowDy=" + mShadowDy
+ ", mShadowRadius=" + mShadowRadius
+ + ", mShadowColor=" + mShadowColor
+ ", mElegantTextHeight=" + mElegantTextHeight
+ ", mFallbackLineSpacing=" + mFallbackLineSpacing
+ ", mLetterSpacing=" + mLetterSpacing
@@ -499,11 +509,290 @@ public final class TextAppearanceInfo implements Parcelable {
+ ", mLineBreakStyle=" + mLineBreakStyle
+ ", mLineBreakWordStyle=" + mLineBreakWordStyle
+ ", mTextScaleX=" + mTextScaleX
- + ", mTextColorHighlight=" + mTextColorHighlight
+ + ", mHighlightTextColor=" + mHighlightTextColor
+ ", mTextColor=" + mTextColor
- + ", mTextColorHint=" + mTextColorHint
- + ", mTextColorLink=" + mTextColorLink
- + ", mMaxLength=" + mMaxLength
+ + ", mHintTextColor=" + mHintTextColor
+ + ", mLinkTextColor=" + mLinkTextColor
+ '}';
}
+
+ /**
+ * Builder for {@link TextAppearanceInfo}.
+ */
+ public static final class Builder {
+ private @Px float mTextSize = -1;
+ private @NonNull LocaleList mTextLocales = LocaleList.getAdjustedDefault();
+ @Nullable private String mSystemFontFamilyName = null;
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED, to = FontStyle.FONT_WEIGHT_MAX)
+ private int mTextFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
+ private @Typeface.Style int mTextStyle = NORMAL;
+ private boolean mAllCaps = false;
+ private @Px float mShadowDx = 0;
+ private @Px float mShadowDy = 0;
+ private @Px float mShadowRadius = 0;
+ private @ColorInt int mShadowColor = 0;
+ private boolean mElegantTextHeight = false;
+ private boolean mFallbackLineSpacing = false;
+ private float mLetterSpacing = 0;
+ @Nullable private String mFontFeatureSettings = null;
+ @Nullable private String mFontVariationSettings = null;
+ @LineBreakConfig.LineBreakStyle
+ private int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE;
+ @LineBreakConfig.LineBreakWordStyle
+ private int mLineBreakWordStyle = LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+ private float mTextScaleX = 1;
+ private @ColorInt int mHighlightTextColor = 0;
+ private @ColorInt int mTextColor = 0;
+ private @ColorInt int mHintTextColor = 0;
+ private @ColorInt int mLinkTextColor = 0;
+
+ /**
+ * Set the text size (in pixels) obtained from the current editor.
+ */
+ @NonNull
+ public Builder setTextSize(@Px float textSize) {
+ mTextSize = textSize;
+ return this;
+ }
+
+ /**
+ * Set the {@link LocaleList} of the text.
+ */
+ @NonNull
+ public Builder setTextLocales(@NonNull LocaleList textLocales) {
+ mTextLocales = textLocales;
+ return this;
+ }
+
+ /**
+ * Set the system font family name if the {@link Typeface} of the text is created from a
+ * system font family.
+ *
+ * @see Typeface#getSystemFontFamilyName()
+ */
+ @NonNull
+ public Builder setSystemFontFamilyName(@Nullable String systemFontFamilyName) {
+ mSystemFontFamilyName = systemFontFamilyName;
+ return this;
+ }
+
+ /**
+ * Set the weight of the text.
+ */
+ @NonNull
+ public Builder setTextFontWeight(
+ @IntRange(from = FontStyle.FONT_WEIGHT_UNSPECIFIED,
+ to = FontStyle.FONT_WEIGHT_MAX) int textFontWeight) {
+ mTextFontWeight = textFontWeight;
+ return this;
+ }
+
+ /**
+ * Set the style (normal, bold, italic, bold|italic) of the text.
+ *
+ * @see Typeface
+ */
+ @NonNull
+ public Builder setTextStyle(@Typeface.Style int textStyle) {
+ mTextStyle = textStyle;
+ return this;
+ }
+
+ /**
+ * Set whether the transformation method applied to the current editor is set to all caps.
+ *
+ * @see TextView#setAllCaps(boolean)
+ * @see TextView#setTransformationMethod(TransformationMethod)
+ */
+ @NonNull
+ public Builder setAllCaps(boolean allCaps) {
+ mAllCaps = allCaps;
+ return this;
+ }
+
+ /**
+ * Set the horizontal offset (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ @NonNull
+ public Builder setShadowDx(@Px float shadowDx) {
+ mShadowDx = shadowDx;
+ return this;
+ }
+
+ /**
+ * Set the vertical offset (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ @NonNull
+ public Builder setShadowDy(@Px float shadowDy) {
+ mShadowDy = shadowDy;
+ return this;
+ }
+
+ /**
+ * Set the blur radius (in pixels) of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ @NonNull
+ public Builder setShadowRadius(@Px float shadowRadius) {
+ mShadowRadius = shadowRadius;
+ return this;
+ }
+
+ /**
+ * Set the color of the text shadow.
+ *
+ * @see Paint#setShadowLayer(float, float, float, int)
+ */
+ @NonNull
+ public Builder setShadowColor(@ColorInt int shadowColor) {
+ mShadowColor = shadowColor;
+ return this;
+ }
+
+ /**
+ * Set the elegant height metrics flag. This setting selects font variants that
+ * have not been compacted to fit Latin-based vertical metrics, and also increases
+ * top and bottom bounds to provide more space.
+ *
+ * @see Paint#isElegantTextHeight()
+ */
+ @NonNull
+ public Builder setElegantTextHeight(boolean elegantTextHeight) {
+ mElegantTextHeight = elegantTextHeight;
+ return this;
+ }
+
+ /**
+ * Set whether to expand linespacing based on fallback fonts.
+ *
+ * @see TextView#setFallbackLineSpacing(boolean)
+ */
+ @NonNull
+ public Builder setFallbackLineSpacing(boolean fallbackLineSpacing) {
+ mFallbackLineSpacing = fallbackLineSpacing;
+ return this;
+ }
+
+ /**
+ * Set the text letter-spacing, which determines the spacing between characters.
+ * The value is in 'EM' units. Normally, this value is 0.0.
+ */
+ @NonNull
+ public Builder setLetterSpacing(float letterSpacing) {
+ mLetterSpacing = letterSpacing;
+ return this;
+ }
+
+ /**
+ * Set the font feature settings.
+ *
+ * @see Paint#getFontFeatureSettings()
+ */
+ @NonNull
+ public Builder setFontFeatureSettings(@Nullable String fontFeatureSettings) {
+ mFontFeatureSettings = fontFeatureSettings;
+ return this;
+ }
+
+ /**
+ * Set the font variation settings. Returns null if no variation is specified.
+ *
+ * @see Paint#getFontVariationSettings()
+ */
+ @NonNull
+ public Builder setFontVariationSettings(@Nullable String fontVariationSettings) {
+ mFontVariationSettings = fontVariationSettings;
+ return this;
+ }
+
+ /**
+ * Set the line-break strategies for text wrapping.
+ *
+ * @see TextView#setLineBreakStyle(int)
+ */
+ @NonNull
+ public Builder setLineBreakStyle(@LineBreakConfig.LineBreakStyle int lineBreakStyle) {
+ mLineBreakStyle = lineBreakStyle;
+ return this;
+ }
+
+ /**
+ * Set the line-break word strategies for text wrapping.
+ *
+ * @see TextView#setLineBreakWordStyle(int)
+ */
+ @NonNull
+ public Builder setLineBreakWordStyle(
+ @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
+ mLineBreakWordStyle = lineBreakWordStyle;
+ return this;
+ }
+
+ /**
+ * Set the extent by which text should be stretched horizontally.
+ */
+ @NonNull
+ public Builder setTextScaleX(float textScaleX) {
+ mTextScaleX = textScaleX;
+ return this;
+ }
+
+ /**
+ * Set the color of the text selection highlight.
+ *
+ * @see TextView#getHighlightColor()
+ */
+ @NonNull
+ public Builder setHighlightTextColor(@ColorInt int highlightTextColor) {
+ mHighlightTextColor = highlightTextColor;
+ return this;
+ }
+
+ /**
+ * Set the current text color of the editor.
+ *
+ * @see TextView#getCurrentTextColor()
+ */
+ @NonNull
+ public Builder setTextColor(@ColorInt int textColor) {
+ mTextColor = textColor;
+ return this;
+ }
+
+ /**
+ * Set the current color of the hint text.
+ *
+ * @see TextView#getCurrentHintTextColor()
+ */
+ @NonNull
+ public Builder setHintTextColor(@ColorInt int hintTextColor) {
+ mHintTextColor = hintTextColor;
+ return this;
+ }
+
+ /**
+ * Set the text color used to paint the links in the editor.
+ *
+ * @see TextView#getLinkTextColors()
+ */
+ @NonNull
+ public Builder setLinkTextColor(@ColorInt int linkTextColor) {
+ mLinkTextColor = linkTextColor;
+ return this;
+ }
+
+ /**
+ * Returns {@link TextAppearanceInfo} using parameters in this
+ * {@link TextAppearanceInfo.Builder}.
+ */
+ @NonNull
+ public TextAppearanceInfo build() {
+ return new TextAppearanceInfo(this);
+ }
+ }
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 56524a2c01ef..5740f86b3486 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -21,6 +21,7 @@ import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID;
import android.R;
import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -37,6 +38,7 @@ import android.content.UndoOperation;
import android.content.UndoOwner;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -49,8 +51,10 @@ import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.RenderNode;
+import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.fonts.FontStyle;
import android.os.Build;
import android.os.Bundle;
import android.os.LocaleList;
@@ -4762,8 +4766,41 @@ public class Editor {
}
if (includeTextAppearance) {
- TextAppearanceInfo textAppearanceInfo = new TextAppearanceInfo(mTextView);
- builder.setTextAppearanceInfo(textAppearanceInfo);
+ Typeface typeface = mTextView.getPaint().getTypeface();
+ String systemFontFamilyName = null;
+ int textFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
+ if (typeface != null) {
+ systemFontFamilyName = typeface.getSystemFontFamilyName();
+ textFontWeight = typeface.getWeight();
+ }
+ ColorStateList linkTextColors = mTextView.getLinkTextColors();
+ @ColorInt int linkTextColor = linkTextColors != null
+ ? linkTextColors.getDefaultColor() : 0;
+
+ TextAppearanceInfo.Builder appearanceBuilder = new TextAppearanceInfo.Builder();
+ appearanceBuilder.setTextSize(mTextView.getTextSize())
+ .setTextLocales(mTextView.getTextLocales())
+ .setSystemFontFamilyName(systemFontFamilyName)
+ .setTextFontWeight(textFontWeight)
+ .setTextStyle(mTextView.getTypefaceStyle())
+ .setAllCaps(mTextView.isAllCaps())
+ .setShadowDx(mTextView.getShadowDx())
+ .setShadowDy(mTextView.getShadowDy())
+ .setShadowRadius(mTextView.getShadowRadius())
+ .setShadowColor(mTextView.getShadowColor())
+ .setElegantTextHeight(mTextView.isElegantTextHeight())
+ .setFallbackLineSpacing(mTextView.isFallbackLineSpacing())
+ .setLetterSpacing(mTextView.getLetterSpacing())
+ .setFontFeatureSettings(mTextView.getFontFeatureSettings())
+ .setFontVariationSettings(mTextView.getFontVariationSettings())
+ .setLineBreakStyle(mTextView.getLineBreakStyle())
+ .setLineBreakWordStyle(mTextView.getLineBreakWordStyle())
+ .setTextScaleX(mTextView.getTextScaleX())
+ .setHighlightTextColor(mTextView.getHighlightColor())
+ .setTextColor(mTextView.getCurrentTextColor())
+ .setHintTextColor(mTextView.getCurrentHintTextColor())
+ .setLinkTextColor(linkTextColor);
+ builder.setTextAppearanceInfo(appearanceBuilder.build());
}
imm.updateCursorAnchorInfo(mTextView, builder.build());
diff --git a/core/java/android/window/IBackAnimationFinishedCallback.aidl b/core/java/android/window/IBackAnimationFinishedCallback.aidl
index 8afc003256ed..f034339add6e 100644
--- a/core/java/android/window/IBackAnimationFinishedCallback.aidl
+++ b/core/java/android/window/IBackAnimationFinishedCallback.aidl
@@ -22,6 +22,6 @@ package android.window;
* @param trigger Whether the back gesture has passed the triggering threshold.
* {@hide}
*/
-oneway interface IBackAnimationFinishedCallback {
+interface IBackAnimationFinishedCallback {
void onAnimationFinished(in boolean triggerBack);
} \ No newline at end of file
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 4248096f307d..395073941930 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -57,12 +57,13 @@ import java.util.function.Supplier;
* option is provided.
*
* The following is what happens within the {@link SurfaceSyncGroup}
- * 1. Each SyncTarget will get a {@link SyncTarget#onReadyToSync} callback that contains a
- * {@link SyncBufferCallback}.
- * 2. Each {@link SyncTarget} needs to invoke {@link SyncBufferCallback#onBufferReady(Transaction)}.
- * This makes sure the SurfaceSyncGroup knows when the SyncTarget is complete, allowing the
- * SurfaceSyncGroup to get the Transaction that contains the buffer.
- * 3. When the final SyncBufferCallback finishes for the SurfaceSyncGroup, in most cases the
+ * 1. Each SyncTarget will get a {@link SyncTarget#onAddedToSyncGroup} callback that contains a
+ * {@link TransactionReadyCallback}.
+ * 2. Each {@link SyncTarget} needs to invoke
+ * {@link TransactionReadyCallback#onTransactionReady(Transaction)}. This makes sure the
+ * SurfaceSyncGroup knows when the SyncTarget is complete, allowing the SurfaceSyncGroup to get the
+ * Transaction that contains the buffer.
+ * 3. When the final TransactionReadyCallback finishes for the SurfaceSyncGroup, in most cases the
* transaction is applied and then the sync complete callbacks are invoked, letting the callers know
* the sync is now complete.
*
@@ -86,8 +87,6 @@ public final class SurfaceSyncGroup {
private final Transaction mTransaction = sTransactionFactory.get();
@GuardedBy("mLock")
private boolean mSyncReady;
- @GuardedBy("mLock")
- private final Set<SyncTarget> mSyncTargets = new ArraySet<>();
@GuardedBy("mLock")
private Consumer<Transaction> mSyncRequestCompleteCallback;
@@ -197,14 +196,13 @@ public final class SurfaceSyncGroup {
* Add a {@link SyncTarget} to a sync set. The sync set will wait for all
* SyncableSurfaces to complete before notifying.
*
- * @param syncTarget A SyncableSurface that implements how to handle syncing
- * buffers.
+ * @param syncTarget A SyncTarget that implements how to handle syncing transactions.
* @return true if the SyncTarget was successfully added to the SyncGroup, false otherwise.
*/
public boolean addToSync(SyncTarget syncTarget) {
- SyncBufferCallback syncBufferCallback = new SyncBufferCallback() {
+ TransactionReadyCallback transactionReadyCallback = new TransactionReadyCallback() {
@Override
- public void onBufferReady(Transaction t) {
+ public void onTransactionReady(Transaction t) {
synchronized (mLock) {
if (t != null) {
mTransaction.merge(t);
@@ -221,10 +219,9 @@ public final class SurfaceSyncGroup {
+ "SyncTargets can be added.");
return false;
}
- mPendingSyncs.add(syncBufferCallback.hashCode());
- mSyncTargets.add(syncTarget);
+ mPendingSyncs.add(transactionReadyCallback.hashCode());
}
- syncTarget.onReadyToSync(syncBufferCallback);
+ syncTarget.onAddedToSyncGroup(this, transactionReadyCallback);
return true;
}
@@ -256,17 +253,13 @@ public final class SurfaceSyncGroup {
Log.d(TAG, "Successfully finished sync id=" + this);
}
- for (SyncTarget syncTarget : mSyncTargets) {
- syncTarget.onSyncComplete();
- }
- mSyncTargets.clear();
mSyncRequestCompleteCallback.accept(mTransaction);
mFinished = true;
}
/**
* Add a Transaction to this sync set. This allows the caller to provide other info that
- * should be synced with the buffers.
+ * should be synced with the transactions.
*/
public void addTransactionToSync(Transaction t) {
synchronized (mLock) {
@@ -334,9 +327,10 @@ public final class SurfaceSyncGroup {
}
@Override
- public void onReadyToSync(SyncBufferCallback syncBufferCallback) {
+ public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+ TransactionReadyCallback transactionReadyCallback) {
mFrameCallbackConsumer.accept(
- () -> mSurfaceView.syncNextFrame(syncBufferCallback::onBufferReady));
+ () -> mSurfaceView.syncNextFrame(transactionReadyCallback::onTransactionReady));
}
}
@@ -345,22 +339,19 @@ public final class SurfaceSyncGroup {
*/
public interface SyncTarget {
/**
- * Called when the Syncable is ready to begin handing a sync request. When invoked, the
- * implementor is required to call {@link SyncBufferCallback#onBufferReady(Transaction)}
- * and {@link SyncBufferCallback#onBufferReady(Transaction)} in order for this Syncable
- * to be marked as complete.
+ * Called when the SyncTarget has been added to a SyncGroup as is ready to begin handing a
+ * sync request. When invoked, the implementor is required to call
+ * {@link TransactionReadyCallback#onTransactionReady(Transaction)} in order for this
+ * SurfaceSyncGroup to fully complete.
*
* Always invoked on the thread that initiated the call to {@link #addToSync(SyncTarget)}
*
- * @param syncBufferCallback A SyncBufferCallback that the caller must invoke onBufferReady
- */
- void onReadyToSync(SyncBufferCallback syncBufferCallback);
-
- /**
- * There's no guarantee about the thread this callback is invoked on.
+ * @param parentSyncGroup The sync group this target has been added to.
+ * @param transactionReadyCallback A TransactionReadyCallback that the caller must invoke
+ * onTransactionReady
*/
- default void onSyncComplete() {
- }
+ void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+ TransactionReadyCallback transactionReadyCallback);
}
/**
@@ -368,14 +359,14 @@ public final class SurfaceSyncGroup {
* completed. The caller should invoke the calls when the rendering has started and finished a
* frame.
*/
- public interface SyncBufferCallback {
+ public interface TransactionReadyCallback {
/**
- * Invoked when the transaction contains the buffer and is ready to sync.
+ * Invoked when the transaction is ready to sync.
*
- * @param t The transaction that contains the buffer to be synced. This can be null if
- * there's nothing to sync
+ * @param t The transaction that contains the anything to be included in the synced. This
+ * can be null if there's nothing to sync
*/
- void onBufferReady(@Nullable Transaction t);
+ void onTransactionReady(@Nullable Transaction t);
}
/**
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index e2c8a31cc987..dc60eddaf7db 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -83,6 +83,12 @@ public final class TaskFragmentInfo implements Parcelable {
private final boolean mIsTaskFragmentClearedForPip;
/**
+ * Whether the last running activity of the TaskFragment was removed because it was reordered to
+ * front of the Task.
+ */
+ private final boolean mIsClearedForReorderActivityToFront;
+
+ /**
* The maximum {@link ActivityInfo.WindowLayout#minWidth} and
* {@link ActivityInfo.WindowLayout#minHeight} aggregated from the TaskFragment's child
* activities.
@@ -96,7 +102,7 @@ public final class TaskFragmentInfo implements Parcelable {
@NonNull Configuration configuration, int runningActivityCount,
boolean isVisible, @NonNull List<IBinder> activities, @NonNull Point positionInParent,
boolean isTaskClearedForReuse, boolean isTaskFragmentClearedForPip,
- @NonNull Point minimumDimensions) {
+ boolean isClearedForReorderActivityToFront, @NonNull Point minimumDimensions) {
mFragmentToken = requireNonNull(fragmentToken);
mToken = requireNonNull(token);
mConfiguration.setTo(configuration);
@@ -106,6 +112,7 @@ public final class TaskFragmentInfo implements Parcelable {
mPositionInParent.set(positionInParent);
mIsTaskClearedForReuse = isTaskClearedForReuse;
mIsTaskFragmentClearedForPip = isTaskFragmentClearedForPip;
+ mIsClearedForReorderActivityToFront = isClearedForReorderActivityToFront;
mMinimumDimensions.set(minimumDimensions);
}
@@ -160,6 +167,11 @@ public final class TaskFragmentInfo implements Parcelable {
return mIsTaskFragmentClearedForPip;
}
+ /** @hide */
+ public boolean isClearedForReorderActivityToFront() {
+ return mIsClearedForReorderActivityToFront;
+ }
+
@WindowingMode
public int getWindowingMode() {
return mConfiguration.windowConfiguration.getWindowingMode();
@@ -207,6 +219,7 @@ public final class TaskFragmentInfo implements Parcelable {
&& mPositionInParent.equals(that.mPositionInParent)
&& mIsTaskClearedForReuse == that.mIsTaskClearedForReuse
&& mIsTaskFragmentClearedForPip == that.mIsTaskFragmentClearedForPip
+ && mIsClearedForReorderActivityToFront == that.mIsClearedForReorderActivityToFront
&& mMinimumDimensions.equals(that.mMinimumDimensions);
}
@@ -220,6 +233,7 @@ public final class TaskFragmentInfo implements Parcelable {
mPositionInParent.readFromParcel(in);
mIsTaskClearedForReuse = in.readBoolean();
mIsTaskFragmentClearedForPip = in.readBoolean();
+ mIsClearedForReorderActivityToFront = in.readBoolean();
mMinimumDimensions.readFromParcel(in);
}
@@ -235,6 +249,7 @@ public final class TaskFragmentInfo implements Parcelable {
mPositionInParent.writeToParcel(dest, flags);
dest.writeBoolean(mIsTaskClearedForReuse);
dest.writeBoolean(mIsTaskFragmentClearedForPip);
+ dest.writeBoolean(mIsClearedForReorderActivityToFront);
mMinimumDimensions.writeToParcel(dest, flags);
}
@@ -262,8 +277,9 @@ public final class TaskFragmentInfo implements Parcelable {
+ " activities=" + mActivities
+ " positionInParent=" + mPositionInParent
+ " isTaskClearedForReuse=" + mIsTaskClearedForReuse
- + " isTaskFragmentClearedForPip" + mIsTaskFragmentClearedForPip
- + " minimumDimensions" + mMinimumDimensions
+ + " isTaskFragmentClearedForPip=" + mIsTaskFragmentClearedForPip
+ + " mIsClearedForReorderActivityToFront=" + mIsClearedForReorderActivityToFront
+ + " minimumDimensions=" + mMinimumDimensions
+ "}";
}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index c7a2d24a8c94..fc64eb9c35c5 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -728,6 +728,29 @@ public final class WindowContainerTransaction implements Parcelable {
}
/**
+ * Sets the TaskFragment {@code container} to have a companion TaskFragment {@code companion}.
+ * This indicates that the organizer will remove the TaskFragment when the companion
+ * TaskFragment is removed.
+ *
+ * @param container the TaskFragment container
+ * @param companion the companion TaskFragment. If it is {@code null}, the transaction will
+ * reset the companion TaskFragment.
+ * @hide
+ */
+ @NonNull
+ public WindowContainerTransaction setCompanionTaskFragment(@NonNull IBinder container,
+ @Nullable IBinder companion) {
+ final HierarchyOp hierarchyOp =
+ new HierarchyOp.Builder(
+ HierarchyOp.HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT)
+ .setContainer(container)
+ .setReparentContainer(companion)
+ .build();
+ mHierarchyOps.add(hierarchyOp);
+ return this;
+ }
+
+ /**
* Sets/removes the always on top flag for this {@code windowContainer}. See
* {@link com.android.server.wm.ConfigurationContainer#setAlwaysOnTop(boolean)}.
* Please note that this method is only intended to be used for a
@@ -1205,6 +1228,7 @@ public final class WindowContainerTransaction implements Parcelable {
public static final int HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP = 19;
public static final int HIERARCHY_OP_TYPE_REMOVE_TASK = 20;
public static final int HIERARCHY_OP_TYPE_FINISH_ACTIVITY = 21;
+ public static final int HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT = 22;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -1419,6 +1443,11 @@ public final class WindowContainerTransaction implements Parcelable {
}
@NonNull
+ public IBinder getCompanionContainer() {
+ return mReparent;
+ }
+
+ @NonNull
public IBinder getCallingActivity() {
return mReparent;
}
@@ -1528,6 +1557,9 @@ public final class WindowContainerTransaction implements Parcelable {
return "{RemoveTask: task=" + mContainer + "}";
case HIERARCHY_OP_TYPE_FINISH_ACTIVITY:
return "{finishActivity: activity=" + mContainer + "}";
+ case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT:
+ return "{setCompanionTaskFragment: container = " + mContainer + " companion = "
+ + mReparent + "}";
default:
return "{mType=" + mType + " container=" + mContainer + " reparent=" + mReparent
+ " mToTop=" + mToTop
diff --git a/core/java/android/window/WindowProvider.java b/core/java/android/window/WindowProvider.java
index b078b9362b90..dbdc68fbfd44 100644
--- a/core/java/android/window/WindowProvider.java
+++ b/core/java/android/window/WindowProvider.java
@@ -15,8 +15,10 @@
*/
package android.window;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Bundle;
+import android.os.IBinder;
import android.view.WindowManager.LayoutParams.WindowType;
/**
@@ -36,4 +38,11 @@ public interface WindowProvider {
/** Gets the launch options of this provider */
@Nullable
Bundle getWindowContextOptions();
+
+ /**
+ * Gets the WindowContextToken of this provider.
+ * @see android.content.Context#getWindowContextToken
+ */
+ @NonNull
+ IBinder getWindowContextToken();
}
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
index 2d2c8de72646..fdc3e5af8d8b 100644
--- a/core/java/android/window/WindowProviderService.java
+++ b/core/java/android/window/WindowProviderService.java
@@ -27,7 +27,10 @@ import android.annotation.UiContext;
import android.app.ActivityThread;
import android.app.LoadedApk;
import android.app.Service;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacksController;
import android.content.Context;
+import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.IBinder;
@@ -54,6 +57,8 @@ public abstract class WindowProviderService extends Service implements WindowPro
private final WindowContextController mController = new WindowContextController(mWindowToken);
private WindowManager mWindowManager;
private boolean mInitialized;
+ private final ComponentCallbacksController mCallbacksController =
+ new ComponentCallbacksController();
/**
* Returns {@code true} if the {@code windowContextOptions} declares that it is a
@@ -118,6 +123,48 @@ public abstract class WindowProviderService extends Service implements WindowPro
return mOptions;
}
+ @SuppressLint({"OnNameExpected", "ExecutorRegistration"})
+ // Suppress lint because this is a legacy named function and doesn't have an optional param
+ // for executor.
+ // TODO(b/259347943): Update documentation for U.
+ /**
+ * Here we override to prevent WindowProviderService from invoking
+ * {@link Application.registerComponentCallback}, which will result in callback registered
+ * for process-level Configuration change updates.
+ */
+ @Override
+ public void registerComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ // For broadcasting Configuration Changes.
+ mCallbacksController.registerCallbacks(callback);
+ }
+
+ @SuppressLint("OnNameExpected")
+ @Override
+ public void unregisterComponentCallbacks(@NonNull ComponentCallbacks callback) {
+ mCallbacksController.unregisterCallbacks(callback);
+ }
+
+ @SuppressLint("OnNameExpected")
+ @Override
+ public void onConfigurationChanged(@Nullable Configuration configuration) {
+ // This is only called from WindowTokenClient.
+ mCallbacksController.dispatchConfigurationChanged(configuration);
+ }
+
+ /**
+ * Override {@link Service}'s empty implementation and listen to {@link ActivityThread} for
+ * low memory and trim memory events.
+ */
+ @Override
+ public void onLowMemory() {
+ mCallbacksController.dispatchLowMemory();
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ mCallbacksController.dispatchTrimMemory(level);
+ }
+
/**
* Returns the display ID to launch this {@link WindowProviderService}.
*
@@ -181,5 +228,6 @@ public abstract class WindowProviderService extends Service implements WindowPro
public void onDestroy() {
super.onDestroy();
mController.detachIfNeeded();
+ mCallbacksController.clearCallbacks();
}
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index e926605ae88a..a237e986f1d7 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -492,6 +492,12 @@ public class ResolverActivity extends Activity implements
/* workProfileUserHandle= */ null);
}
+ private UserHandle getIntentUser() {
+ return getIntent().hasExtra(EXTRA_CALLING_USER)
+ ? getIntent().getParcelableExtra(EXTRA_CALLING_USER, android.os.UserHandle.class)
+ : getUser();
+ }
+
private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles(
Intent[] initialIntents,
List<ResolveInfo> rList,
@@ -500,9 +506,7 @@ public class ResolverActivity extends Activity implements
// the intent resolver is started in the other profile. Since this is the only case when
// this happens, we check for it here and set the current profile's tab.
int selectedProfile = getCurrentProfile();
- UserHandle intentUser = getIntent().hasExtra(EXTRA_CALLING_USER)
- ? getIntent().getParcelableExtra(EXTRA_CALLING_USER, android.os.UserHandle.class)
- : getUser();
+ UserHandle intentUser = getIntentUser();
if (!getUser().equals(intentUser)) {
if (getPersonalProfileUserHandle().equals(intentUser)) {
selectedProfile = PROFILE_PERSONAL;
diff --git a/core/java/com/android/internal/expresslog/OWNERS b/core/java/com/android/internal/expresslog/OWNERS
new file mode 100644
index 000000000000..ee865b1e4ec8
--- /dev/null
+++ b/core/java/com/android/internal/expresslog/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/stats/OWNERS
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 09f099120812..614f96255acb 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -91,7 +91,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.UiThread;
import android.annotation.WorkerThread;
-import android.app.ActivityThread;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
@@ -417,7 +416,6 @@ public class InteractionJankMonitor {
public InteractionJankMonitor(@NonNull HandlerThread worker) {
// Check permission early.
DeviceConfig.enforceReadPermission(
- ActivityThread.currentApplication().getApplicationContext(),
DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
mRunningTrackers = new SparseArray<>();
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 696f0ffba518..556e1467c7df 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -403,7 +403,7 @@ public class BatteryStatsHistory {
* Returns true if this instance only supports reading history.
*/
public boolean isReadOnly() {
- return mActiveFile == null;
+ return mActiveFile == null || mHistoryDir == null;
}
/**
@@ -1292,7 +1292,9 @@ public class BatteryStatsHistory {
&& mHistoryLastWritten.batteryHealth == cur.batteryHealth
&& mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
&& mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
- && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
+ && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage
+ && mHistoryLastWritten.measuredEnergyDetails == null
+ && mHistoryLastWritten.cpuUsageDetails == null) {
// We can merge this new change in with the last one. Merging is
// allowed as long as only the states have changed, and within those states
// as long as no bit has changed both between now and the last entry, as
@@ -1761,8 +1763,8 @@ public class BatteryStatsHistory {
* Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} .
*/
public void writeHistory() {
- if (mActiveFile == null) {
- Slog.w(TAG, "writeHistory: no history file associated with this instance");
+ if (isReadOnly()) {
+ Slog.w(TAG, "writeHistory: this instance instance is read-only");
return;
}
diff --git a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
index 9be686a772c1..a1ad5d5d5039 100644
--- a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
+++ b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
@@ -26,5 +26,7 @@ package com.android.internal.os;
interface IBinaryTransparencyService {
String getSignedImageInfo();
- Map getApexInfo();
+ List getApexInfo();
+
+ List getMeasurementsForAllPackages();
} \ No newline at end of file
diff --git a/core/java/com/android/internal/os/ProcLocksReader.java b/core/java/com/android/internal/os/ProcLocksReader.java
index 2143bc1b4532..9ddb8c75f5c8 100644
--- a/core/java/com/android/internal/os/ProcLocksReader.java
+++ b/core/java/com/android/internal/os/ProcLocksReader.java
@@ -16,6 +16,8 @@
package com.android.internal.os;
+import android.util.IntArray;
+
import com.android.internal.util.ProcFileReader;
import java.io.FileInputStream;
@@ -35,6 +37,7 @@ import java.io.IOException;
public class ProcLocksReader {
private final String mPath;
private ProcFileReader mReader = null;
+ private IntArray mPids = new IntArray();
public ProcLocksReader() {
mPath = "/proc/locks";
@@ -51,9 +54,13 @@ public class ProcLocksReader {
public interface ProcLocksReaderCallback {
/**
* Call the callback function of handleBlockingFileLocks().
- * @param pid Each process that hold file locks blocking other processes.
+ * @param pids Each process that hold file locks blocking other processes.
+ * pids[0] is the process blocking others
+ * pids[1..n-1] are the processes being blocked
+ * NOTE: pids are cleared immediately after onBlockingFileLock() returns. If the caller
+ * needs to cache it, please make a copy, e.g. by calling pids.toArray().
*/
- void onBlockingFileLock(int pid);
+ void onBlockingFileLock(IntArray pids);
}
/**
@@ -64,8 +71,7 @@ public class ProcLocksReader {
public void handleBlockingFileLocks(ProcLocksReaderCallback callback) throws IOException {
long last = -1;
long id; // ordinal position of the lock in the list
- int owner = -1; // the PID of the process that owns the lock
- int pid = -1; // the PID of the process blocking others
+ int pid = -1; // the PID of the process being blocked
if (mReader == null) {
mReader = new ProcFileReader(new FileInputStream(mPath));
@@ -73,26 +79,49 @@ public class ProcLocksReader {
mReader.rewind();
}
+ mPids.clear();
while (mReader.hasMoreData()) {
id = mReader.nextLong(true); // lock id
if (id == last) {
- mReader.finishLine(); // blocked lock
- if (pid < 0) {
- pid = owner; // get pid from the previous line
- callback.onBlockingFileLock(pid);
+ // blocked lock found
+ mReader.nextIgnored(); // ->
+ mReader.nextIgnored(); // lock type: POSIX?
+ mReader.nextIgnored(); // lock type: MANDATORY?
+ mReader.nextIgnored(); // lock type: RW?
+
+ pid = mReader.nextInt(); // pid
+ if (pid > 0) {
+ mPids.add(pid);
}
- continue;
+
+ mReader.finishLine();
} else {
- pid = -1; // a new lock
- }
+ // process blocking lock and move on to a new lock
+ if (mPids.size() > 1) {
+ callback.onBlockingFileLock(mPids);
+ mPids.clear();
+ }
- mReader.nextIgnored(); // lock type: POSIX?
- mReader.nextIgnored(); // lock type: MANDATORY?
- mReader.nextIgnored(); // lock type: RW?
+ // new lock found
+ mReader.nextIgnored(); // lock type: POSIX?
+ mReader.nextIgnored(); // lock type: MANDATORY?
+ mReader.nextIgnored(); // lock type: RW?
- owner = mReader.nextInt(); // pid
- mReader.finishLine();
- last = id;
+ pid = mReader.nextInt(); // pid
+ if (pid > 0) {
+ if (mPids.size() == 0) {
+ mPids.add(pid);
+ } else {
+ mPids.set(0, pid);
+ }
+ }
+ mReader.finishLine();
+ last = id;
+ }
+ }
+ // The last unprocessed blocking lock immediately before EOF
+ if (mPids.size() > 1) {
+ callback.onBlockingFileLock(mPids);
}
}
}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 28b98d6fab06..8a9445d8554a 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -16,10 +16,14 @@
package com.android.internal.os;
+import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
+
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ApplicationErrorReport;
import android.app.IActivityManager;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.type.DefaultMimeMapFactory;
import android.net.TrafficStats;
@@ -36,6 +40,7 @@ import com.android.internal.logging.AndroidConfig;
import dalvik.system.RuntimeHooks;
import dalvik.system.VMRuntime;
+import dalvik.system.ZipPathValidator;
import libcore.content.type.MimeMap;
@@ -260,10 +265,31 @@ public class RuntimeInit {
*/
TrafficStats.attachSocketTagger();
+ /*
+ * Initialize the zip path validator callback depending on the targetSdk.
+ */
+ initZipPathValidatorCallback();
+
initialized = true;
}
/**
+ * If targetSDK >= U: set the safe zip path validator callback which disallows dangerous zip
+ * entry names.
+ * Otherwise: clear the callback to the default validation.
+ *
+ * @hide
+ */
+ @TestApi
+ public static void initZipPathValidatorCallback() {
+ if (CompatChanges.isChangeEnabled(VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL)) {
+ ZipPathValidator.setCallback(new SafeZipPathValidatorCallback());
+ } else {
+ ZipPathValidator.clearCallback();
+ }
+ }
+
+ /**
* Returns an HTTP user agent of the form
* "Dalvik/1.1.0 (Linux; U; Android Eclair Build/MAIN)".
*/
diff --git a/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java b/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java
new file mode 100644
index 000000000000..a6ee108eadeb
--- /dev/null
+++ b/core/java/com/android/internal/os/SafeZipPathValidatorCallback.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.annotation.NonNull;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
+
+import dalvik.system.ZipPathValidator;
+
+import java.io.File;
+import java.util.zip.ZipException;
+
+/**
+ * A child implementation of the {@link dalvik.system.ZipPathValidator.Callback} that removes the
+ * risk of zip path traversal vulnerabilities.
+ *
+ * @hide
+ */
+public class SafeZipPathValidatorCallback implements ZipPathValidator.Callback {
+ /**
+ * This change targets zip path traversal vulnerabilities by throwing
+ * {@link java.util.zip.ZipException} if zip path entries contain ".." or start with "/".
+ * <p>
+ * The exception will be thrown in {@link java.util.zip.ZipInputStream#getNextEntry} or
+ * {@link java.util.zip.ZipFile#ZipFile(String)}.
+ * <p>
+ * This validation is enabled for apps with targetSDK >= U.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL = 242716250L;
+
+ @Override
+ public void onZipEntryAccess(@NonNull String path) throws ZipException {
+ if (path.startsWith("/")) {
+ throw new ZipException("Invalid zip entry path: " + path);
+ }
+ if (path.contains("..")) {
+ // If the string does contain "..", break it down into its actual name elements to
+ // ensure it actually contains ".." as a name, not just a name like "foo..bar" or even
+ // "foo..", which should be fine.
+ File file = new File(path);
+ while (file != null) {
+ if (file.getName().equals("..")) {
+ throw new ZipException("Invalid zip entry path: " + path);
+ }
+ file = file.getParentFile();
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index be3f1720acdd..680f8fe6535f 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -18,6 +18,7 @@ package com.android.internal.os;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.content.Intent;
import android.os.SystemClock;
import com.android.internal.os.anr.AnrLatencyTracker;
@@ -92,7 +93,17 @@ public class TimeoutRecord {
/** Record for a broadcast receiver timeout. */
@NonNull
- public static TimeoutRecord forBroadcastReceiver(@NonNull String reason) {
+ public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent) {
+ String reason = "Broadcast of " + intent.toString();
+ return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
+ }
+
+ /** Record for a broadcast receiver timeout. */
+ @NonNull
+ public static TimeoutRecord forBroadcastReceiver(@NonNull Intent intent,
+ long timeoutDurationMs) {
+ String reason = "Broadcast of " + intent.toString() + ", waited " + timeoutDurationMs
+ + "ms";
return TimeoutRecord.endingNow(TimeoutKind.BROADCAST_RECEIVER, reason);
}
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index a068008f5e22..9f39c32171d7 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -99,3 +99,5 @@ per-file com_android_internal_os_*MultiStateCounter* = file:/BATTERY_STATS_OWNER
# PM
per-file com_android_internal_content_* = file:/PACKAGE_MANAGER_OWNERS
+# Stats/expresslog
+per-file *expresslog* = file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 2b932cb37332..98814bf602fc 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -380,8 +380,8 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Received motion event.", getInputChannelName().c_str());
}
- MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
- if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
+ const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*inputEvent);
+ if ((motionEvent.getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
*outConsumedBatch = true;
}
inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index a30935b621a2..80df0ead4bcd 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -82,7 +82,7 @@ static void android_view_MotionEvent_setNativePtr(JNIEnv* env, jobject eventObj,
reinterpret_cast<jlong>(event));
}
-jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event) {
+jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event) {
jobject eventObj = env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,
gMotionEventClassInfo.obtain);
if (env->ExceptionCheck() || !eventObj) {
@@ -98,7 +98,7 @@ jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* ev
android_view_MotionEvent_setNativePtr(env, eventObj, destEvent);
}
- destEvent->copyFrom(event, true);
+ destEvent->copyFrom(&event, true);
return eventObj;
}
diff --git a/core/jni/android_view_MotionEvent.h b/core/jni/android_view_MotionEvent.h
index 9ce4bf367688..32a280ec1974 100644
--- a/core/jni/android_view_MotionEvent.h
+++ b/core/jni/android_view_MotionEvent.h
@@ -26,7 +26,7 @@ class MotionEvent;
/* Obtains an instance of a DVM MotionEvent object as a copy of a native MotionEvent instance.
* Returns NULL on error. */
-extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event);
+extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event);
/* Gets the underlying native MotionEvent instance within a DVM MotionEvent object.
* Returns NULL if the event is NULL or if it is uninitialized. */
diff --git a/core/proto/android/app/location_time_zone_manager.proto b/core/proto/android/app/location_time_zone_manager.proto
index 5fdcfdf35a37..7037a6c4f68a 100644
--- a/core/proto/android/app/location_time_zone_manager.proto
+++ b/core/proto/android/app/location_time_zone_manager.proto
@@ -40,7 +40,7 @@ enum ControllerStateEnum {
message LocationTimeZoneManagerServiceStateProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
- optional GeolocationTimeZoneSuggestionProto last_suggestion = 1;
+ optional LocationTimeZoneProviderEventProto last_event = 1;
repeated TimeZoneProviderStateProto primary_provider_states = 2;
repeated TimeZoneProviderStateProto secondary_provider_states = 3;
repeated ControllerStateEnum controller_states = 4;
diff --git a/core/proto/android/app/time_zone_detector.proto b/core/proto/android/app/time_zone_detector.proto
index b52aa828bef9..cd4a36fafef0 100644
--- a/core/proto/android/app/time_zone_detector.proto
+++ b/core/proto/android/app/time_zone_detector.proto
@@ -22,13 +22,38 @@ import "frameworks/base/core/proto/android/privacy.proto";
option java_multiple_files = true;
option java_outer_classname = "TimeZoneDetectorProto";
-// Represents a GeolocationTimeZoneSuggestion that can be / has been passed to the time zone
+// Represents a LocationTimeZoneProviderEvent that can be / has been passed to the time zone
// detector.
+message LocationTimeZoneProviderEventProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional GeolocationTimeZoneSuggestionProto suggestion = 1;
+ repeated string debug_info = 2;
+ optional LocationTimeZoneAlgorithmStatusProto algorithm_status = 3;
+}
+
+// Represents a LocationTimeZoneAlgorithmStatus that can be / has been passed to the time zone
+// detector.
+message LocationTimeZoneAlgorithmStatusProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional DetectionAlgorithmStatusEnum status = 1;
+}
+
+// The state enum for detection algorithms.
+enum DetectionAlgorithmStatusEnum {
+ DETECTION_ALGORITHM_STATUS_UNKNOWN = 0;
+ DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED = 1;
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING = 2;
+ DETECTION_ALGORITHM_STATUS_RUNNING = 3;
+}
+
+// Represents a GeolocationTimeZoneSuggestion that can be contained in a
+// LocationTimeZoneProviderEvent.
message GeolocationTimeZoneSuggestionProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
repeated string zone_ids = 1;
- repeated string debug_info = 2;
}
/*
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 196ea59fe0f4..e24d667e8600 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3177,10 +3177,10 @@
<permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"
android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role"/>
- <!-- Allows an application to hint that a broadcast is associated with an
- "interactive" usage scenario
+ <!-- Allows an application to hint that a component lifecycle operation such as sending
+ a broadcast is associated with an "interactive" usage scenario.
@hide -->
- <permission android:name="android.permission.BROADCAST_OPTION_INTERACTIVE"
+ <permission android:name="android.permission.COMPONENT_OPTION_INTERACTIVE"
android:protectionLevel="signature|privileged" />
<!-- @SystemApi Must be required by activities that handle the intent action
diff --git a/core/res/res/anim/dock_bottom_enter.xml b/core/res/res/anim/dock_bottom_enter.xml
deleted file mode 100644
index bfb97b6d9b80..000000000000
--- a/core/res/res/anim/dock_bottom_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the bottom of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/decelerate_cubic">
- <translate android:fromYDelta="100%" android:toYDelta="0"
- android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/anim/dock_bottom_exit.xml b/core/res/res/anim/dock_bottom_exit.xml
deleted file mode 100644
index 4e15448aba3e..000000000000
--- a/core/res/res/anim/dock_bottom_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the bottom of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/accelerate_cubic">
- <translate android:fromYDelta="0" android:toYDelta="100%"
- android:startOffset="100" android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/anim/dock_bottom_exit_keyguard.xml b/core/res/res/anim/dock_bottom_exit_keyguard.xml
deleted file mode 100644
index 4de3ce5b8932..000000000000
--- a/core/res/res/anim/dock_bottom_exit_keyguard.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<!--
- ~ Copyright (C) 2016 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<!-- Animation for when a dock window at the bottom of the screen is exiting while on Keyguard -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/fast_out_linear_in">
- <translate android:fromYDelta="0" android:toYDelta="100%"
- android:duration="200"/>
-</set> \ No newline at end of file
diff --git a/core/res/res/anim/dock_left_enter.xml b/core/res/res/anim/dock_left_enter.xml
deleted file mode 100644
index 7f5dfd50afc7..000000000000
--- a/core/res/res/anim/dock_left_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the left of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/decelerate_cubic">
- <translate android:fromXDelta="-100%" android:toXDelta="0"
- android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_left_exit.xml b/core/res/res/anim/dock_left_exit.xml
deleted file mode 100644
index 11cbc0b36405..000000000000
--- a/core/res/res/anim/dock_left_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the right of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/accelerate_cubic">
- <translate android:fromXDelta="0" android:toXDelta="-100%"
- android:startOffset="100" android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_right_enter.xml b/core/res/res/anim/dock_right_enter.xml
deleted file mode 100644
index a92c7d234e58..000000000000
--- a/core/res/res/anim/dock_right_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the right of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/decelerate_cubic">
- <translate android:fromXDelta="100%" android:toXDelta="0"
- android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_right_exit.xml b/core/res/res/anim/dock_right_exit.xml
deleted file mode 100644
index 80e4dc318192..000000000000
--- a/core/res/res/anim/dock_right_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the right of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/accelerate_cubic">
- <translate android:fromXDelta="0" android:toXDelta="100%"
- android:startOffset="100" android:duration="250"/>
-</set>
diff --git a/core/res/res/anim/dock_top_enter.xml b/core/res/res/anim/dock_top_enter.xml
deleted file mode 100644
index f763fb5c1cc9..000000000000
--- a/core/res/res/anim/dock_top_enter.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the top of the screen is entering. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/decelerate_cubic">
- <translate android:fromYDelta="-100%" android:toYDelta="0"
- android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/anim/dock_top_exit.xml b/core/res/res/anim/dock_top_exit.xml
deleted file mode 100644
index 995b7d0ffc8c..000000000000
--- a/core/res/res/anim/dock_top_exit.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<!-- Animation for when a dock window at the top of the screen is exiting. -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:interpolator="@android:interpolator/accelerate_cubic">
- <translate android:fromYDelta="0" android:toYDelta="-100%"
- android:startOffset="100" android:duration="@integer/dock_enter_exit_duration"/>
-</set>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 932b24ec08c9..6c18259c031c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3688,6 +3688,12 @@
experience while the device is non-interactive. -->
<bool name="config_emergencyGestureEnabled">true</bool>
+ <!-- Default value for Use Emergency SOS in Settings false = disabled, true = enabled -->
+ <bool name="config_defaultEmergencyGestureEnabled">true</bool>
+
+ <!-- Default value for Use Play countdown alarm in Settings false = disabled, true = enabled -->
+ <bool name="config_defaultEmergencyGestureSoundEnabled">false</bool>
+
<!-- Allow the gesture power + volume up to change the ringer mode while the device
is interactive. -->
<bool name="config_volumeHushGestureEnabled">true</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 89ec5ba48142..5811ed9b591f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1691,15 +1691,6 @@
<!-- From android.policy -->
<java-symbol type="anim" name="app_starting_exit" />
- <java-symbol type="anim" name="dock_top_enter" />
- <java-symbol type="anim" name="dock_top_exit" />
- <java-symbol type="anim" name="dock_bottom_enter" />
- <java-symbol type="anim" name="dock_bottom_exit" />
- <java-symbol type="anim" name="dock_bottom_exit_keyguard" />
- <java-symbol type="anim" name="dock_left_enter" />
- <java-symbol type="anim" name="dock_left_exit" />
- <java-symbol type="anim" name="dock_right_enter" />
- <java-symbol type="anim" name="dock_right_exit" />
<java-symbol type="anim" name="fade_in" />
<java-symbol type="anim" name="fade_out" />
<java-symbol type="anim" name="voice_activity_close_exit" />
@@ -3024,6 +3015,8 @@
<java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
<java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
<java-symbol type="bool" name="config_emergencyGestureEnabled" />
+ <java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" />
+ <java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" />
<java-symbol type="bool" name="config_volumeHushGestureEnabled" />
<java-symbol type="drawable" name="platlogo_m" />
diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
index 7e875ada7267..10452fd64430 100644
--- a/core/tests/coretests/src/android/app/activity/BroadcastTest.java
+++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
@@ -541,35 +541,35 @@ public class BroadcastTest extends ActivityTestsBase {
public void testBroadcastOption_interactive() throws Exception {
final BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setInteractiveBroadcast(true);
+ options.setInteractive(true);
final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
try {
getContext().sendBroadcast(intent, null, options.toBundle());
- fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+ fail("No exception thrown with BroadcastOptions.setInteractive(true)");
} catch (SecurityException se) {
// Expected, correct behavior - this case intentionally empty
} catch (Exception e) {
fail("Unexpected exception " + e.getMessage()
- + " thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+ + " thrown with BroadcastOptions.setInteractive(true)");
}
}
public void testBroadcastOption_interactive_PendingIntent() throws Exception {
final BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setInteractiveBroadcast(true);
+ options.setInteractive(true);
final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
PendingIntent brPending = PendingIntent.getBroadcast(getContext(),
1, intent, PendingIntent.FLAG_IMMUTABLE);
try {
brPending.send(getContext(), 1, null, null, null, null, options.toBundle());
- fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+ fail("No exception thrown with BroadcastOptions.setInteractive(true)");
} catch (SecurityException se) {
// Expected, correct behavior - this case intentionally empty
} catch (Exception e) {
fail("Unexpected exception " + e.getMessage()
- + " thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+ + " thrown with BroadcastOptions.setInteractive(true)");
} finally {
brPending.cancel();
}
diff --git a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
index 37cf4700c1d0..4d5b0d2d4ae7 100644
--- a/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupAgentTest.java
@@ -46,6 +46,7 @@ import java.util.Set;
public class BackupAgentTest {
// An arbitrary user.
private static final UserHandle USER_HANDLE = new UserHandle(15);
+ private static final String DATA_TYPE_BACKED_UP = "test data type";
@Mock FullBackup.BackupScheme mBackupScheme;
@@ -73,6 +74,42 @@ public class BackupAgentTest {
assertThat(rules).isEqualTo(expectedRules);
}
+ @Test
+ public void getBackupRestoreEventLogger_beforeOnCreate_isNull() {
+ BackupAgent agent = new TestFullBackupAgent();
+
+ assertThat(agent.getBackupRestoreEventLogger()).isNull();
+ }
+
+ @Test
+ public void getBackupRestoreEventLogger_afterOnCreateForBackup_initializedForBackup() {
+ BackupAgent agent = new TestFullBackupAgent();
+ agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+
+ assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1);
+ }
+
+ @Test
+ public void getBackupRestoreEventLogger_afterOnCreateForRestore_initializedForRestore() {
+ BackupAgent agent = new TestFullBackupAgent();
+ agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+
+ assertThat(agent.getBackupRestoreEventLogger().getOperationType()).isEqualTo(1);
+ }
+
+ @Test
+ public void getBackupRestoreEventLogger_afterBackup_containsLogsLoggedByAgent()
+ throws Exception {
+ BackupAgent agent = new TestFullBackupAgent();
+ agent.onCreate(USER_HANDLE, OperationType.BACKUP); // TODO: pass in new operation type
+
+ // TestFullBackupAgent logs DATA_TYPE_BACKED_UP when onFullBackup is called.
+ agent.onFullBackup(new FullBackupDataOutput(/* quota = */ 0));
+
+ assertThat(agent.getBackupRestoreEventLogger().getLoggingResults().get(0).getDataType())
+ .isEqualTo(DATA_TYPE_BACKED_UP);
+ }
+
private BackupAgent getAgentForOperationType(@OperationType int operationType) {
BackupAgent agent = new TestFullBackupAgent();
agent.onCreate(USER_HANDLE, operationType);
@@ -88,6 +125,11 @@ public class BackupAgentTest {
}
@Override
+ public void onFullBackup(FullBackupDataOutput data) {
+ getBackupRestoreEventLogger().logItemsBackedUp(DATA_TYPE_BACKED_UP, 1);
+ }
+
+ @Override
public void onRestore(BackupDataInput data, int appVersionCode,
ParcelFileDescriptor newState) throws IOException {
// Left empty as this is a full backup agent.
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
index bbd2ef38d786..b9fdc6d2aa23 100644
--- a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -25,6 +25,7 @@ import static junit.framework.Assert.fail;
import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
+import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
@@ -35,6 +36,7 @@ import org.junit.runner.RunWith;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -236,10 +238,10 @@ public class BackupRestoreEventLoggerTest {
mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, ERROR_1);
mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, ERROR_2);
- int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
- .getErrors().get(ERROR_1);
- int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
- .getErrors().get(ERROR_2);
+ int firstErrorTypeCount =
+ getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_1);
+ int secondErrorTypeCount =
+ getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_2);
assertThat(firstErrorTypeCount).isEqualTo(firstCount);
assertThat(secondErrorTypeCount).isEqualTo(secondCount);
}
@@ -253,16 +255,54 @@ public class BackupRestoreEventLoggerTest {
mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, ERROR_1);
mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, ERROR_2);
- int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
- .getErrors().get(ERROR_1);
- int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
- .getErrors().get(ERROR_2);
+ int firstErrorTypeCount =
+ getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_1);
+ int secondErrorTypeCount =
+ getResultForDataType(mLogger, DATA_TYPE_1).getErrors().get(ERROR_2);
assertThat(firstErrorTypeCount).isEqualTo(firstCount);
assertThat(secondErrorTypeCount).isEqualTo(secondCount);
}
- private static DataTypeResult getResultForDataType(BackupRestoreEventLogger logger,
- @BackupRestoreDataType String dataType) {
+ @Test
+ public void testGetLoggingResults_resultsParceledAndUnparceled_recreatedCorrectly() {
+ mLogger = new BackupRestoreEventLogger(RESTORE);
+ int firstTypeSuccessCount = 1;
+ int firstTypeErrorOneCount = 2;
+ int firstTypeErrorTwoCount = 3;
+ mLogger.logItemsRestored(DATA_TYPE_1, firstTypeSuccessCount);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstTypeErrorOneCount, ERROR_1);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstTypeErrorTwoCount, ERROR_2);
+ mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_1);
+ int secondTypeSuccessCount = 4;
+ int secondTypeErrorOneCount = 5;
+ mLogger.logItemsRestored(DATA_TYPE_2, secondTypeSuccessCount);
+ mLogger.logItemsRestoreFailed(DATA_TYPE_2, secondTypeErrorOneCount, ERROR_1);
+
+ List<DataTypeResult> resultsList = mLogger.getLoggingResults();
+ Parcel parcel = Parcel.obtain();
+
+ parcel.writeParcelableList(resultsList, /* flags= */ 0);
+
+ parcel.setDataPosition(0);
+ List<DataTypeResult> recreatedList = new ArrayList<>();
+ parcel.readParcelableList(
+ recreatedList, DataTypeResult.class.getClassLoader(), DataTypeResult.class);
+
+ assertThat(recreatedList.get(0).getDataType()).isEqualTo(DATA_TYPE_1);
+ assertThat(recreatedList.get(0).getSuccessCount()).isEqualTo(firstTypeSuccessCount);
+ assertThat(recreatedList.get(0).getFailCount())
+ .isEqualTo(firstTypeErrorOneCount + firstTypeErrorTwoCount);
+ assertThat(recreatedList.get(0).getErrors().get(ERROR_1)).isEqualTo(firstTypeErrorOneCount);
+ assertThat(recreatedList.get(0).getErrors().get(ERROR_2)).isEqualTo(firstTypeErrorTwoCount);
+ assertThat(recreatedList.get(1).getDataType()).isEqualTo(DATA_TYPE_2);
+ assertThat(recreatedList.get(1).getSuccessCount()).isEqualTo(secondTypeSuccessCount);
+ assertThat(recreatedList.get(1).getFailCount()).isEqualTo(secondTypeErrorOneCount);
+ assertThat(recreatedList.get(1).getErrors().get(ERROR_1))
+ .isEqualTo(secondTypeErrorOneCount);
+ }
+
+ private static DataTypeResult getResultForDataType(
+ BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
if (result.isEmpty()) {
fail("Failed to find result for data type: " + dataType);
@@ -273,8 +313,9 @@ public class BackupRestoreEventLoggerTest {
private static Optional<DataTypeResult> getResultForDataTypeIfPresent(
BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
List<DataTypeResult> resultList = logger.getLoggingResults();
- return resultList.stream().filter(
- dataTypeResult -> dataTypeResult.getDataType().equals(dataType)).findAny();
+ return resultList.stream()
+ .filter(dataTypeResult -> dataTypeResult.getDataType().equals(dataType))
+ .findAny();
}
private byte[] getMetaDataHash(String metaData) {
diff --git a/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java b/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java
new file mode 100644
index 000000000000..f57ee43b76c8
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/DetectorStatusTypesTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_UNKNOWN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.app.time.DetectorStatusTypes.DetectorStatus;
+
+import org.junit.Test;
+
+public class DetectorStatusTypesTest {
+
+ @Test
+ public void testRequireValidDetectionAlgorithmStatus() {
+ for (@DetectionAlgorithmStatus int status = DETECTION_ALGORITHM_STATUS_UNKNOWN;
+ status <= DETECTION_ALGORITHM_STATUS_RUNNING; status++) {
+ assertEquals(status, DetectorStatusTypes.requireValidDetectionAlgorithmStatus(status));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.requireValidDetectionAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_UNKNOWN - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.requireValidDetectionAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING + 1));
+ }
+
+ @Test
+ public void testFormatAndParseDetectionAlgorithmStatus() {
+ for (@DetectionAlgorithmStatus int status = DETECTION_ALGORITHM_STATUS_UNKNOWN;
+ status <= DETECTION_ALGORITHM_STATUS_RUNNING; status++) {
+ assertEquals(status, DetectorStatusTypes.detectionAlgorithmStatusFromString(
+ DetectorStatusTypes.detectionAlgorithmStatusToString(status)));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusToString(
+ DETECTION_ALGORITHM_STATUS_UNKNOWN - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusToString(
+ DETECTION_ALGORITHM_STATUS_RUNNING + 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString(null));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString(""));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString("FOO"));
+ }
+
+ @Test
+ public void testRequireValidDetectorStatus() {
+ for (@DetectorStatus int status = DETECTOR_STATUS_UNKNOWN;
+ status <= DETECTOR_STATUS_RUNNING; status++) {
+ assertEquals(status, DetectorStatusTypes.requireValidDetectorStatus(status));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.requireValidDetectorStatus(DETECTOR_STATUS_UNKNOWN - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.requireValidDetectorStatus(DETECTOR_STATUS_RUNNING + 1));
+ }
+
+ @Test
+ public void testFormatAndParseDetectorStatus() {
+ for (@DetectorStatus int status = DETECTOR_STATUS_UNKNOWN;
+ status <= DETECTOR_STATUS_RUNNING; status++) {
+ assertEquals(status, DetectorStatusTypes.detectorStatusFromString(
+ DetectorStatusTypes.detectorStatusToString(status)));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusToString(DETECTOR_STATUS_UNKNOWN - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusToString(DETECTOR_STATUS_RUNNING + 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString(null));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString(""));
+ assertThrows(IllegalArgumentException.class,
+ () -> DetectorStatusTypes.detectorStatusFromString("FOO"));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
new file mode 100644
index 000000000000..a648a885aea2
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/LocationTimeZoneAlgorithmStatusTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
+import android.service.timezone.TimeZoneProviderStatus;
+
+import org.junit.Test;
+
+public class LocationTimeZoneAlgorithmStatusTest {
+
+ private static final TimeZoneProviderStatus ARBITRARY_PROVIDER_RUNNING_STATUS =
+ new TimeZoneProviderStatus.Builder()
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+ .build();
+
+ @Test
+ public void testConstructorValidation() {
+ // Sample some invalid cases
+
+ // There can't be a reported provider status if the algorithm isn't running.
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS));
+
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null));
+
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS));
+
+ // No reported provider status expected if the associated provider isn't ready / present.
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null));
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_READY, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertThrows(IllegalArgumentException.class,
+ () -> new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_READY, null,
+ PROVIDER_STATUS_NOT_PRESENT, ARBITRARY_PROVIDER_RUNNING_STATUS));
+ }
+
+ @Test
+ public void testEquals() {
+ LocationTimeZoneAlgorithmStatus one = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertEqualsAndHashCode(one, one);
+
+ {
+ LocationTimeZoneAlgorithmStatus two = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertEqualsAndHashCode(one, two);
+ }
+
+ {
+ LocationTimeZoneAlgorithmStatus three = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_NOT_READY, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+ }
+
+ @Test
+ public void testParcelable() {
+ // Primary provider only.
+ {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+
+ // Secondary provider only
+ {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+
+ // Algorithm not running.
+ {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+ }
+
+ @Test
+ public void testRequireValidProviderStatus() {
+ for (@ProviderStatus int status = PROVIDER_STATUS_NOT_PRESENT;
+ status <= PROVIDER_STATUS_IS_UNCERTAIN; status++) {
+ assertEquals(status,
+ LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(status));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(
+ PROVIDER_STATUS_NOT_PRESENT - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.requireValidProviderStatus(
+ PROVIDER_STATUS_IS_UNCERTAIN + 1));
+ }
+
+ @Test
+ public void testFormatAndParseProviderStatus() {
+ for (@ProviderStatus int status = PROVIDER_STATUS_NOT_PRESENT;
+ status <= PROVIDER_STATUS_IS_UNCERTAIN; status++) {
+ assertEquals(status, LocationTimeZoneAlgorithmStatus.providerStatusFromString(
+ LocationTimeZoneAlgorithmStatus.providerStatusToString(status)));
+ }
+
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusToString(
+ PROVIDER_STATUS_NOT_PRESENT - 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusToString(
+ PROVIDER_STATUS_IS_UNCERTAIN + 1));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString(null));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString(""));
+ assertThrows(IllegalArgumentException.class,
+ () -> LocationTimeZoneAlgorithmStatus.providerStatusFromString("FOO"));
+ }
+
+ @Test
+ public void testParseCommandlineArg_noNullReportedStatuses() {
+ LocationTimeZoneAlgorithmStatus status = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS,
+ PROVIDER_STATUS_IS_UNCERTAIN, ARBITRARY_PROVIDER_RUNNING_STATUS);
+ assertEquals(status,
+ LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString()));
+ }
+
+ @Test
+ public void testParseCommandlineArg_withNullReportedStatuses() {
+ LocationTimeZoneAlgorithmStatus status = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, null,
+ PROVIDER_STATUS_IS_UNCERTAIN, null);
+ assertEquals(status,
+ LocationTimeZoneAlgorithmStatus.parseCommandlineArg(status.toString()));
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java b/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java
new file mode 100644
index 000000000000..b90c485bbbb6
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TelephonyTimeZoneAlgorithmStatusTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class TelephonyTimeZoneAlgorithmStatusTest {
+
+ @Test
+ public void testEquals() {
+ TelephonyTimeZoneAlgorithmStatus one = new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
+ assertEqualsAndHashCode(one, one);
+
+ {
+ TelephonyTimeZoneAlgorithmStatus two = new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
+ assertEqualsAndHashCode(one, two);
+ }
+
+ {
+ TelephonyTimeZoneAlgorithmStatus three = new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+ }
+
+ @Test
+ public void testParcelable() {
+ // Algorithm running.
+ {
+ TelephonyTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+
+ // Algorithm not running.
+ {
+ TelephonyTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new TelephonyTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java b/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java
new file mode 100644
index 000000000000..dfff7ecdf989
--- /dev/null
+++ b/core/tests/coretests/src/android/app/time/TimeZoneDetectorStatusTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.app.time;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.ParcelableTestSupport.assertEqualsAndHashCode;
+import static android.app.time.ParcelableTestSupport.assertRoundTripParcelable;
+
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class TimeZoneDetectorStatusTest {
+
+ private static final TelephonyTimeZoneAlgorithmStatus ARBITRARY_TELEPHONY_ALGORITHM_STATUS =
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
+
+ private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_ALGORITHM_STATUS =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null,
+ LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT, null);
+
+ @Test
+ public void testEquals() {
+ TimeZoneDetectorStatus one = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertEqualsAndHashCode(one, one);
+
+ {
+ TimeZoneDetectorStatus two = new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertEqualsAndHashCode(one, two);
+ }
+
+ {
+ TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+
+ {
+ TelephonyTimeZoneAlgorithmStatus telephonyAlgorithmStatus =
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
+ assertNotEquals(telephonyAlgorithmStatus, ARBITRARY_TELEPHONY_ALGORITHM_STATUS);
+
+ TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+ telephonyAlgorithmStatus, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+
+ {
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING,
+ LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null,
+ LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY, null);
+ assertNotEquals(locationAlgorithmStatus, ARBITRARY_LOCATION_ALGORITHM_STATUS);
+
+ TimeZoneDetectorStatus three = new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS, locationAlgorithmStatus);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+ }
+ }
+
+ @Test
+ public void testParcelable() {
+ // Detector running.
+ {
+ TimeZoneDetectorStatus locationAlgorithmStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING, ARBITRARY_TELEPHONY_ALGORITHM_STATUS,
+ ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+
+ // Detector not running.
+ {
+ TimeZoneDetectorStatus locationAlgorithmStatus =
+ new TimeZoneDetectorStatus(DETECTOR_STATUS_NOT_RUNNING,
+ ARBITRARY_TELEPHONY_ALGORITHM_STATUS,
+ ARBITRARY_LOCATION_ALGORITHM_STATUS);
+ assertRoundTripParcelable(locationAlgorithmStatus);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index 212cc44eefab..8459330cc07b 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -156,8 +156,8 @@ public class DateFormatTest {
@DisableCompatChanges({DateFormat.DISALLOW_DUPLICATE_FIELD_IN_SKELETON})
public void testGetBestDateTimePattern_enableDuplicateField() {
// en-US uses 12-hour format by default.
- assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
- assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
+ assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
+ assertEquals("h:mm\u202fa", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
}
private static void assertIllegalArgumentException(Locale l, String skeleton) {
diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
index 9c063954c0ad..de7244d49834 100644
--- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java
@@ -93,7 +93,8 @@ public class DateIntervalFormatTest {
assertEquals("January 19",
formatDateRange(en_US, tz, timeWithCurrentYear, timeWithCurrentYear + HOUR,
FORMAT_SHOW_DATE));
- assertEquals("3:30 AM", formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_SHOW_TIME));
+ assertEquals("3:30\u202fAM", formatDateRange(en_US, tz, fixedTime, fixedTime,
+ FORMAT_SHOW_TIME));
assertEquals("January 19, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + HOUR, FORMAT_SHOW_YEAR));
assertEquals("January 19",
@@ -101,27 +102,27 @@ public class DateIntervalFormatTest {
assertEquals("January",
formatDateRange(en_US, tz, timeWithCurrentYear, timeWithCurrentYear + HOUR,
FORMAT_NO_MONTH_DAY));
- assertEquals("3:30 AM",
+ assertEquals("3:30\u202fAM",
formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_12HOUR | FORMAT_SHOW_TIME));
assertEquals("03:30",
formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_24HOUR | FORMAT_SHOW_TIME));
- assertEquals("3:30 AM", formatDateRange(en_US, tz, fixedTime, fixedTime,
+ assertEquals("3:30\u202fAM", formatDateRange(en_US, tz, fixedTime, fixedTime,
FORMAT_12HOUR /*| FORMAT_CAP_AMPM*/ | FORMAT_SHOW_TIME));
- assertEquals("12:00 PM",
+ assertEquals("12:00\u202fPM",
formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
FORMAT_12HOUR | FORMAT_SHOW_TIME));
- assertEquals("12:00 PM",
+ assertEquals("12:00\u202fPM",
formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
FORMAT_12HOUR | FORMAT_SHOW_TIME /*| FORMAT_CAP_NOON*/));
- assertEquals("12:00 PM",
+ assertEquals("12:00\u202fPM",
formatDateRange(en_US, tz, fixedTime + noonDuration, fixedTime + noonDuration,
FORMAT_12HOUR /*| FORMAT_NO_NOON*/ | FORMAT_SHOW_TIME));
- assertEquals("12:00 AM", formatDateRange(en_US, tz, fixedTime - midnightDuration,
+ assertEquals("12:00\u202fAM", formatDateRange(en_US, tz, fixedTime - midnightDuration,
fixedTime - midnightDuration,
FORMAT_12HOUR | FORMAT_SHOW_TIME /*| FORMAT_NO_MIDNIGHT*/));
- assertEquals("3:30 AM",
+ assertEquals("3:30\u202fAM",
formatDateRange(en_US, tz, fixedTime, fixedTime, FORMAT_SHOW_TIME | FORMAT_UTC));
- assertEquals("3 AM", formatDateRange(en_US, tz, onTheHour, onTheHour,
+ assertEquals("3\u202fAM", formatDateRange(en_US, tz, onTheHour, onTheHour,
FORMAT_SHOW_TIME | FORMAT_ABBREV_TIME));
assertEquals("Mon", formatDateRange(en_US, tz, fixedTime, fixedTime + HOUR,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_WEEKDAY));
@@ -134,13 +135,13 @@ public class DateIntervalFormatTest {
assertEquals("1/19/2009", formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * HOUR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("1/19/2009 – 1/22/2009",
+ assertEquals("1/19/2009\u2009\u2013\u20091/22/2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("1/19/2009 – 4/22/2009",
+ assertEquals("1/19/2009\u2009\u2013\u20094/22/2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("1/19/2009 – 2/9/2012",
+ assertEquals("1/19/2009\u2009\u2013\u20092/9/2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
@@ -151,7 +152,7 @@ public class DateIntervalFormatTest {
assertEquals("19.01. – 22.04.2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19.01.2009 – 09.02.2012",
+ assertEquals("19.01.2009\u2009\u2013\u200909.02.2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
@@ -169,48 +170,48 @@ public class DateIntervalFormatTest {
assertEquals("19/1/2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + HOUR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19/1/2009 – 22/1/2009",
+ assertEquals("19/1/2009\u2009\u2013\u200922/1/2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19/1/2009 – 22/4/2009",
+ assertEquals("19/1/2009\u2009\u2013\u200922/4/2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
- assertEquals("19/1/2009 – 9/2/2012",
+ assertEquals("19/1/2009\u2009\u2013\u20099/2/2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE));
// These are some random other test cases I came up with.
- assertEquals("January 19 – 22, 2009",
+ assertEquals("January 19\u2009\u2013\u200922, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY, 0));
- assertEquals("Jan 19 – 22, 2009", formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
- FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Mon, Jan 19 – Thu, Jan 22, 2009",
+ assertEquals("Jan 19\u2009\u2013\u200922, 2009", formatDateRange(en_US, tz, fixedTime,
+ fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
+ assertEquals("Mon, Jan 19\u2009\u2013\u2009Thu, Jan 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("Monday, January 19 – Thursday, January 22, 2009",
+ assertEquals("Monday, January 19\u2009\u2013\u2009Thursday, January 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
- assertEquals("January 19 – April 22, 2009",
+ assertEquals("January 19\u2009\u2013\u2009April 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH, 0));
- assertEquals("Jan 19 – Apr 22, 2009",
+ assertEquals("Jan 19\u2009\u2013\u2009Apr 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Mon, Jan 19 – Wed, Apr 22, 2009",
+ assertEquals("Mon, Jan 19\u2009\u2013\u2009Wed, Apr 22, 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("January – April 2009",
+ assertEquals("January\u2009\u2013\u2009April 2009",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
- assertEquals("Jan 19, 2009 – Feb 9, 2012",
+ assertEquals("Jan 19, 2009\u2009\u2013\u2009Feb 9, 2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Jan 2009 – Feb 2012",
+ assertEquals("Jan 2009\u2009\u2013\u2009Feb 2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
- assertEquals("January 19, 2009 – February 9, 2012",
+ assertEquals("January 19, 2009\u2009\u2013\u2009February 9, 2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR, 0));
- assertEquals("Monday, January 19, 2009 – Thursday, February 9, 2012",
+ assertEquals("Monday, January 19, 2009\u2009\u2013\u2009Thursday, February 9, 2012",
formatDateRange(en_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
// The same tests but for de_DE.
@@ -225,26 +226,26 @@ public class DateIntervalFormatTest {
assertEquals("Montag, 19. – Donnerstag, 22. Januar 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
- assertEquals("19. Januar – 22. April 2009",
+ assertEquals("19. Januar\u2009\u2013\u200922. April 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, 0));
- assertEquals("19. Jan. – 22. Apr. 2009",
+ assertEquals("19. Jan.\u2009\u2013\u200922. Apr. 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Mo., 19. Jan. – Mi., 22. Apr. 2009",
+ assertEquals("Mo., 19. Jan.\u2009\u2013\u2009Mi., 22. Apr. 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
assertEquals("Januar–April 2009",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
- assertEquals("19. Jan. 2009 – 9. Feb. 2012",
+ assertEquals("19. Jan. 2009\u2009\u2013\u20099. Feb. 2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("Jan. 2009 – Feb. 2012",
+ assertEquals("Jan. 2009\u2009\u2013\u2009Feb. 2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
- assertEquals("19. Januar 2009 – 9. Februar 2012",
+ assertEquals("19. Januar 2009\u2009\u2013\u20099. Februar 2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, 0));
- assertEquals("Montag, 19. Januar 2009 – Donnerstag, 9. Februar 2012",
+ assertEquals("Montag, 19. Januar 2009\u2009\u2013\u2009Donnerstag, 9. Februar 2012",
formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
// The same tests but for es_US.
@@ -254,32 +255,32 @@ public class DateIntervalFormatTest {
assertEquals("19–22 de ene de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("lun, 19 de ene – jue, 22 de ene de 2009",
+ assertEquals("lun, 19 de ene\u2009\u2013\u2009jue, 22 de ene de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("lunes, 19 de enero – jueves, 22 de enero de 2009",
+ assertEquals("lunes, 19 de enero\u2009\u2013\u2009jueves, 22 de enero de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
- assertEquals("19 de enero – 22 de abril de 2009",
+ assertEquals("19 de enero\u2009\u2013\u200922 de abril de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, 0));
- assertEquals("19 de ene – 22 de abr 2009",
+ assertEquals("19 de ene\u2009\u2013\u200922 de abr 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("lun, 19 de ene – mié, 22 de abr de 2009",
+ assertEquals("lun, 19 de ene\u2009\u2013\u2009mié, 22 de abr de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
assertEquals("enero–abril de 2009",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
- assertEquals("19 de ene de 2009 – 9 de feb de 2012",
+ assertEquals("19 de ene de 2009\u2009\u2013\u20099 de feb de 2012",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("ene de 2009 – feb de 2012",
+ assertEquals("ene de 2009\u2009\u2013\u2009feb de 2012",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
- assertEquals("19 de enero de 2009 – 9 de febrero de 2012",
+ assertEquals("19 de enero de 2009\u2009\u2013\u20099 de febrero de 2012",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, 0));
- assertEquals("lunes, 19 de enero de 2009 – jueves, 9 de febrero de 2012",
+ assertEquals("lunes, 19 de enero de 2009\u2009\u2013\u2009jueves, 9 de febrero de 2012",
formatDateRange(es_US, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
// The same tests but for es_ES.
@@ -288,32 +289,32 @@ public class DateIntervalFormatTest {
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, 0));
assertEquals("19–22 ene 2009", formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("lun, 19 ene – jue, 22 ene 2009",
+ assertEquals("lun, 19 ene\u2009\u2013\u2009jue, 22 ene 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
- assertEquals("lunes, 19 de enero – jueves, 22 de enero de 2009",
+ assertEquals("lunes, 19 de enero\u2009\u2013\u2009jueves, 22 de enero de 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY));
- assertEquals("19 de enero – 22 de abril de 2009",
+ assertEquals("19 de enero\u2009\u2013\u200922 de abril de 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, 0));
- assertEquals("19 ene – 22 abr 2009",
+ assertEquals("19 ene\u2009\u2013\u200922 abr 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("lun, 19 ene – mié, 22 abr 2009",
+ assertEquals("lun, 19 ene\u2009\u2013\u2009mié, 22 abr 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH,
FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL));
assertEquals("enero–abril de 2009",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_NO_MONTH_DAY));
- assertEquals("19 ene 2009 – 9 feb 2012",
+ assertEquals("19 ene 2009\u2009\u2013\u20099 feb 2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL));
- assertEquals("ene 2009 – feb 2012",
+ assertEquals("ene 2009\u2009\u2013\u2009feb 2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR,
FORMAT_NO_MONTH_DAY | FORMAT_ABBREV_ALL));
- assertEquals("19 de enero de 2009 – 9 de febrero de 2012",
+ assertEquals("19 de enero de 2009\u2009\u2013\u20099 de febrero de 2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, 0));
- assertEquals("lunes, 19 de enero de 2009 – jueves, 9 de febrero de 2012",
+ assertEquals("lunes, 19 de enero de 2009\u2009\u2013\u2009jueves, 9 de febrero de 2012",
formatDateRange(es_ES, tz, fixedTime, fixedTime + 3 * YEAR, FORMAT_SHOW_WEEKDAY));
}
@@ -330,7 +331,7 @@ public class DateIntervalFormatTest {
c.set(2046, Calendar.OCTOBER, 4, 3, 30);
long oct_4_2046 = c.getTimeInMillis();
int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL;
- assertEquals("Jan 19, 2042 – Oct 4, 2046",
+ assertEquals("Jan 19, 2042\u2009\u2013\u2009Oct 4, 2046",
formatDateRange(l, tz, jan_19_2042, oct_4_2046, flags));
}
@@ -343,15 +344,15 @@ public class DateIntervalFormatTest {
int flags = FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL | FORMAT_SHOW_TIME | FORMAT_24HOUR;
// The Unix epoch is UTC, so 0 is 1970-01-01T00:00Z...
- assertEquals("Jan 1, 1970, 00:00 – Jan 2, 1970, 00:00",
+ assertEquals("Jan 1, 1970, 00:00\u2009\u2013\u2009Jan 2, 1970, 00:00",
formatDateRange(l, utc, 0, DAY + 1, flags));
// But MTV is hours behind, so 0 was still the afternoon of the previous day...
- assertEquals("Dec 31, 1969, 16:00 – Jan 1, 1970, 16:00",
+ assertEquals("Dec 31, 1969, 16:00\u2009\u2013\u2009Jan 1, 1970, 16:00",
formatDateRange(l, pacific, 0, DAY, flags));
}
// http://b/10318326 - we can drop the minutes in a 12-hour time if they're zero,
- // but not if we're using the 24-hour clock. That is: "4 PM" is reasonable, "16" is not.
+ // but not if we're using the 24-hour clock. That is: "4\u202fPM" is reasonable, "16" is not.
@Test
public void test10318326() throws Exception {
long midnight = 0;
@@ -367,23 +368,26 @@ public class DateIntervalFormatTest {
// Full length on-the-hour times.
assertEquals("00:00", formatDateRange(l, utc, midnight, midnight, time24));
- assertEquals("12:00 AM", formatDateRange(l, utc, midnight, midnight, time12));
+ assertEquals("12:00\u202fAM", formatDateRange(l, utc, midnight, midnight, time12));
assertEquals("16:00", formatDateRange(l, utc, teaTime, teaTime, time24));
- assertEquals("4:00 PM", formatDateRange(l, utc, teaTime, teaTime, time12));
+ assertEquals("4:00\u202fPM", formatDateRange(l, utc, teaTime, teaTime, time12));
// Abbreviated on-the-hour times.
assertEquals("00:00", formatDateRange(l, utc, midnight, midnight, abbr24));
- assertEquals("12 AM", formatDateRange(l, utc, midnight, midnight, abbr12));
+ assertEquals("12\u202fAM", formatDateRange(l, utc, midnight, midnight, abbr12));
assertEquals("16:00", formatDateRange(l, utc, teaTime, teaTime, abbr24));
- assertEquals("4 PM", formatDateRange(l, utc, teaTime, teaTime, abbr12));
+ assertEquals("4\u202fPM", formatDateRange(l, utc, teaTime, teaTime, abbr12));
// Abbreviated on-the-hour ranges.
- assertEquals("00:00 – 16:00", formatDateRange(l, utc, midnight, teaTime, abbr24));
- assertEquals("12 AM – 4 PM", formatDateRange(l, utc, midnight, teaTime, abbr12));
+ assertEquals("00:00\u2009\u2013\u200916:00", formatDateRange(l, utc, midnight, teaTime,
+ abbr24));
+ assertEquals("12\u202fAM\u2009\u2013\u20094\u202fPM", formatDateRange(l, utc, midnight,
+ teaTime, abbr12));
// Abbreviated mixed ranges.
- assertEquals("00:00 – 16:01", formatDateRange(l, utc, midnight, teaTime + MINUTE, abbr24));
- assertEquals("12:00 AM – 4:01 PM",
+ assertEquals("00:00\u2009\u2013\u200916:01", formatDateRange(l, utc, midnight,
+ teaTime + MINUTE, abbr24));
+ assertEquals("12:00\u202fAM\u2009\u2013\u20094:01\u202fPM",
formatDateRange(l, utc, midnight, teaTime + MINUTE, abbr12));
}
@@ -406,12 +410,12 @@ public class DateIntervalFormatTest {
// Run one millisecond over, though, and you're into the next day.
long nextMorning = 1 * DAY + 1;
- assertEquals("Thursday, January 1 – Friday, January 2, 1970",
+ assertEquals("Thursday, January 1\u2009\u2013\u2009Friday, January 2, 1970",
formatDateRange(l, utc, midnight, nextMorning, flags));
// But the same reasoning applies for that day.
long nextMidnight = 2 * DAY;
- assertEquals("Thursday, January 1 – Friday, January 2, 1970",
+ assertEquals("Thursday, January 1\u2009\u2013\u2009Friday, January 2, 1970",
formatDateRange(l, utc, midnight, nextMidnight, flags));
}
@@ -424,9 +428,9 @@ public class DateIntervalFormatTest {
int flags = FORMAT_SHOW_TIME | FORMAT_24HOUR | FORMAT_SHOW_DATE;
- assertEquals("January 1, 1970, 22:00 – 00:00",
+ assertEquals("January 1, 1970, 22:00\u2009\u2013\u200900:00",
formatDateRange(l, utc, 22 * HOUR, 24 * HOUR, flags));
- assertEquals("January 1, 1970 at 22:00 – January 2, 1970 at 00:30",
+ assertEquals("January 1, 1970 at 22:00\u2009\u2013\u2009January 2, 1970 at 00:30",
formatDateRange(l, utc, 22 * HOUR, 24 * HOUR + 30 * MINUTE, flags));
}
@@ -443,9 +447,9 @@ public class DateIntervalFormatTest {
c.clear();
c.set(1980, Calendar.JANUARY, 1, 0, 0);
long jan_1_1980 = c.getTimeInMillis();
- assertEquals("January 1, 1980, 22:00 – 00:00",
+ assertEquals("January 1, 1980, 22:00\u2009\u2013\u200900:00",
formatDateRange(l, utc, jan_1_1980 + 22 * HOUR, jan_1_1980 + 24 * HOUR, flags));
- assertEquals("January 1, 1980 at 22:00 – January 2, 1980 at 00:30",
+ assertEquals("January 1, 1980 at 22:00\u2009\u2013\u2009January 2, 1980 at 00:30",
formatDateRange(l, utc, jan_1_1980 + 22 * HOUR,
jan_1_1980 + 24 * HOUR + 30 * MINUTE, flags));
}
@@ -463,12 +467,12 @@ public class DateIntervalFormatTest {
c.clear();
c.set(1980, Calendar.JANUARY, 1, 0, 0);
long jan_1_1980 = c.getTimeInMillis();
- assertEquals("January 1, 1980, 22:00 – 00:00",
+ assertEquals("January 1, 1980, 22:00\u2009\u2013\u200900:00",
formatDateRange(l, pacific, jan_1_1980 + 22 * HOUR, jan_1_1980 + 24 * HOUR, flags));
c.set(1980, Calendar.JULY, 1, 0, 0);
long jul_1_1980 = c.getTimeInMillis();
- assertEquals("July 1, 1980, 22:00 – 00:00",
+ assertEquals("July 1, 1980, 22:00\u2009\u2013\u200900:00",
formatDateRange(l, pacific, jul_1_1980 + 22 * HOUR, jul_1_1980 + 24 * HOUR, flags));
}
@@ -531,11 +535,13 @@ public class DateIntervalFormatTest {
formatDateRange(l, utc, oldYear, oldYear, FORMAT_SHOW_DATE | FORMAT_NO_YEAR));
// ...or the start and end years aren't the same...
- assertEquals(String.format("February 10, 1980 – February 10, %d", c.get(Calendar.YEAR)),
+ assertEquals(String.format("February 10, 1980\u2009\u2013\u2009February 10, %d",
+ c.get(Calendar.YEAR)),
formatDateRange(l, utc, oldYear, thisYear, FORMAT_SHOW_DATE));
// (And you can't avoid that --- icu4c steps in and overrides you.)
- assertEquals(String.format("February 10, 1980 – February 10, %d", c.get(Calendar.YEAR)),
+ assertEquals(String.format("February 10, 1980\u2009\u2013\u2009February 10, %d",
+ c.get(Calendar.YEAR)),
formatDateRange(l, utc, oldYear, thisYear, FORMAT_SHOW_DATE | FORMAT_NO_YEAR));
}
@@ -595,7 +601,7 @@ public class DateIntervalFormatTest {
formatDateRange(new ULocale("fa"), utc, thisYear, thisYear, flags));
assertEquals("يونۍ د ۱۹۸۰ د فبروري ۱۰",
formatDateRange(new ULocale("ps"), utc, thisYear, thisYear, flags));
- assertEquals("วันอาทิตย์ที่ 10 กุมภาพันธ์ ค.ศ. 1980",
+ assertEquals("วันอาทิตย์ที่ 10 กุมภาพันธ์ 1980",
formatDateRange(new ULocale("th"), utc, thisYear, thisYear, flags));
}
@@ -607,9 +613,12 @@ public class DateIntervalFormatTest {
int flags = FORMAT_SHOW_TIME | FORMAT_ABBREV_ALL | FORMAT_12HOUR;
- assertEquals("10 – 11 AM", formatDateRange(l, utc, 10 * HOUR, 11 * HOUR, flags));
- assertEquals("11 AM – 1 PM", formatDateRange(l, utc, 11 * HOUR, 13 * HOUR, flags));
- assertEquals("2 – 3 PM", formatDateRange(l, utc, 14 * HOUR, 15 * HOUR, flags));
+ assertEquals("10\u2009\u2013\u200911\u202fAM", formatDateRange(l, utc,
+ 10 * HOUR, 11 * HOUR, flags));
+ assertEquals("11\u202fAM\u2009\u2013\u20091\u202fPM", formatDateRange(l, utc,
+ 11 * HOUR, 13 * HOUR, flags));
+ assertEquals("2\u2009\u2013\u20093\u202fPM", formatDateRange(l, utc,
+ 14 * HOUR, 15 * HOUR, flags));
}
// http://b/20708022
@@ -618,8 +627,8 @@ public class DateIntervalFormatTest {
final ULocale locale = new ULocale("en");
final TimeZone timeZone = TimeZone.getTimeZone("UTC");
- assertEquals("11:00 PM – 12:00 AM", formatDateRange(locale, timeZone,
- 1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
+ assertEquals("11:00\u202fPM\u2009\u2013\u200912:00\u202fAM", formatDateRange(locale,
+ timeZone, 1430434800000L, 1430438400000L, FORMAT_SHOW_TIME));
}
// http://b/68847519
@@ -629,23 +638,25 @@ public class DateIntervalFormatTest {
ENGLISH, GMT_ZONE, from, to, FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_24HOUR);
// If we're showing times and the end-point is midnight the following day, we want the
// behaviour of suppressing the date for the end...
- assertEquals("February 27, 2007, 04:00 – 00:00", fmt.apply(1172548800000L, 1172620800000L));
+ assertEquals("February 27, 2007, 04:00\u2009\u2013\u200900:00", fmt.apply(1172548800000L,
+ 1172620800000L));
// ...unless the start-point is also midnight, in which case we need dates to disambiguate.
- assertEquals("February 27, 2007 at 00:00 – February 28, 2007 at 00:00",
+ assertEquals("February 27, 2007 at 00:00\u2009\u2013\u2009February 28, 2007 at 00:00",
fmt.apply(1172534400000L, 1172620800000L));
// We want to show the date if the end-point is a millisecond after midnight the following
// day, or if it is exactly midnight the day after that.
- assertEquals("February 27, 2007 at 04:00 – February 28, 2007 at 00:00",
+ assertEquals("February 27, 2007 at 04:00\u2009\u2013\u2009February 28, 2007 at 00:00",
fmt.apply(1172548800000L, 1172620800001L));
- assertEquals("February 27, 2007 at 04:00 – March 1, 2007 at 00:00",
+ assertEquals("February 27, 2007 at 04:00\u2009\u2013\u2009March 1, 2007 at 00:00",
fmt.apply(1172548800000L, 1172707200000L));
// We want to show the date if the start-point is anything less than a minute after
// midnight,
// since that gets displayed as midnight...
- assertEquals("February 27, 2007 at 00:00 – February 28, 2007 at 00:00",
+ assertEquals("February 27, 2007 at 00:00\u2009\u2013\u2009February 28, 2007 at 00:00",
fmt.apply(1172534459999L, 1172620800000L));
// ...but not if it is exactly one minute after midnight.
- assertEquals("February 27, 2007, 00:01 – 00:00", fmt.apply(1172534460000L, 1172620800000L));
+ assertEquals("February 27, 2007, 00:01\u2009\u2013\u200900:00", fmt.apply(1172534460000L,
+ 1172620800000L));
}
// http://b/68847519
@@ -656,16 +667,20 @@ public class DateIntervalFormatTest {
// If we're only showing dates and the end-point is midnight of any day, we want the
// behaviour of showing an end date one earlier. So if the end-point is March 2, 2007 00:00,
// show March 1, 2007 instead (whether the start-point is midnight or not).
- assertEquals("February 27 – March 1, 2007", fmt.apply(1172534400000L, 1172793600000L));
- assertEquals("February 27 – March 1, 2007", fmt.apply(1172548800000L, 1172793600000L));
+ assertEquals("February 27\u2009\u2013\u2009March 1, 2007",
+ fmt.apply(1172534400000L, 1172793600000L));
+ assertEquals("February 27\u2009\u2013\u2009March 1, 2007",
+ fmt.apply(1172548800000L, 1172793600000L));
// We want to show the true date if the end-point is a millisecond after midnight.
- assertEquals("February 27 – March 2, 2007", fmt.apply(1172534400000L, 1172793600001L));
+ assertEquals("February 27\u2009\u2013\u2009March 2, 2007",
+ fmt.apply(1172534400000L, 1172793600001L));
// 2006-02-27 00:00:00.000 GMT - 2007-03-02 00:00:00.000 GMT
- assertEquals("February 27, 2006 – March 1, 2007",
+ assertEquals("February 27, 2006\u2009\u2013\u2009March 1, 2007",
fmt.apply(1140998400000L, 1172793600000L));
// Spans a leap year's Feb 29th.
- assertEquals("February 27 – March 1, 2004", fmt.apply(1077840000000L, 1078185600000L));
+ assertEquals("February 27\u2009\u2013\u2009March 1, 2004",
+ fmt.apply(1077840000000L, 1078185600000L));
}
}
diff --git a/core/tests/coretests/src/android/text/format/DateUtilsTest.java b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
index 381c0512c532..39ed82ef40f3 100644
--- a/core/tests/coretests/src/android/text/format/DateUtilsTest.java
+++ b/core/tests/coretests/src/android/text/format/DateUtilsTest.java
@@ -139,16 +139,16 @@ public class DateUtilsTest {
fixedTime, java.text.DateFormat.SHORT, java.text.DateFormat.FULL));
final long hourDuration = 2 * 60 * 60 * 1000;
- assertEquals("5:30:15 AM Greenwich Mean Time", DateUtils.formatSameDayTime(
+ assertEquals("5:30:15\u202fAM Greenwich Mean Time", DateUtils.formatSameDayTime(
fixedTime + hourDuration, fixedTime, java.text.DateFormat.FULL,
java.text.DateFormat.FULL));
- assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.DEFAULT));
- assertEquals("5:30:15 AM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30:15\u202fAM GMT", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.LONG));
- assertEquals("5:30:15 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30:15\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.MEDIUM));
- assertEquals("5:30 AM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
+ assertEquals("5:30\u202fAM", DateUtils.formatSameDayTime(fixedTime + hourDuration,
fixedTime, java.text.DateFormat.FULL, java.text.DateFormat.SHORT));
}
diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
index b3425162f48f..2337802db71f 100644
--- a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
+++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java
@@ -468,37 +468,37 @@ public class RelativeDateTimeFormatterTest {
cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0);
final long base = cal.getTimeInMillis();
- assertEquals("5 seconds ago, 10:49 AM",
+ assertEquals("5 seconds ago, 10:49\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * SECOND_IN_MILLIS, base, 0,
MINUTE_IN_MILLIS, 0));
- assertEquals("5 min. ago, 10:45 AM",
+ assertEquals("5 min. ago, 10:45\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, 0,
HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
- assertEquals("0 hr. ago, 10:45 AM",
+ assertEquals("0 hr. ago, 10:45\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base,
HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
- assertEquals("5 hours ago, 5:50 AM",
+ assertEquals("5 hours ago, 5:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base,
HOUR_IN_MILLIS, DAY_IN_MILLIS, 0));
- assertEquals("Yesterday, 7:50 PM",
+ assertEquals("Yesterday, 7:50\u202fPM",
getRelativeDateTimeString(en_US, tz, base - 15 * HOUR_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE));
- assertEquals("5 days ago, 10:50 AM",
+ assertEquals("5 days ago, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 5 * DAY_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
- assertEquals("Jan 29, 10:50 AM",
+ assertEquals("Jan 29, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 7 * DAY_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
- assertEquals("11/27/2014, 10:50 AM",
+ assertEquals("11/27/2014, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
- assertEquals("11/27/2014, 10:50 AM",
+ assertEquals("11/27/2014, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
YEAR_IN_MILLIS, 0));
// User-supplied flags should be ignored when formatting the date clause.
final int FORMAT_SHOW_WEEKDAY = 0x00002;
- assertEquals("11/27/2014, 10:50 AM",
+ assertEquals("11/27/2014, 10:50\u202fAM",
getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0,
WEEK_IN_MILLIS,
FORMAT_ABBREV_ALL | FORMAT_SHOW_WEEKDAY));
@@ -514,14 +514,14 @@ public class RelativeDateTimeFormatterTest {
// So 5 hours before 3:15 AM should be formatted as 'Yesterday, 9:15 PM'.
cal.set(2014, Calendar.MARCH, 9, 3, 15, 0);
long base = cal.getTimeInMillis();
- assertEquals("Yesterday, 9:15 PM",
+ assertEquals("Yesterday, 9:15\u202fPM",
getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
// 1 hour after 2:00 AM should be formatted as 'In 1 hour, 4:00 AM'.
cal.set(2014, Calendar.MARCH, 9, 2, 0, 0);
base = cal.getTimeInMillis();
- assertEquals("In 1 hour, 4:00 AM",
+ assertEquals("In 1 hour, 4:00\u202fAM",
getRelativeDateTimeString(en_US, tz, base + 1 * HOUR_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
@@ -529,22 +529,22 @@ public class RelativeDateTimeFormatterTest {
// 1:00 AM. 8 hours before 5:20 AM should be 'Yesterday, 10:20 PM'.
cal.set(2014, Calendar.NOVEMBER, 2, 5, 20, 0);
base = cal.getTimeInMillis();
- assertEquals("Yesterday, 10:20 PM",
+ assertEquals("Yesterday, 10:20\u202fPM",
getRelativeDateTimeString(en_US, tz, base - 8 * HOUR_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
cal.set(2014, Calendar.NOVEMBER, 2, 0, 45, 0);
base = cal.getTimeInMillis();
// 45 minutes after 0:45 AM should be 'In 45 minutes, 1:30 AM'.
- assertEquals("In 45 minutes, 1:30 AM",
+ assertEquals("In 45 minutes, 1:30\u202fAM",
getRelativeDateTimeString(en_US, tz, base + 45 * MINUTE_IN_MILLIS, base, 0,
WEEK_IN_MILLIS, 0));
// 45 minutes later, it should be 'In 45 minutes, 1:15 AM'.
- assertEquals("In 45 minutes, 1:15 AM",
+ assertEquals("In 45 minutes, 1:15\u202fAM",
getRelativeDateTimeString(en_US, tz, base + 90 * MINUTE_IN_MILLIS,
base + 45 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
// Another 45 minutes later, it should be 'In 45 minutes, 2:00 AM'.
- assertEquals("In 45 minutes, 2:00 AM",
+ assertEquals("In 45 minutes, 2:00\u202fAM",
getRelativeDateTimeString(en_US, tz, base + 135 * MINUTE_IN_MILLIS,
base + 90 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0));
}
@@ -593,7 +593,7 @@ public class RelativeDateTimeFormatterTest {
Calendar yesterdayCalendar1 = Calendar.getInstance(tz, en_US);
yesterdayCalendar1.set(2011, Calendar.SEPTEMBER, 1, 10, 24, 0);
long yesterday1 = yesterdayCalendar1.getTimeInMillis();
- assertEquals("Yesterday, 10:24 AM",
+ assertEquals("Yesterday, 10:24\u202fAM",
getRelativeDateTimeString(en_US, tz, yesterday1, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -601,7 +601,7 @@ public class RelativeDateTimeFormatterTest {
Calendar yesterdayCalendar2 = Calendar.getInstance(tz, en_US);
yesterdayCalendar2.set(2011, Calendar.SEPTEMBER, 1, 10, 22, 0);
long yesterday2 = yesterdayCalendar2.getTimeInMillis();
- assertEquals("Yesterday, 10:22 AM",
+ assertEquals("Yesterday, 10:22\u202fAM",
getRelativeDateTimeString(en_US, tz, yesterday2, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -609,7 +609,7 @@ public class RelativeDateTimeFormatterTest {
Calendar twoDaysAgoCalendar1 = Calendar.getInstance(tz, en_US);
twoDaysAgoCalendar1.set(2011, Calendar.AUGUST, 31, 10, 24, 0);
long twoDaysAgo1 = twoDaysAgoCalendar1.getTimeInMillis();
- assertEquals("2 days ago, 10:24 AM",
+ assertEquals("2 days ago, 10:24\u202fAM",
getRelativeDateTimeString(en_US, tz, twoDaysAgo1, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -617,7 +617,7 @@ public class RelativeDateTimeFormatterTest {
Calendar twoDaysAgoCalendar2 = Calendar.getInstance(tz, en_US);
twoDaysAgoCalendar2.set(2011, Calendar.AUGUST, 31, 10, 22, 0);
long twoDaysAgo2 = twoDaysAgoCalendar2.getTimeInMillis();
- assertEquals("2 days ago, 10:22 AM",
+ assertEquals("2 days ago, 10:22\u202fAM",
getRelativeDateTimeString(en_US, tz, twoDaysAgo2, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -625,7 +625,7 @@ public class RelativeDateTimeFormatterTest {
Calendar tomorrowCalendar1 = Calendar.getInstance(tz, en_US);
tomorrowCalendar1.set(2011, Calendar.SEPTEMBER, 3, 10, 22, 0);
long tomorrow1 = tomorrowCalendar1.getTimeInMillis();
- assertEquals("Tomorrow, 10:22 AM",
+ assertEquals("Tomorrow, 10:22\u202fAM",
getRelativeDateTimeString(en_US, tz, tomorrow1, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -633,7 +633,7 @@ public class RelativeDateTimeFormatterTest {
Calendar tomorrowCalendar2 = Calendar.getInstance(tz, en_US);
tomorrowCalendar2.set(2011, Calendar.SEPTEMBER, 3, 10, 24, 0);
long tomorrow2 = tomorrowCalendar2.getTimeInMillis();
- assertEquals("Tomorrow, 10:24 AM",
+ assertEquals("Tomorrow, 10:24\u202fAM",
getRelativeDateTimeString(en_US, tz, tomorrow2, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -641,7 +641,7 @@ public class RelativeDateTimeFormatterTest {
Calendar twoDaysLaterCalendar1 = Calendar.getInstance(tz, en_US);
twoDaysLaterCalendar1.set(2011, Calendar.SEPTEMBER, 4, 10, 22, 0);
long twoDaysLater1 = twoDaysLaterCalendar1.getTimeInMillis();
- assertEquals("In 2 days, 10:22 AM",
+ assertEquals("In 2 days, 10:22\u202fAM",
getRelativeDateTimeString(en_US, tz, twoDaysLater1, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
@@ -649,7 +649,7 @@ public class RelativeDateTimeFormatterTest {
Calendar twoDaysLaterCalendar2 = Calendar.getInstance(tz, en_US);
twoDaysLaterCalendar2.set(2011, Calendar.SEPTEMBER, 4, 10, 24, 0);
long twoDaysLater2 = twoDaysLaterCalendar2.getTimeInMillis();
- assertEquals("In 2 days, 10:24 AM",
+ assertEquals("In 2 days, 10:24\u202fAM",
getRelativeDateTimeString(en_US, tz, twoDaysLater2, now, MINUTE_IN_MILLIS,
WEEK_IN_MILLIS, 0));
}
@@ -664,11 +664,11 @@ public class RelativeDateTimeFormatterTest {
cal.set(2012, Calendar.FEBRUARY, 5, 10, 50, 0);
long base = cal.getTimeInMillis();
- assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("Feb 5, 5:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0));
- assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("Jan 29, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
- assertEquals("11/27/2011, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("11/27/2011, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
@@ -687,11 +687,11 @@ public class RelativeDateTimeFormatterTest {
// Feb 5, 2018 at 10:50 PST
cal.set(2018, Calendar.FEBRUARY, 5, 10, 50, 0);
base = cal.getTimeInMillis();
- assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("Feb 5, 5:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0));
- assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("Jan 29, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
- assertEquals("11/27/2017, 10:50 AM", getRelativeDateTimeString(en_US, tz,
+ assertEquals("11/27/2017, 10:50\u202fAM", getRelativeDateTimeString(en_US, tz,
base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0));
assertEquals("January 6", getRelativeTimeSpanString(en_US, tz,
diff --git a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
index b34554cd113d..c3d40eb09237 100644
--- a/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ProcLocksReaderTest.java
@@ -20,6 +20,7 @@ import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.os.FileUtils;
+import android.util.IntArray;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -33,13 +34,15 @@ import org.junit.runner.RunWith;
import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
+import java.util.Arrays;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ProcLocksReaderTest implements
ProcLocksReader.ProcLocksReaderCallback {
private File mProcDirectory;
- private ArrayList<Integer> mPids = new ArrayList<>();
+
+ private ArrayList<int[]> mPids = new ArrayList<>();
@Before
public void setUp() {
@@ -54,41 +57,51 @@ public class ProcLocksReaderTest implements
@Test
public void testRunSimpleLocks() throws Exception {
- String simpleLocks =
- "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
- "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n";
+ String simpleLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n"
+ + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n";
runHandleBlockingFileLocks(simpleLocks);
assertTrue(mPids.isEmpty());
}
@Test
public void testRunBlockingLocks() throws Exception {
- String blockedLocks =
- "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
- "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" +
- "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" +
- "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" +
- "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" +
- "4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n";
+ String blockedLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n"
+ + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n"
+ + "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n"
+ + "4: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n";
+ runHandleBlockingFileLocks(blockedLocks);
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
+ assertTrue(mPids.isEmpty());
+ }
+
+ @Test
+ public void testRunLastBlockingLocks() throws Exception {
+ String blockedLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n"
+ + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n";
runHandleBlockingFileLocks(blockedLocks);
- assertTrue(mPids.remove(0).equals(18292));
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
assertTrue(mPids.isEmpty());
}
@Test
public void testRunMultipleBlockingLocks() throws Exception {
- String blockedLocks =
- "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n" +
- "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n" +
- "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n" +
- "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n" +
- "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n" +
- "4: FLOCK ADVISORY WRITE 3840 fe:01:5111809 0 EOF\n" +
- "4: -> FLOCK ADVISORY WRITE 3841 fe:01:5111809 0 EOF\n" +
- "5: POSIX ADVISORY READ 3888 fd:09:14230 1073741826 1073742335\n";
+ String blockedLocks = "1: POSIX ADVISORY READ 18403 fd:09:9070 1073741826 1073742335\n"
+ + "2: POSIX ADVISORY WRITE 18292 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18291 fd:09:34062 0 EOF\n"
+ + "2: -> POSIX ADVISORY WRITE 18293 fd:09:34062 0 EOF\n"
+ + "3: POSIX ADVISORY READ 3888 fd:09:13992 128 128\n"
+ + "4: FLOCK ADVISORY WRITE 3840 fe:01:5111809 0 EOF\n"
+ + "4: -> FLOCK ADVISORY WRITE 3841 fe:01:5111809 0 EOF\n"
+ + "5: FLOCK ADVISORY READ 3888 fd:09:14230 0 EOF\n"
+ + "5: -> FLOCK ADVISORY READ 3887 fd:09:14230 0 EOF\n";
runHandleBlockingFileLocks(blockedLocks);
- assertTrue(mPids.remove(0).equals(18292));
- assertTrue(mPids.remove(0).equals(3840));
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{18292, 18291, 18293}));
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{3840, 3841}));
+ assertTrue(Arrays.equals(mPids.remove(0), new int[]{3888, 3887}));
assertTrue(mPids.isEmpty());
}
@@ -102,11 +115,12 @@ public class ProcLocksReaderTest implements
/**
* Call the callback function of handleBlockingFileLocks().
- *
- * @param pid Each process that hold file locks blocking other processes.
+ * @param pids Each process that hold file locks blocking other processes.
+ * pids[0] is the process blocking others
+ * pids[1..n-1] are the processes being blocked
*/
@Override
- public void onBlockingFileLock(int pid) {
- mPids.add(pid);
+ public void onBlockingFileLock(IntArray pids) {
+ mPids.add(pids.toArray());
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
new file mode 100644
index 000000000000..c540a150bf35
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/SafeZipPathValidatorCallbackTest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertThrows;
+
+import android.compat.testing.PlatformCompatChangeRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Test SafeZipPathCallback.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SafeZipPathValidatorCallbackTest {
+ @Rule
+ public TestRule mCompatChangeRule = new PlatformCompatChangeRule();
+
+ @Before
+ public void setUp() {
+ RuntimeInit.initZipPathValidatorCallback();
+ }
+
+ @Test
+ @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void testNewZipFile_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
+ throws Exception {
+ final String[] dangerousEntryNames = {
+ "../foo.bar",
+ "foo/../bar.baz",
+ "foo/../../bar.baz",
+ "foo.bar/..",
+ "foo.bar/../",
+ "..",
+ "../",
+ "/foo",
+ };
+ for (String entryName : dangerousEntryNames) {
+ final File tempFile = File.createTempFile("smdc", "zip");
+ try {
+ writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+
+ assertThrows(
+ "ZipException expected for entry: " + entryName,
+ ZipException.class,
+ () -> {
+ new ZipFile(tempFile);
+ });
+ } finally {
+ tempFile.delete();
+ }
+ }
+ }
+
+ @Test
+ @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void
+ testZipInputStreamGetNextEntry_whenZipFileHasDangerousEntriesAndChangeEnabled_throws()
+ throws Exception {
+ final String[] dangerousEntryNames = {
+ "../foo.bar",
+ "foo/../bar.baz",
+ "foo/../../bar.baz",
+ "foo.bar/..",
+ "foo.bar/../",
+ "..",
+ "../",
+ "/foo",
+ };
+ for (String entryName : dangerousEntryNames) {
+ byte[] badZipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+ try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(badZipBytes))) {
+ assertThrows(
+ "ZipException expected for entry: " + entryName,
+ ZipException.class,
+ () -> {
+ zis.getNextEntry();
+ });
+ }
+ }
+ }
+
+ @Test
+ @EnableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void testNewZipFile_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
+ throws Exception {
+ final String[] normalEntryNames = {
+ "foo", "foo.bar", "foo..bar",
+ };
+ for (String entryName : normalEntryNames) {
+ final File tempFile = File.createTempFile("smdc", "zip");
+ try {
+ writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+ try {
+ new ZipFile((tempFile));
+ } catch (ZipException e) {
+ throw new AssertionError("ZipException not expected for entry: " + entryName);
+ }
+ } finally {
+ tempFile.delete();
+ }
+ }
+ }
+
+ @Test
+ @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void
+ testZipInputStreamGetNextEntry_whenZipFileHasNormalEntriesAndChangeEnabled_doesNotThrow()
+ throws Exception {
+ final String[] normalEntryNames = {
+ "foo", "foo.bar", "foo..bar",
+ };
+ for (String entryName : normalEntryNames) {
+ byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+ try {
+ ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
+ zis.getNextEntry();
+ } catch (ZipException e) {
+ throw new AssertionError("ZipException not expected for entry: " + entryName);
+ }
+ }
+ }
+
+ @Test
+ @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void
+ testNewZipFile_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
+ throws Exception {
+ final String[] entryNames = {
+ "../foo.bar",
+ "foo/../bar.baz",
+ "foo/../../bar.baz",
+ "foo.bar/..",
+ "foo.bar/../",
+ "..",
+ "../",
+ "/foo",
+ "foo",
+ "foo.bar",
+ "foo..bar",
+ };
+ for (String entryName : entryNames) {
+ final File tempFile = File.createTempFile("smdc", "zip");
+ try {
+ writeZipFileOutputStreamWithEmptyEntry(tempFile, entryName);
+ try {
+ new ZipFile((tempFile));
+ } catch (ZipException e) {
+ throw new AssertionError("ZipException not expected for entry: " + entryName);
+ }
+ } finally {
+ tempFile.delete();
+ }
+ }
+ }
+
+ @Test
+ @DisableCompatChanges({SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL})
+ public void
+ testZipInputStreamGetNextEntry_whenZipFileHasNormalAndDangerousEntriesAndChangeDisabled_doesNotThrow()
+ throws Exception {
+ final String[] entryNames = {
+ "../foo.bar",
+ "foo/../bar.baz",
+ "foo/../../bar.baz",
+ "foo.bar/..",
+ "foo.bar/../",
+ "..",
+ "../",
+ "/foo",
+ "foo",
+ "foo.bar",
+ "foo..bar",
+ };
+ for (String entryName : entryNames) {
+ byte[] zipBytes = getZipBytesFromZipOutputStreamWithEmptyEntry(entryName);
+ try {
+ ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zipBytes));
+ zis.getNextEntry();
+ } catch (ZipException e) {
+ throw new AssertionError("ZipException not expected for entry: " + entryName);
+ }
+ }
+ }
+
+ private void writeZipFileOutputStreamWithEmptyEntry(File tempFile, String entryName)
+ throws IOException {
+ FileOutputStream tempFileStream = new FileOutputStream(tempFile);
+ writeZipOutputStreamWithEmptyEntry(tempFileStream, entryName);
+ tempFileStream.close();
+ }
+
+ private byte[] getZipBytesFromZipOutputStreamWithEmptyEntry(String entryName)
+ throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ writeZipOutputStreamWithEmptyEntry(bos, entryName);
+ return bos.toByteArray();
+ }
+
+ private void writeZipOutputStreamWithEmptyEntry(OutputStream os, String entryName)
+ throws IOException {
+ ZipOutputStream zos = new ZipOutputStream(os);
+ ZipEntry entry = new ZipEntry(entryName);
+ zos.putNextEntry(entry);
+ zos.write(new byte[2]);
+ zos.closeEntry();
+ zos.close();
+ }
+}
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
index 34659897e588..2da9a2ebbdb6 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/FabricatedOverlaysTest.java
@@ -18,7 +18,6 @@ package com.android.overlaytest;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertThrows;
@@ -45,7 +44,6 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.Collections;
-import java.util.List;
import java.util.concurrent.TimeoutException;
@RunWith(JUnit4.class)
@@ -221,11 +219,56 @@ public class FabricatedOverlaysTest {
}
@Test
+ public void setResourceValue_withNullResourceName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(NullPointerException.class,
+ () -> builder.setResourceValue(null, TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
+ public void setResourceValue_withEmptyResourceName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setResourceValue("", TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
+ public void setResourceValue_withEmptyPackageName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setResourceValue(":color/mycolor", TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
+ public void setResourceValue_withInvalidTypeName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setResourceValue("c/mycolor", TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
+ public void setResourceValue_withEmptyTypeName() throws Exception {
+ final FabricatedOverlay.Builder builder = new FabricatedOverlay.Builder(
+ "android", TEST_OVERLAY_NAME, mContext.getPackageName());
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.setResourceValue("/mycolor", TypedValue.TYPE_INT_DEC, 1));
+ }
+
+ @Test
public void testInvalidResourceValues() throws Exception {
final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
"android", TEST_OVERLAY_NAME, mContext.getPackageName())
.setResourceValue(TEST_RESOURCE, TypedValue.TYPE_INT_DEC, 1)
- .setResourceValue("something", TypedValue.TYPE_INT_DEC, 1)
+ .setResourceValue("color/something", TypedValue.TYPE_INT_DEC, 1)
.build();
waitForResourceValue(0);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 74303e2fab7c..9d841ea2e55d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -18,6 +18,12 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
+import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishPrimaryWithSecondary;
+import static androidx.window.extensions.embedding.SplitContainer.shouldFinishSecondaryWithPrimary;
+
import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
@@ -140,6 +146,8 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
// Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, launchingFragmentToken, secondaryFragmentToken, rule);
+ setCompanionTaskFragment(wct, launchingFragmentToken, secondaryFragmentToken, rule,
+ false /* isStacked */);
}
/**
@@ -215,6 +223,28 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
}
+ void setCompanionTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder primary, @NonNull IBinder secondary, @NonNull SplitRule splitRule,
+ boolean isStacked) {
+ final boolean finishPrimaryWithSecondary;
+ if (isStacked) {
+ finishPrimaryWithSecondary = shouldFinishAssociatedContainerWhenStacked(
+ getFinishPrimaryWithSecondaryBehavior(splitRule));
+ } else {
+ finishPrimaryWithSecondary = shouldFinishPrimaryWithSecondary(splitRule);
+ }
+ wct.setCompanionTaskFragment(primary, finishPrimaryWithSecondary ? secondary : null);
+
+ final boolean finishSecondaryWithPrimary;
+ if (isStacked) {
+ finishSecondaryWithPrimary = shouldFinishAssociatedContainerWhenStacked(
+ getFinishSecondaryWithPrimaryBehavior(splitRule));
+ } else {
+ finishSecondaryWithPrimary = shouldFinishSecondaryWithPrimary(splitRule);
+ }
+ wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
+ }
+
TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
if (mFragmentInfos.containsKey(fragmentToken)) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 16760e26b3f1..d52caaf4c3e8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -389,6 +389,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// launching activity in the Task.
mTransactionManager.getCurrentTransactionRecord().setOriginType(TRANSIT_CLOSE);
mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
+ } else if (taskFragmentInfo.isClearedForReorderActivityToFront()) {
+ // Do not finish the dependents if this TaskFragment was cleared to reorder
+ // the launching Activity to front of the Task.
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (!container.isWaitingActivityAppear()) {
// Do not finish the container before the expected activity appear until
// timeout.
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 362f1fa096cc..cb470bac5c9a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -371,13 +371,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull SplitAttributes splitAttributes) {
// Clear adjacent TaskFragments if the container is shown in fullscreen, or the
// secondaryContainer could not be finished.
- if (!shouldShowSplit(splitAttributes)) {
+ boolean isStacked = !shouldShowSplit(splitAttributes);
+ if (isStacked) {
setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
null /* secondary */, null /* splitRule */);
} else {
setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
secondaryContainer.getTaskFragmentToken(), splitRule);
}
+ setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(),
+ secondaryContainer.getTaskFragmentToken(), splitRule, isStacked);
}
/**
@@ -489,8 +492,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|| splitContainer.getSecondaryContainer().getInfo() == null) {
return RESULT_EXPAND_FAILED_NO_TF_INFO;
}
- expandTaskFragment(wct, splitContainer.getPrimaryContainer().getTaskFragmentToken());
- expandTaskFragment(wct, splitContainer.getSecondaryContainer().getTaskFragmentToken());
+ final IBinder primaryToken =
+ splitContainer.getPrimaryContainer().getTaskFragmentToken();
+ final IBinder secondaryToken =
+ splitContainer.getSecondaryContainer().getTaskFragmentToken();
+ expandTaskFragment(wct, primaryToken);
+ expandTaskFragment(wct, secondaryToken);
+ // Set the companion TaskFragment when the two containers stacked.
+ setCompanionTaskFragment(wct, primaryToken, secondaryToken,
+ splitContainer.getSplitRule(), true /* isStacked */);
return RESULT_EXPANDED;
}
return RESULT_NOT_EXPANDED;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index b516e1407b11..2192b5ca219c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -36,6 +36,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
import android.window.WindowContext;
+import android.window.WindowProvider;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -71,7 +72,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
- private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
+ private final Map<IBinder, ConfigurationChangeListener> mConfigurationChangeListeners =
new ArrayMap<>();
public WindowLayoutComponentImpl(@NonNull Context context) {
@@ -121,21 +122,21 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
if (!context.isUiContext()) {
throw new IllegalArgumentException("Context must be a UI Context, which should be"
- + " an Activity or a WindowContext");
+ + " an Activity, WindowContext or InputMethodService");
}
mFoldingFeatureProducer.getData((features) -> {
- // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
consumer.accept(newWindowLayout);
});
mWindowLayoutChangeListeners.put(context, consumer);
- if (context instanceof WindowContext) {
+ // TODO(b/258065175) Further extend this to ContextWrappers.
+ if (context instanceof WindowProvider) {
final IBinder windowContextToken = context.getWindowContextToken();
- final WindowContextConfigListener listener =
- new WindowContextConfigListener(windowContextToken);
+ final ConfigurationChangeListener listener =
+ new ConfigurationChangeListener(windowContextToken);
context.registerComponentCallbacks(listener);
- mWindowContextConfigListeners.put(windowContextToken, listener);
+ mConfigurationChangeListeners.put(windowContextToken, listener);
}
}
@@ -150,10 +151,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) {
continue;
}
- if (context instanceof WindowContext) {
+ if (context instanceof WindowProvider) {
final IBinder token = context.getWindowContextToken();
- context.unregisterComponentCallbacks(mWindowContextConfigListeners.get(token));
- mWindowContextConfigListeners.remove(token);
+ context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token));
+ mConfigurationChangeListeners.remove(token);
}
break;
}
@@ -349,10 +350,10 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
}
- private final class WindowContextConfigListener implements ComponentCallbacks {
+ private final class ConfigurationChangeListener implements ComponentCallbacks {
final IBinder mToken;
- WindowContextConfigListener(IBinder token) {
+ ConfigurationChangeListener(IBinder token) {
mToken = token;
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index 40f7a273980a..92011af27619 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -169,6 +169,7 @@ public class EmbeddingTestUtils {
new Point(),
false /* isTaskClearedForReuse */,
false /* isTaskFragmentClearedForPip */,
+ false /* isClearedForReorderActivityToFront */,
new Point());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 957a24873998..79813c7d064e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -144,6 +144,6 @@ public class JetpackTaskFragmentOrganizerTest {
mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
false /* isVisible */, new ArrayList<>(), new Point(),
false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */,
- new Point());
+ false /* isClearedForReorderActivityToFront */, new Point());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 07cd7d6f60b7..f811940fd304 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -116,7 +116,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private final TouchTracker mTouchTracker = new TouchTracker();
private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
-
+ @Nullable
private IOnBackInvokedCallback mActiveCallback;
@VisibleForTesting
@@ -180,6 +180,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private void initBackAnimationRunners() {
+ if (!IS_U_ANIMATION_ENABLED) {
+ return;
+ }
+
final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
@@ -207,7 +211,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private void updateEnableAnimationFromSetting() {
int settingValue = Global.getInt(mContext.getContentResolver(),
Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF);
- boolean isEnabled = settingValue == SETTING_VALUE_ON && IS_U_ANIMATION_ENABLED;
+ boolean isEnabled = settingValue == SETTING_VALUE_ON;
mEnableAnimations.set(isEnabled);
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
}
@@ -350,10 +354,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
final int backType = backNavigationInfo.getType();
- final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
+ final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
if (shouldDispatchToAnimator) {
- mActiveCallback = mAnimationDefinition.get(backType).getCallback();
- mAnimationDefinition.get(backType).startGesture();
+ if (mAnimationDefinition.contains(backType)) {
+ mActiveCallback = mAnimationDefinition.get(backType).getCallback();
+ mAnimationDefinition.get(backType).startGesture();
+ } else {
+ mActiveCallback = null;
+ }
} else {
mActiveCallback = mBackNavigationInfo.getOnBackInvokedCallback();
dispatchOnBackStarted(mActiveCallback, mTouchTracker.createStartEvent(null));
@@ -361,9 +369,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private void onMove() {
- if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()) {
+ if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()
+ || mActiveCallback == null) {
return;
}
+
final BackEvent backEvent = mTouchTracker.createProgressEvent();
dispatchOnBackProgressed(mActiveCallback, backEvent);
}
@@ -387,11 +397,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
}
- private boolean shouldDispatchToAnimator(int backType) {
+ private boolean shouldDispatchToAnimator() {
return mEnableAnimations.get()
&& mBackNavigationInfo != null
- && mBackNavigationInfo.isPrepareRemoteAnimation()
- && mAnimationDefinition.contains(backType);
+ && mBackNavigationInfo.isPrepareRemoteAnimation();
}
private void dispatchOnBackStarted(IOnBackInvokedCallback callback,
@@ -461,6 +470,30 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mTouchTracker.setProgressThreshold(progressThreshold);
}
+ private void invokeOrCancelBack() {
+ // Make a synchronized call to core before dispatch back event to client side.
+ // If the close transition happens before the core receives onAnimationFinished, there will
+ // play a second close animation for that transition.
+ if (mBackAnimationFinishedCallback != null) {
+ try {
+ mBackAnimationFinishedCallback.onAnimationFinished(mTriggerBack);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e);
+ }
+ mBackAnimationFinishedCallback = null;
+ }
+
+ if (mBackNavigationInfo != null) {
+ final IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
+ if (mTriggerBack) {
+ dispatchOnBackInvoked(callback);
+ } else {
+ dispatchOnBackCancelled(callback);
+ }
+ }
+ finishBackNavigation();
+ }
+
/**
* Called when the gesture is released, then it could start the post commit animation.
*/
@@ -493,15 +526,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
final int backType = mBackNavigationInfo.getType();
- // Directly finish back navigation if no animator defined.
- if (!shouldDispatchToAnimator(backType)) {
- if (mTriggerBack) {
- dispatchOnBackInvoked(mActiveCallback);
- } else {
- dispatchOnBackCancelled(mActiveCallback);
- }
- // Animation missing. Simply finish back navigation.
- finishBackNavigation();
+ // Simply trigger and finish back navigation when no animator defined.
+ if (!shouldDispatchToAnimator() || mActiveCallback == null) {
+ invokeOrCancelBack();
return;
}
@@ -549,16 +576,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
// Trigger the real back.
- if (mBackNavigationInfo != null) {
- IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
- if (mTriggerBack) {
- dispatchOnBackInvoked(callback);
- } else {
- dispatchOnBackCancelled(callback);
- }
- }
-
- finishBackNavigation();
+ invokeOrCancelBack();
}
/**
@@ -567,25 +585,14 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@VisibleForTesting
void finishBackNavigation() {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
- BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
- boolean triggerBack = mTriggerBack;
- mBackNavigationInfo = null;
- mTriggerBack = false;
mShouldStartOnNextMoveEvent = false;
mTouchTracker.reset();
mActiveCallback = null;
- if (backNavigationInfo == null) {
- return;
- }
- if (mBackAnimationFinishedCallback != null) {
- try {
- mBackAnimationFinishedCallback.onAnimationFinished(triggerBack);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e);
- }
- mBackAnimationFinishedCallback = null;
+ if (mBackNavigationInfo != null) {
+ mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
+ mBackNavigationInfo = null;
}
- backNavigationInfo.onBackNavigationFinished(triggerBack);
+ mTriggerBack = false;
}
private void createAdapter() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 5b7ed278e843..6e116b958ac9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -163,7 +163,8 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
- Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY) {
+ Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
+ boolean immediately) {
if (mResizingIconView == null) {
return;
}
@@ -178,8 +179,8 @@ public class SplitDecorManager extends WindowlessWindowManager {
final boolean show =
newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height();
- final boolean animate = show != mShown;
- if (animate && mFadeAnimator != null && mFadeAnimator.isRunning()) {
+ final boolean update = show != mShown;
+ if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) {
// If we need to animate and animator still running, cancel it before we ensure both
// background and icon surfaces are non null for next animation.
mFadeAnimator.cancel();
@@ -192,7 +193,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
.setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
}
- if (mGapBackgroundLeash == null) {
+ if (mGapBackgroundLeash == null && !immediately) {
final boolean isLandscape = newBounds.height() == sideBounds.height();
final int left = isLandscape ? mBounds.width() : 0;
final int top = isLandscape ? 0 : mBounds.height();
@@ -221,8 +222,13 @@ public class SplitDecorManager extends WindowlessWindowManager {
newBounds.width() / 2 - mIconSize / 2,
newBounds.height() / 2 - mIconSize / 2);
- if (animate) {
- startFadeAnimation(show, null /* finishedConsumer */);
+ if (update) {
+ if (immediately) {
+ t.setVisibility(mBackgroundLeash, show);
+ t.setVisibility(mIconLeash, show);
+ } else {
+ startFadeAnimation(show, null /* finishedConsumer */);
+ }
mShown = show;
}
}
@@ -319,10 +325,12 @@ public class SplitDecorManager extends WindowlessWindowManager {
@Override
public void onAnimationStart(@NonNull Animator animation) {
if (show) {
- animT.show(mBackgroundLeash).show(mIconLeash).show(mGapBackgroundLeash).apply();
- } else {
- animT.hide(mGapBackgroundLeash).apply();
+ animT.show(mBackgroundLeash).show(mIconLeash);
+ }
+ if (mGapBackgroundLeash != null) {
+ animT.setVisibility(mGapBackgroundLeash, show);
}
+ animT.apply();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 3de1045bfbda..ec9e6f7573bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -83,8 +83,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private static final int FLING_RESIZE_DURATION = 250;
private static final int FLING_SWITCH_DURATION = 350;
- private static final int FLING_ENTER_DURATION = 350;
- private static final int FLING_EXIT_DURATION = 350;
+ private static final int FLING_ENTER_DURATION = 450;
+ private static final int FLING_EXIT_DURATION = 450;
private int mDividerWindowWidth;
private int mDividerInsets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 5533ad56d17c..56aa742b8626 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -95,7 +95,7 @@ interface ISplitScreen {
*/
oneway void startShortcutAndTask(in ShortcutInfo shortcutInfo, in Bundle options1, int taskId,
in Bundle options2, int splitPosition, float splitRatio,
- in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
+ in RemoteTransition remoteTransition, in InstanceId instanceId) = 17;
/**
* Version of startTasks using legacy transition system.
@@ -119,6 +119,21 @@ interface ISplitScreen {
in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 15;
/**
+ * Start a pair of intents using legacy transition system.
+ */
+ oneway void startIntentsWithLegacyTransition(in PendingIntent pendingIntent1,
+ in Bundle options1, in PendingIntent pendingIntent2, in Bundle options2,
+ int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
+ in InstanceId instanceId) = 18;
+
+ /**
+ * Start a pair of intents in one transition.
+ */
+ oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1,
+ in PendingIntent pendingIntent2, in Bundle options2, int splitPosition,
+ float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19;
+
+ /**
* Blocking call that notifies and gets additional split-screen targets when entering
* recents (for example: the dividerBar).
* @param appTargets apps that will be re-parented to display area
@@ -132,4 +147,4 @@ interface ISplitScreen {
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
}
-// Last id = 17
+// Last id = 19 \ No newline at end of file
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 cdc8cdd2c28d..1774dd5e3b6f 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
@@ -548,6 +548,24 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
options2, splitPosition, splitRatio, remoteTransition, instanceId);
}
+ private void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ Intent fillInIntent1 = null;
+ Intent fillInIntent2 = null;
+ if (launchSameComponentAdjacently(pendingIntent1, pendingIntent2)
+ && supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
+ fillInIntent1 = new Intent();
+ fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ fillInIntent2 = new Intent();
+ fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
+ pendingIntent2, fillInIntent2, options2, splitPosition, splitRatio, adapter,
+ instanceId);
+ }
+
@Override
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
@@ -621,6 +639,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return Objects.equals(launchingActivity, pairedActivity);
}
+ private boolean launchSameComponentAdjacently(PendingIntent pendingIntent1,
+ PendingIntent pendingIntent2) {
+ return Objects.equals(pendingIntent1.getIntent().getComponent(),
+ pendingIntent2.getIntent().getComponent());
+ }
+
@VisibleForTesting
/** Returns {@code true} if the component supports multi-instances split. */
boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
@@ -986,6 +1010,27 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
+ public void startIntentsWithLegacyTransition(PendingIntent pendingIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ executeRemoteCallWithTaskPermission(mController, "startIntentsWithLegacyTransition",
+ (controller) ->
+ controller.startIntentsWithLegacyTransition(
+ pendingIntent1, options1, pendingIntent2, options2, splitPosition,
+ splitRatio, adapter, instanceId)
+ );
+ }
+
+ @Override
+ public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1,
+ PendingIntent pendingIntent2, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio,
+ @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ // TODO(b/259368992): To be implemented.
+ }
+
+ @Override
public void startShortcut(String packageName, String shortcutId, int position,
@Nullable Bundle options, UserHandle user, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcut",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c2ab7ef7e7bf..acb71a80ee8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -169,6 +169,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private ValueAnimator mDividerFadeInAnimator;
private boolean mDividerVisible;
private boolean mKeyguardShowing;
+ private boolean mShowDecorImmediately;
private final SyncTransactionQueue mSyncQueue;
private final ShellTaskOrganizer mTaskOrganizer;
private final Context mContext;
@@ -588,8 +589,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Starts a pair of tasks using legacy transition. */
void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1,
int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition,
- float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
addActivityOptions(options1, mSideStage);
@@ -599,7 +599,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
instanceId);
}
- /** Starts a pair of intent and task using legacy transition. */
+ /** Starts a pair of intents using legacy transition. */
+ void startIntentsWithLegacyTransition(PendingIntent pendingIntent1, Intent fillInIntent1,
+ @Nullable Bundle options1, PendingIntent pendingIntent2, Intent fillInIntent2,
+ @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (options1 == null) options1 = new Bundle();
+ addActivityOptions(options1, mSideStage);
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+
+ startWithLegacyTransition(wct, pendingIntent2, fillInIntent2, options2, splitPosition,
+ splitRatio, adapter, instanceId);
+ }
+
void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, Intent fillInIntent,
@Nullable Bundle options1, int taskId, @Nullable Bundle options2,
@SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
@@ -627,12 +640,29 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
instanceId);
}
+ private void startWithLegacyTransition(WindowContainerTransaction wct,
+ @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
+ @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ startWithLegacyTransition(wct, INVALID_TASK_ID, mainPendingIntent, mainFillInIntent,
+ mainOptions, sidePosition, splitRatio, adapter, instanceId);
+ }
+
+ private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
+ @Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ startWithLegacyTransition(wct, mainTaskId, null /* mainPendingIntent */,
+ null /* mainFillInIntent */, mainOptions, sidePosition, splitRatio, adapter,
+ instanceId);
+ }
+
/**
* @param wct transaction to start the first task
* @param instanceId if {@code null}, will not log. Otherwise it will be used in
* {@link SplitscreenEventLogger#logEnter(float, int, int, int, int, boolean)}
*/
private void startWithLegacyTransition(WindowContainerTransaction wct, int mainTaskId,
+ @Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable Bundle mainOptions, @SplitPosition int sidePosition, float splitRatio,
RemoteAnimationAdapter adapter, InstanceId instanceId) {
// Init divider first to make divider leash for remote animation target.
@@ -701,7 +731,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mainOptions == null) mainOptions = new Bundle();
addActivityOptions(mainOptions, mMainStage);
updateWindowBounds(mSplitLayout, wct);
- wct.startTask(mainTaskId, mainOptions);
+ if (mainTaskId == INVALID_TASK_ID) {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, mainOptions);
+ } else {
+ wct.startTask(mainTaskId, mainOptions);
+ }
wct.reorder(mRootTaskInfo.token, true);
wct.setForceTranslucent(mRootTaskInfo.token, false);
@@ -1556,6 +1590,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mLogger.isEnterRequestedByDrag()) {
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
} else {
+ mShowDecorImmediately = true;
mSplitLayout.flingDividerToCenter();
}
});
@@ -1631,14 +1666,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
updateSurfaceBounds(layout, t, true /* applyResizingOffset */);
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
- mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY);
- mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY);
+ mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
+ mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
t.apply();
mTransactionPool.release(t);
}
@Override
public void onLayoutSizeChanged(SplitLayout layout) {
+ // Reset this flag every time onLayoutSizeChanged.
+ mShowDecorImmediately = false;
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct);
sendOnBoundsChanged();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index acad5d93eab4..bcf900b99c69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -289,10 +289,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
- int offsetY) {
+ int offsetY, boolean immediately) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
- offsetY);
+ offsetY, immediately);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 48c0cea150cc..d3f1332f6224 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -317,7 +317,7 @@ class DragResizeInputListener implements AutoCloseable {
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
- if (mDragging) {
+ if (mShouldHandleEvents && mDragging) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
mCallback.onDragResizeEnd(
e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index b603e0355e98..d75c36c99a4c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -38,6 +38,7 @@ import android.app.WindowConfiguration;
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
@@ -56,6 +57,7 @@ import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.IOnBackInvokedCallback;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -189,31 +191,23 @@ public class BackAnimationControllerTest extends ShellTestCase {
}
for (int type: testTypes) {
- boolean[] backNavigationDone = new boolean[]{false};
- boolean[] triggerBack = new boolean[]{false};
-
+ final ResultListener result = new ResultListener();
createNavigationInfo(new BackNavigationInfo.Builder()
.setType(type)
.setOnBackInvokedCallback(mAppCallback)
.setPrepareRemoteAnimation(true)
- .setOnBackNavigationDone(
- new RemoteCallback(result -> {
- backNavigationDone[0] = true;
- triggerBack[0] = result.getBoolean(KEY_TRIGGER_BACK);
- })));
+ .setOnBackNavigationDone(new RemoteCallback(result)));
triggerBackGesture();
simulateRemoteAnimationStart(type);
simulateRemoteAnimationFinished();
mShellExecutor.flushAll();
assertTrue("Navigation Done callback not called for "
- + BackNavigationInfo.typeToString(type), backNavigationDone[0]);
- assertTrue("TriggerBack should have been true", triggerBack[0]);
+ + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+ assertTrue("TriggerBack should have been true", result.mTriggerBack);
}
}
-
-
@Test
public void backToHome_dispatchesEvents() throws RemoteException {
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
@@ -351,6 +345,65 @@ public class BackAnimationControllerTest extends ShellTestCase {
verify(mAnimatorCallback, never()).onBackInvoked();
}
+ @Test
+ public void animationNotDefined() throws RemoteException {
+ final int[] testTypes = new int[] {
+ BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ BackNavigationInfo.TYPE_CROSS_TASK,
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ BackNavigationInfo.TYPE_DIALOG_CLOSE};
+
+ for (int type: testTypes) {
+ final ResultListener result = new ResultListener();
+ createNavigationInfo(new BackNavigationInfo.Builder()
+ .setType(type)
+ .setOnBackInvokedCallback(mAppCallback)
+ .setPrepareRemoteAnimation(true)
+ .setOnBackNavigationDone(new RemoteCallback(result)));
+ triggerBackGesture();
+ simulateRemoteAnimationStart(type);
+ mShellExecutor.flushAll();
+
+ assertTrue("Navigation Done callback not called for "
+ + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+ assertTrue("TriggerBack should have been true", result.mTriggerBack);
+ }
+
+ verify(mAppCallback, never()).onBackStarted(any());
+ verify(mAppCallback, never()).onBackProgressed(any());
+ verify(mAppCallback, times(testTypes.length)).onBackInvoked();
+
+ verify(mAnimatorCallback, never()).onBackStarted(any());
+ verify(mAnimatorCallback, never()).onBackProgressed(any());
+ verify(mAnimatorCallback, never()).onBackInvoked();
+ }
+
+ @Test
+ public void callbackShouldDeliverProgress() throws RemoteException {
+ registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+
+ final int type = BackNavigationInfo.TYPE_CALLBACK;
+ final ResultListener result = new ResultListener();
+ createNavigationInfo(new BackNavigationInfo.Builder()
+ .setType(type)
+ .setOnBackInvokedCallback(mAppCallback)
+ .setOnBackNavigationDone(new RemoteCallback(result)));
+ triggerBackGesture();
+ mShellExecutor.flushAll();
+
+ assertTrue("Navigation Done callback not called for "
+ + BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
+ assertTrue("TriggerBack should have been true", result.mTriggerBack);
+
+ verify(mAppCallback, times(1)).onBackStarted(any());
+ verify(mAppCallback, times(1)).onBackProgressed(any());
+ verify(mAppCallback, times(1)).onBackInvoked();
+
+ verify(mAnimatorCallback, never()).onBackStarted(any());
+ verify(mAnimatorCallback, never()).onBackProgressed(any());
+ verify(mAnimatorCallback, never()).onBackInvoked();
+ }
+
private void doMotionEvent(int actionDown, int coordinate) {
mController.onMotionEvent(
coordinate, coordinate,
@@ -377,4 +430,14 @@ public class BackAnimationControllerTest extends ShellTestCase {
mController.registerAnimation(type,
new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
}
+
+ private static class ResultListener implements RemoteCallback.OnResultListener {
+ boolean mBackNavigationDone = false;
+ boolean mTriggerBack = false;
+ @Override
+ public void onResult(@Nullable Bundle result) {
+ mBackNavigationDone = true;
+ mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK);
+ }
+ };
}
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 1381bdd6a50d..06ffb72d183b 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -43,28 +43,19 @@ namespace {
using EntryValue = std::variant<Res_value, incfs::verified_map_ptr<ResTable_map_entry>>;
+/* NOTE: table_entry has been verified in LoadedPackage::GetEntryFromOffset(),
+ * and so access to ->value() and ->map_entry() are safe here
+ */
base::expected<EntryValue, IOError> GetEntryValue(
incfs::verified_map_ptr<ResTable_entry> table_entry) {
- const uint16_t entry_size = dtohs(table_entry->size);
+ const uint16_t entry_size = table_entry->size();
// Check if the entry represents a bag value.
- if (entry_size >= sizeof(ResTable_map_entry) &&
- (dtohs(table_entry->flags) & ResTable_entry::FLAG_COMPLEX)) {
- const auto map_entry = table_entry.convert<ResTable_map_entry>();
- if (!map_entry) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
- return map_entry.verified();
+ if (entry_size >= sizeof(ResTable_map_entry) && table_entry->is_complex()) {
+ return table_entry.convert<ResTable_map_entry>().verified();
}
- // The entry represents a non-bag value.
- const auto entry_value = table_entry.offset(entry_size).convert<Res_value>();
- if (!entry_value) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
- Res_value value;
- value.copyFrom_dtoh(entry_value.value());
- return value;
+ return table_entry->value();
}
} // namespace
@@ -814,17 +805,12 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
return base::unexpected(std::nullopt);
}
- auto best_entry_result = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
- if (!best_entry_result.has_value()) {
- return base::unexpected(best_entry_result.error());
- }
-
- const incfs::map_ptr<ResTable_entry> best_entry = *best_entry_result;
- if (!best_entry) {
- return base::unexpected(IOError::PAGES_MISSING);
+ auto best_entry_verified = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
+ if (!best_entry_verified.has_value()) {
+ return base::unexpected(best_entry_verified.error());
}
- const auto entry = GetEntryValue(best_entry.verified());
+ const auto entry = GetEntryValue(*best_entry_verified);
if (!entry.has_value()) {
return base::unexpected(entry.error());
}
@@ -837,7 +823,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
.package_name = &best_package->GetPackageName(),
.type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
.entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(),
- best_entry->key.index),
+ (*best_entry_verified)->key()),
.dynamic_ref_table = package_group.dynamic_ref_table.get(),
};
}
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 5b69cca2d747..386f718208b3 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -88,7 +88,9 @@ static bool VerifyResTableType(incfs::map_ptr<ResTable_type> header) {
// Make sure that there is enough room for the entry offsets.
const size_t offsets_offset = dtohs(header->header.headerSize);
const size_t entries_offset = dtohl(header->entriesStart);
- const size_t offsets_length = sizeof(uint32_t) * entry_count;
+ const size_t offsets_length = header->flags & ResTable_type::FLAG_OFFSET16
+ ? sizeof(uint16_t) * entry_count
+ : sizeof(uint32_t) * entry_count;
if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) {
LOG(ERROR) << "RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data.";
@@ -107,8 +109,8 @@ static bool VerifyResTableType(incfs::map_ptr<ResTable_type> header) {
return true;
}
-static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
- incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
+static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+VerifyResTableEntry(incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
// Check that the offset is aligned.
if (UNLIKELY(entry_offset & 0x03U)) {
LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned.";
@@ -136,7 +138,7 @@ static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
return base::unexpected(IOError::PAGES_MISSING);
}
- const size_t entry_size = dtohs(entry->size);
+ const size_t entry_size = entry->size();
if (UNLIKELY(entry_size < sizeof(entry.value()))) {
LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
<< " is too small.";
@@ -149,6 +151,11 @@ static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
return base::unexpected(std::nullopt);
}
+ // If entry is compact, value is already encoded, and a compact entry
+ // cannot be a map_entry, we are done verifying
+ if (entry->is_compact())
+ return entry.verified();
+
if (entry_size < sizeof(ResTable_map_entry)) {
// There needs to be room for one Res_value struct.
if (UNLIKELY(entry_offset + entry_size > chunk_size - sizeof(Res_value))) {
@@ -192,7 +199,7 @@ static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
return base::unexpected(std::nullopt);
}
}
- return {};
+ return entry.verified();
}
LoadedPackage::iterator::iterator(const LoadedPackage* lp, size_t ti, size_t ei)
@@ -228,7 +235,7 @@ uint32_t LoadedPackage::iterator::operator*() const {
entryIndex_);
}
-base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
+base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index) {
base::expected<uint32_t, NullOrIOError> entry_offset = GetEntryOffset(type_chunk, entry_index);
if (UNLIKELY(!entry_offset.has_value())) {
@@ -242,14 +249,13 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::GetEntryOffset(
// The configuration matches and is better than the previous selection.
// Find the entry value if it exists for this configuration.
const size_t entry_count = dtohl(type_chunk->entryCount);
- const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
+ const auto offsets = type_chunk.offset(dtohs(type_chunk->header.headerSize));
// Check if there is the desired entry in this type.
if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
// This is encoded as a sparse map, so perform a binary search.
bool error = false;
- auto sparse_indices = type_chunk.offset(offsets_offset)
- .convert<ResTable_sparseTypeEntry>().iterator();
+ auto sparse_indices = offsets.convert<ResTable_sparseTypeEntry>().iterator();
auto sparse_indices_end = sparse_indices + entry_count;
auto result = std::lower_bound(sparse_indices, sparse_indices_end, entry_index,
[&error](const incfs::map_ptr<ResTable_sparseTypeEntry>& entry,
@@ -284,26 +290,36 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::GetEntryOffset(
return base::unexpected(std::nullopt);
}
- const auto entry_offset_ptr = type_chunk.offset(offsets_offset).convert<uint32_t>() + entry_index;
- if (UNLIKELY(!entry_offset_ptr)) {
- return base::unexpected(IOError::PAGES_MISSING);
+ uint32_t result;
+
+ if (type_chunk->flags & ResTable_type::FLAG_OFFSET16) {
+ const auto entry_offset_ptr = offsets.convert<uint16_t>() + entry_index;
+ if (UNLIKELY(!entry_offset_ptr)) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
+ result = offset_from16(entry_offset_ptr.value());
+ } else {
+ const auto entry_offset_ptr = offsets.convert<uint32_t>() + entry_index;
+ if (UNLIKELY(!entry_offset_ptr)) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
+ result = dtohl(entry_offset_ptr.value());
}
- const uint32_t value = dtohl(entry_offset_ptr.value());
- if (value == ResTable_type::NO_ENTRY) {
+ if (result == ResTable_type::NO_ENTRY) {
return base::unexpected(std::nullopt);
}
-
- return value;
+ return result;
}
-base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntryFromOffset(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset) {
+base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+LoadedPackage::GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk,
+ uint32_t offset) {
auto valid = VerifyResTableEntry(type_chunk, offset);
if (UNLIKELY(!valid.has_value())) {
return base::unexpected(valid.error());
}
- return type_chunk.offset(offset + dtohl(type_chunk->entriesStart)).convert<ResTable_entry>();
+ return valid;
}
base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations(
@@ -376,31 +392,42 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::FindEntryByName(
for (const auto& type_entry : type_spec->type_entries) {
const incfs::verified_map_ptr<ResTable_type>& type = type_entry.type;
- size_t entry_count = dtohl(type->entryCount);
- for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
- auto entry_offset_ptr = type.offset(dtohs(type->header.headerSize)).convert<uint32_t>() +
- entry_idx;
- if (!entry_offset_ptr) {
- return base::unexpected(IOError::PAGES_MISSING);
- }
+ const size_t entry_count = dtohl(type->entryCount);
+ const auto entry_offsets = type.offset(dtohs(type->header.headerSize));
+ for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
uint32_t offset;
uint16_t res_idx;
if (type->flags & ResTable_type::FLAG_SPARSE) {
- auto sparse_entry = entry_offset_ptr.convert<ResTable_sparseTypeEntry>();
+ auto sparse_entry = entry_offsets.convert<ResTable_sparseTypeEntry>() + entry_idx;
+ if (!sparse_entry) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
offset = dtohs(sparse_entry->offset) * 4u;
res_idx = dtohs(sparse_entry->idx);
+ } else if (type->flags & ResTable_type::FLAG_OFFSET16) {
+ auto entry = entry_offsets.convert<uint16_t>() + entry_idx;
+ if (!entry) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
+ offset = offset_from16(entry.value());
+ res_idx = entry_idx;
} else {
- offset = dtohl(entry_offset_ptr.value());
+ auto entry = entry_offsets.convert<uint32_t>() + entry_idx;
+ if (!entry) {
+ return base::unexpected(IOError::PAGES_MISSING);
+ }
+ offset = dtohl(entry.value());
res_idx = entry_idx;
}
+
if (offset != ResTable_type::NO_ENTRY) {
auto entry = type.offset(dtohl(type->entriesStart) + offset).convert<ResTable_entry>();
if (!entry) {
return base::unexpected(IOError::PAGES_MISSING);
}
- if (dtohl(entry->key.index) == static_cast<uint32_t>(*key_idx)) {
+ if (entry->key() == static_cast<uint32_t>(*key_idx)) {
// The package ID will be overridden by the caller (due to runtime assignment of package
// IDs for shared libraries).
return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx);
diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp
index b3fb1452919b..b68143d82090 100644
--- a/libs/androidfw/LocaleDataTables.cpp
+++ b/libs/androidfw/LocaleDataTables.cpp
@@ -143,6 +143,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
{0xACE00000u, 46u}, // ahl -> Latn
{0xB8E00000u, 1u}, // aho -> Ahom
{0x99200000u, 46u}, // ajg -> Latn
+ {0xCD200000u, 2u}, // ajt -> Arab
{0x616B0000u, 46u}, // ak -> Latn
{0xA9400000u, 101u}, // akk -> Xsux
{0x81600000u, 46u}, // ala -> Latn
@@ -1053,6 +1054,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
{0xB70D0000u, 46u}, // nyn -> Latn
{0xA32D0000u, 46u}, // nzi -> Latn
{0x6F630000u, 46u}, // oc -> Latn
+ {0x6F634553u, 46u}, // oc-ES -> Latn
{0x88CE0000u, 46u}, // ogc -> Latn
{0x6F6A0000u, 11u}, // oj -> Cans
{0xC92E0000u, 11u}, // ojs -> Cans
@@ -1093,6 +1095,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
{0xB4EF0000u, 71u}, // phn -> Phnx
{0xAD0F0000u, 46u}, // pil -> Latn
{0xBD0F0000u, 46u}, // pip -> Latn
+ {0xC90F0000u, 46u}, // pis -> Latn
{0x814F0000u, 9u}, // pka -> Brah
{0xB94F0000u, 46u}, // pko -> Latn
{0x706C0000u, 46u}, // pl -> Latn
@@ -1204,12 +1207,14 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
{0xE1720000u, 46u}, // sly -> Latn
{0x736D0000u, 46u}, // sm -> Latn
{0x81920000u, 46u}, // sma -> Latn
+ {0x8D920000u, 46u}, // smd -> Latn
{0xA5920000u, 46u}, // smj -> Latn
{0xB5920000u, 46u}, // smn -> Latn
{0xBD920000u, 76u}, // smp -> Samr
{0xC1920000u, 46u}, // smq -> Latn
{0xC9920000u, 46u}, // sms -> Latn
{0x736E0000u, 46u}, // sn -> Latn
+ {0x85B20000u, 46u}, // snb -> Latn
{0x89B20000u, 46u}, // snc -> Latn
{0xA9B20000u, 46u}, // snk -> Latn
{0xBDB20000u, 46u}, // snp -> Latn
@@ -1314,6 +1319,7 @@ const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({
{0x746F0000u, 46u}, // to -> Latn
{0x95D30000u, 46u}, // tof -> Latn
{0x99D30000u, 46u}, // tog -> Latn
+ {0xA9D30000u, 46u}, // tok -> Latn
{0xC1D30000u, 46u}, // toq -> Latn
{0xA1F30000u, 46u}, // tpi -> Latn
{0xB1F30000u, 46u}, // tpm -> Latn
@@ -1527,6 +1533,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
0x61665A414C61746ELLU, // af_Latn_ZA
0xC0C0434D4C61746ELLU, // agq_Latn_CM
0xB8E0494E41686F6DLLU, // aho_Ahom_IN
+ 0xCD20544E41726162LLU, // ajt_Arab_TN
0x616B47484C61746ELLU, // ak_Latn_GH
0xA940495158737578LLU, // akk_Xsux_IQ
0xB560584B4C61746ELLU, // aln_Latn_XK
@@ -1534,6 +1541,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
0x616D455445746869LLU, // am_Ethi_ET
0xB9804E474C61746ELLU, // amo_Latn_NG
0x616E45534C61746ELLU, // an_Latn_ES
+ 0xB5A04E474C61746ELLU, // ann_Latn_NG
0xE5C049444C61746ELLU, // aoz_Latn_ID
0x8DE0544741726162LLU, // apd_Arab_TG
0x6172454741726162LLU, // ar_Arab_EG
@@ -2039,6 +2047,7 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
0xB88F49525870656FLLU, // peo_Xpeo_IR
0xACAF44454C61746ELLU, // pfl_Latn_DE
0xB4EF4C4250686E78LLU, // phn_Phnx_LB
+ 0xC90F53424C61746ELLU, // pis_Latn_SB
0x814F494E42726168LLU, // pka_Brah_IN
0xB94F4B454C61746ELLU, // pko_Latn_KE
0x706C504C4C61746ELLU, // pl_Latn_PL
@@ -2119,11 +2128,13 @@ std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({
0xE17249444C61746ELLU, // sly_Latn_ID
0x736D57534C61746ELLU, // sm_Latn_WS
0x819253454C61746ELLU, // sma_Latn_SE
+ 0x8D92414F4C61746ELLU, // smd_Latn_AO
0xA59253454C61746ELLU, // smj_Latn_SE
0xB59246494C61746ELLU, // smn_Latn_FI
0xBD92494C53616D72LLU, // smp_Samr_IL
0xC99246494C61746ELLU, // sms_Latn_FI
0x736E5A574C61746ELLU, // sn_Latn_ZW
+ 0x85B24D594C61746ELLU, // snb_Latn_MY
0xA9B24D4C4C61746ELLU, // snk_Latn_ML
0x736F534F4C61746ELLU, // so_Latn_SO
0x99D2555A536F6764LLU, // sog_Sogd_UZ
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 5e8a623d4205..267190a54195 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -4487,20 +4487,14 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag
return err;
}
- if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) {
+ if (entry.entry->map_entry()) {
if (!mayBeBag) {
ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID);
}
return BAD_VALUE;
}
- const Res_value* value = reinterpret_cast<const Res_value*>(
- reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size);
-
- outValue->size = dtohs(value->size);
- outValue->res0 = value->res0;
- outValue->dataType = value->dataType;
- outValue->data = dtohl(value->data);
+ *outValue = entry.entry->value();
// The reference may be pointing to a resource in a shared library. These
// references have build-time generated package IDs. These ids may not match
@@ -4691,11 +4685,10 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
return err;
}
- const uint16_t entrySize = dtohs(entry.entry->size);
- const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
- ? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0;
- const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
- ? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0;
+ const uint16_t entrySize = entry.entry->size();
+ const ResTable_map_entry* map_entry = entry.entry->map_entry();
+ const uint32_t parent = map_entry ? dtohl(map_entry->parent.ident) : 0;
+ const uint32_t count = map_entry ? dtohl(map_entry->count) : 0;
size_t N = count;
@@ -4759,7 +4752,7 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
// Now merge in the new attributes...
size_t curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type))
- + dtohs(entry.entry->size);
+ + entrySize;
const ResTable_map* map;
bag_entry* entries = (bag_entry*)(set+1);
size_t curEntry = 0;
@@ -5137,7 +5130,7 @@ uint32_t ResTable::findEntry(const PackageGroup* group, ssize_t typeIndex, const
continue;
}
- if (dtohl(entry->key.index) == (size_t) *ei) {
+ if (entry->key() == (size_t) *ei) {
uint32_t resId = Res_MAKEID(group->id - 1, typeIndex, iter.index());
if (outTypeSpecFlags) {
Entry result;
@@ -6600,8 +6593,12 @@ status_t ResTable::getEntry(
// Entry does not exist.
continue;
}
-
- thisOffset = dtohl(eindex[realEntryIndex]);
+ if (thisType->flags & ResTable_type::FLAG_OFFSET16) {
+ auto eindex16 = reinterpret_cast<const uint16_t*>(eindex);
+ thisOffset = offset_from16(eindex16[realEntryIndex]);
+ } else {
+ thisOffset = dtohl(eindex[realEntryIndex]);
+ }
}
if (thisOffset == ResTable_type::NO_ENTRY) {
@@ -6651,8 +6648,8 @@ status_t ResTable::getEntry(
const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>(
reinterpret_cast<const uint8_t*>(bestType) + bestOffset);
- if (dtohs(entry->size) < sizeof(*entry)) {
- ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
+ if (entry->size() < sizeof(*entry)) {
+ ALOGW("ResTable_entry size 0x%zx is too small", entry->size());
return BAD_TYPE;
}
@@ -6663,7 +6660,7 @@ status_t ResTable::getEntry(
outEntry->specFlags = specFlags;
outEntry->package = bestPackage;
outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
- outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
+ outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, entry->key());
}
return NO_ERROR;
}
@@ -7653,6 +7650,9 @@ void ResTable::print(bool inclValues) const
if (type->flags & ResTable_type::FLAG_SPARSE) {
printf(" [sparse]");
}
+ if (type->flags & ResTable_type::FLAG_OFFSET16) {
+ printf(" [offset16]");
+ }
}
printf(":\n");
@@ -7684,7 +7684,13 @@ void ResTable::print(bool inclValues) const
thisOffset = static_cast<uint32_t>(dtohs(entry->offset)) * 4u;
} else {
entryId = entryIndex;
- thisOffset = dtohl(eindex[entryIndex]);
+ if (type->flags & ResTable_type::FLAG_OFFSET16) {
+ const auto eindex16 =
+ reinterpret_cast<const uint16_t*>(eindex);
+ thisOffset = offset_from16(eindex16[entryIndex]);
+ } else {
+ thisOffset = dtohl(eindex[entryIndex]);
+ }
if (thisOffset == ResTable_type::NO_ENTRY) {
continue;
}
@@ -7734,7 +7740,7 @@ void ResTable::print(bool inclValues) const
continue;
}
- uintptr_t esize = dtohs(ent->size);
+ uintptr_t esize = ent->size();
if ((esize&0x3) != 0) {
printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize);
continue;
@@ -7746,30 +7752,27 @@ void ResTable::print(bool inclValues) const
}
const Res_value* valuePtr = NULL;
- const ResTable_map_entry* bagPtr = NULL;
+ const ResTable_map_entry* bagPtr = ent->map_entry();
Res_value value;
- if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
+ if (bagPtr) {
printf("<bag>");
- bagPtr = (const ResTable_map_entry*)ent;
} else {
- valuePtr = (const Res_value*)
- (((const uint8_t*)ent) + esize);
- value.copyFrom_dtoh(*valuePtr);
+ value = ent->value();
printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
(int)value.dataType, (int)value.data,
(int)value.size, (int)value.res0);
}
- if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
+ if (ent->flags() & ResTable_entry::FLAG_PUBLIC) {
printf(" (PUBLIC)");
}
printf("\n");
if (inclValues) {
- if (valuePtr != NULL) {
+ if (bagPtr == NULL) {
printf(" ");
print_value(typeConfigs->package, value);
- } else if (bagPtr != NULL) {
+ } else {
const int N = dtohl(bagPtr->count);
const uint8_t* baseMapPtr = (const uint8_t*)ent;
size_t mapOffset = esize;
diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp
index 647aa197a94d..70d14a11830e 100644
--- a/libs/androidfw/TypeWrappers.cpp
+++ b/libs/androidfw/TypeWrappers.cpp
@@ -59,7 +59,9 @@ const ResTable_entry* TypeVariant::iterator::operator*() const {
+ dtohl(type->header.size);
const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize));
- if (reinterpret_cast<uintptr_t>(entryIndices) + (sizeof(uint32_t) * entryCount) > containerEnd) {
+ const size_t indexSize = type->flags & ResTable_type::FLAG_OFFSET16 ?
+ sizeof(uint16_t) : sizeof(uint32_t);
+ if (reinterpret_cast<uintptr_t>(entryIndices) + (indexSize * entryCount) > containerEnd) {
ALOGE("Type's entry indices extend beyond its boundaries");
return NULL;
}
@@ -73,6 +75,9 @@ const ResTable_entry* TypeVariant::iterator::operator*() const {
}
entryOffset = static_cast<uint32_t>(dtohs(ResTable_sparseTypeEntry{*iter}.offset)) * 4u;
+ } else if (type->flags & ResTable_type::FLAG_OFFSET16) {
+ auto entryIndices16 = reinterpret_cast<const uint16_t*>(entryIndices);
+ entryOffset = offset_from16(entryIndices16[mIndex]);
} else {
entryOffset = dtohl(entryIndices[mIndex]);
}
@@ -91,11 +96,11 @@ const ResTable_entry* TypeVariant::iterator::operator*() const {
if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) {
ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex);
return NULL;
- } else if (reinterpret_cast<uintptr_t>(entry) + dtohs(entry->size) > containerEnd) {
+ } else if (reinterpret_cast<uintptr_t>(entry) + entry->size() > containerEnd) {
ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex);
return NULL;
- } else if (dtohs(entry->size) < sizeof(*entry)) {
- ALOGE("Entry at index %u is too small (%u)", mIndex, dtohs(entry->size));
+ } else if (entry->size() < sizeof(*entry)) {
+ ALOGE("Entry at index %u is too small (%zu)", mIndex, entry->size());
return NULL;
}
return entry;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index e45963950b04..79d962829046 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -166,14 +166,14 @@ class LoadedPackage {
base::expected<uint32_t, NullOrIOError> FindEntryByName(const std::u16string& type_name,
const std::u16string& entry_name) const;
- static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntry(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
+ static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+ GetEntry(incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
static base::expected<uint32_t, NullOrIOError> GetEntryOffset(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
- static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntryFromOffset(
- incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
+ static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
+ GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
// Returns the string pool where type names are stored.
const ResStringPool* GetTypeStringPool() const {
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index a625889eaf3c..c740832522fc 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -26,6 +26,7 @@
#include <androidfw/Errors.h>
#include <androidfw/LocaleData.h>
#include <androidfw/StringPiece.h>
+#include <utils/ByteOrder.h>
#include <utils/Errors.h>
#include <utils/String16.h>
#include <utils/Vector.h>
@@ -1437,6 +1438,10 @@ struct ResTable_type
// Mark any types that use this with a v26 qualifier to prevent runtime issues on older
// platforms.
FLAG_SPARSE = 0x01,
+
+ // If set, the offsets to the entries are encoded in 16-bit, real_offset = offset * 4u
+ // An 16-bit offset of 0xffffu means a NO_ENTRY
+ FLAG_OFFSET16 = 0x02,
};
uint8_t flags;
@@ -1453,6 +1458,11 @@ struct ResTable_type
ResTable_config config;
};
+// Convert a 16-bit offset to 32-bit if FLAG_OFFSET16 is set
+static inline uint32_t offset_from16(uint16_t off16) {
+ return dtohs(off16) == 0xffffu ? ResTable_type::NO_ENTRY : dtohs(off16) * 4u;
+}
+
// The minimum size required to read any version of ResTable_type.
constexpr size_t kResTableTypeMinSize =
sizeof(ResTable_type) - sizeof(ResTable_config) + sizeof(ResTable_config::size);
@@ -1480,6 +1490,8 @@ union ResTable_sparseTypeEntry {
static_assert(sizeof(ResTable_sparseTypeEntry) == sizeof(uint32_t),
"ResTable_sparseTypeEntry must be 4 bytes in size");
+struct ResTable_map_entry;
+
/**
* This is the beginning of information about an entry in the resource
* table. It holds the reference to the name of this entry, and is
@@ -1487,12 +1499,11 @@ static_assert(sizeof(ResTable_sparseTypeEntry) == sizeof(uint32_t),
* * A Res_value structure, if FLAG_COMPLEX is -not- set.
* * An array of ResTable_map structures, if FLAG_COMPLEX is set.
* These supply a set of name/value mappings of data.
+ * * If FLAG_COMPACT is set, this entry is a compact entry for
+ * simple values only
*/
-struct ResTable_entry
+union ResTable_entry
{
- // Number of bytes in this structure.
- uint16_t size;
-
enum {
// If set, this is a complex entry, holding a set of name/value
// mappings. It is followed by an array of ResTable_map structures.
@@ -1504,18 +1515,91 @@ struct ResTable_entry
// resources of the same name/type. This is only useful during
// linking with other resource tables.
FLAG_WEAK = 0x0004,
+ // If set, this is a compact entry with data type and value directly
+ // encoded in the this entry, see ResTable_entry::compact
+ FLAG_COMPACT = 0x0008,
};
- uint16_t flags;
-
- // Reference into ResTable_package::keyStrings identifying this entry.
- struct ResStringPool_ref key;
+
+ struct Full {
+ // Number of bytes in this structure.
+ uint16_t size;
+
+ uint16_t flags;
+
+ // Reference into ResTable_package::keyStrings identifying this entry.
+ struct ResStringPool_ref key;
+ } full;
+
+ /* A compact entry is indicated by FLAG_COMPACT, with flags at the same
+ * offset as a normal entry. This is only for simple data values where
+ *
+ * - size for entry or value can be inferred (both being 8 bytes).
+ * - key index is encoded in 16-bit
+ * - dataType is encoded as the higher 8-bit of flags
+ * - data is encoded directly in this entry
+ */
+ struct Compact {
+ uint16_t key;
+ uint16_t flags;
+ uint32_t data;
+ } compact;
+
+ uint16_t flags() const { return dtohs(full.flags); };
+ bool is_compact() const { return flags() & FLAG_COMPACT; }
+ bool is_complex() const { return flags() & FLAG_COMPLEX; }
+
+ size_t size() const {
+ return is_compact() ? sizeof(ResTable_entry) : dtohs(this->full.size);
+ }
+
+ uint32_t key() const {
+ return is_compact() ? dtohs(this->compact.key) : dtohl(this->full.key.index);
+ }
+
+ /* Always verify the memory associated with this entry and its value
+ * before calling value() or map_entry()
+ */
+ Res_value value() const {
+ Res_value v;
+ if (is_compact()) {
+ v.size = sizeof(Res_value);
+ v.res0 = 0;
+ v.data = dtohl(this->compact.data);
+ v.dataType = dtohs(compact.flags) >> 8;
+ } else {
+ auto vaddr = reinterpret_cast<const uint8_t*>(this) + dtohs(this->full.size);
+ auto value = reinterpret_cast<const Res_value*>(vaddr);
+ v.size = dtohs(value->size);
+ v.res0 = value->res0;
+ v.data = dtohl(value->data);
+ v.dataType = value->dataType;
+ }
+ return v;
+ }
+
+ const ResTable_map_entry* map_entry() const {
+ return is_complex() && !is_compact() ?
+ reinterpret_cast<const ResTable_map_entry*>(this) : nullptr;
+ }
};
+/* Make sure size of ResTable_entry::Full and ResTable_entry::Compact
+ * be the same as ResTable_entry. This is to allow iteration of entries
+ * to work in either cases.
+ *
+ * The offset of flags must be at the same place for both structures,
+ * to ensure the correct reading to decide whether this is a full entry
+ * or a compact entry.
+ */
+static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Full));
+static_assert(sizeof(ResTable_entry) == sizeof(ResTable_entry::Compact));
+static_assert(offsetof(ResTable_entry, full.flags) == offsetof(ResTable_entry, compact.flags));
+
/**
* Extended form of a ResTable_entry for map entries, defining a parent map
* resource from which to inherit values.
*/
-struct ResTable_map_entry : public ResTable_entry
+struct ResTable_map_entry : public ResTable_entry::Full
{
// Resource identifier of the parent mapping, or 0 if there is none.
// This is always treated as a TYPE_DYNAMIC_REFERENCE.
diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp
index d69abe5d0f11..aba3ab3a06a2 100644
--- a/libs/androidfw/tests/TypeWrappers_test.cpp
+++ b/libs/androidfw/tests/TypeWrappers_test.cpp
@@ -37,8 +37,8 @@ void* createTypeData() {
offsets[0] = 0;
ResTable_entry e1;
memset(&e1, 0, sizeof(e1));
- e1.size = sizeof(e1);
- e1.key.index = 0;
+ e1.full.size = sizeof(e1);
+ e1.full.key.index = 0;
t.header.size += sizeof(e1);
Res_value v1;
@@ -50,8 +50,8 @@ void* createTypeData() {
offsets[2] = sizeof(e1) + sizeof(v1);
ResTable_entry e2;
memset(&e2, 0, sizeof(e2));
- e2.size = sizeof(e2);
- e2.key.index = 1;
+ e2.full.size = sizeof(e2);
+ e2.full.key.index = 1;
t.header.size += sizeof(e2);
Res_value v2;
@@ -83,7 +83,7 @@ TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) {
TypeVariant::iterator iter = v.beginEntries();
ASSERT_EQ(uint32_t(0), iter.index());
ASSERT_TRUE(NULL != *iter);
- ASSERT_EQ(uint32_t(0), iter->key.index);
+ ASSERT_EQ(uint32_t(0), iter->full.key.index);
ASSERT_NE(v.endEntries(), iter);
iter++;
@@ -96,7 +96,7 @@ TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) {
ASSERT_EQ(uint32_t(2), iter.index());
ASSERT_TRUE(NULL != *iter);
- ASSERT_EQ(uint32_t(1), iter->key.index);
+ ASSERT_EQ(uint32_t(1), iter->full.key.index);
ASSERT_NE(v.endEntries(), iter);
iter++;
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index c18edcd8689b..15049302d322 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -360,6 +360,7 @@ void LnbClientCallbackImpl::onEvent(const LnbEventType lnbEventType) {
lnb,
gFields.onLnbEventID,
(jint)lnbEventType);
+ env->DeleteLocalRef(lnb);
} else {
ALOGE("LnbClientCallbackImpl::onEvent:"
"Lnb object has been freed. Ignoring callback.");
@@ -378,6 +379,7 @@ void LnbClientCallbackImpl::onDiseqcMessage(const vector<uint8_t> &diseqcMessage
lnb,
gFields.onLnbDiseqcMessageID,
array);
+ env->DeleteLocalRef(lnb);
} else {
ALOGE("LnbClientCallbackImpl::onDiseqcMessage:"
"Lnb object has been freed. Ignoring callback.");
@@ -404,6 +406,7 @@ void DvrClientCallbackImpl::onRecordStatus(RecordStatus status) {
jobject dvr(env->NewLocalRef(mDvrObj));
if (!env->IsSameObject(dvr, nullptr)) {
env->CallVoidMethod(dvr, gFields.onDvrRecordStatusID, (jint)status);
+ env->DeleteLocalRef(dvr);
} else {
ALOGE("DvrClientCallbackImpl::onRecordStatus:"
"Dvr object has been freed. Ignoring callback.");
@@ -416,6 +419,7 @@ void DvrClientCallbackImpl::onPlaybackStatus(PlaybackStatus status) {
jobject dvr(env->NewLocalRef(mDvrObj));
if (!env->IsSameObject(dvr, nullptr)) {
env->CallVoidMethod(dvr, gFields.onDvrPlaybackStatusID, (jint)status);
+ env->DeleteLocalRef(dvr);
} else {
ALOGE("DvrClientCallbackImpl::onPlaybackStatus:"
"Dvr object has been freed. Ignoring callback.");
@@ -603,6 +607,7 @@ void FilterClientCallbackImpl::getSectionEvent(jobjectArray &arr, const int size
jobject obj = env->NewObject(eventClazz, eventInit, tableId, version, sectionNum, dataLength);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size,
@@ -673,6 +678,10 @@ void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size,
}
env->SetObjectArrayElement(arr, size, obj);
+ if(audioDescriptor != nullptr) {
+ env->DeleteLocalRef(audioDescriptor);
+ }
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
@@ -688,6 +697,7 @@ void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
jobject obj = env->NewObject(eventClazz, eventInit, streamId, dataLength, mpuSequenceNumber);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int size,
@@ -725,6 +735,7 @@ void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int siz
jobject obj =
env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber, pts, firstMbInSlice);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int size,
@@ -745,6 +756,7 @@ void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int s
jobject obj = env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber,
mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int size,
@@ -764,6 +776,7 @@ void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int siz
jobject obj = env->NewObject(eventClazz, eventInit, itemId, downloadId, mpuSequenceNumber,
itemFragmentIndex, lastItemFragmentIndex, dataLength);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getIpPayloadEvent(jobjectArray &arr, const int size,
@@ -776,6 +789,7 @@ void FilterClientCallbackImpl::getIpPayloadEvent(jobjectArray &arr, const int si
jint dataLength = ipPayloadEvent.dataLength;
jobject obj = env->NewObject(eventClazz, eventInit, dataLength);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size,
@@ -794,6 +808,8 @@ void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size,
jobject obj = env->NewObject(eventClazz, eventInit, pts, descrTag, array);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(array);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getScramblingStatusEvent(jobjectArray &arr, const int size,
@@ -807,6 +823,7 @@ void FilterClientCallbackImpl::getScramblingStatusEvent(jobjectArray &arr, const
.get<DemuxFilterMonitorEvent::Tag::scramblingStatus>();
jobject obj = env->NewObject(eventClazz, eventInit, scramblingStatus);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getIpCidChangeEvent(jobjectArray &arr, const int size,
@@ -819,6 +836,7 @@ void FilterClientCallbackImpl::getIpCidChangeEvent(jobjectArray &arr, const int
.get<DemuxFilterMonitorEvent::Tag::cid>();
jobject obj = env->NewObject(eventClazz, eventInit, cid);
env->SetObjectArrayElement(arr, size, obj);
+ env->DeleteLocalRef(obj);
}
void FilterClientCallbackImpl::getRestartEvent(jobjectArray &arr, const int size,
@@ -922,10 +940,12 @@ void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &eve
methodID = gFields.onSharedFilterEventID;
}
env->CallVoidMethod(filter, methodID, array);
+ env->DeleteLocalRef(filter);
} else {
ALOGE("FilterClientCallbackImpl::onFilterEvent:"
"Filter object has been freed. Ignoring callback.");
}
+ env->DeleteLocalRef(array);
}
void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
@@ -938,6 +958,7 @@ void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
methodID = gFields.onSharedFilterStatusID;
}
env->CallVoidMethod(filter, methodID, (jint)static_cast<uint8_t>(status));
+ env->DeleteLocalRef(filter);
} else {
ALOGE("FilterClientCallbackImpl::onFilterStatus:"
"Filter object has been freed. Ignoring callback.");
@@ -1006,6 +1027,7 @@ void FrontendClientCallbackImpl::onEvent(FrontendEventType frontendEventType) {
frontend,
gFields.onFrontendEventID,
(jint)frontendEventType);
+ env->DeleteLocalRef(frontend);
} else {
ALOGW("FrontendClientCallbackImpl::onEvent:"
"Frontend object has been freed. Ignoring callback.");
@@ -1028,6 +1050,7 @@ void FrontendClientCallbackImpl::onScanMessage(
continue;
}
executeOnScanMessage(env, clazz, frontend, type, message);
+ env->DeleteLocalRef(frontend);
}
}
@@ -1069,6 +1092,7 @@ void FrontendClientCallbackImpl::executeOnScanMessage(
env->SetLongArrayRegion(freqs, 0, v.size(), reinterpret_cast<jlong *>(&v[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onFrequenciesReport", "([J)V"),
freqs);
+ env->DeleteLocalRef(freqs);
break;
}
case FrontendScanMessageType::SYMBOL_RATE: {
@@ -1077,6 +1101,7 @@ void FrontendClientCallbackImpl::executeOnScanMessage(
env->SetIntArrayRegion(symbolRates, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onSymbolRates", "([I)V"),
symbolRates);
+ env->DeleteLocalRef(symbolRates);
break;
}
case FrontendScanMessageType::HIERARCHY: {
@@ -1094,6 +1119,7 @@ void FrontendClientCallbackImpl::executeOnScanMessage(
jintArray plpIds = env->NewIntArray(jintV.size());
env->SetIntArrayRegion(plpIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onPlpIds", "([I)V"), plpIds);
+ env->DeleteLocalRef(plpIds);
break;
}
case FrontendScanMessageType::GROUP_IDS: {
@@ -1101,6 +1127,7 @@ void FrontendClientCallbackImpl::executeOnScanMessage(
jintArray groupIds = env->NewIntArray(jintV.size());
env->SetIntArrayRegion(groupIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onGroupIds", "([I)V"), groupIds);
+ env->DeleteLocalRef(groupIds);
break;
}
case FrontendScanMessageType::INPUT_STREAM_IDS: {
@@ -1109,6 +1136,7 @@ void FrontendClientCallbackImpl::executeOnScanMessage(
env->SetIntArrayRegion(streamIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onInputStreamIds", "([I)V"),
streamIds);
+ env->DeleteLocalRef(streamIds);
break;
}
case FrontendScanMessageType::STANDARD: {
@@ -1142,12 +1170,14 @@ void FrontendClientCallbackImpl::executeOnScanMessage(
jboolean lls = info.bLlsFlag;
jobject obj = env->NewObject(plpClazz, init, plpId, lls);
env->SetObjectArrayElement(array, i, obj);
+ env->DeleteLocalRef(obj);
}
env->CallVoidMethod(frontend,
env->GetMethodID(clazz, "onAtsc3PlpInfos",
"([Landroid/media/tv/tuner/frontend/"
"Atsc3PlpInfo;)V"),
array);
+ env->DeleteLocalRef(array);
break;
}
case FrontendScanMessageType::MODULATION: {
@@ -1219,6 +1249,7 @@ void FrontendClientCallbackImpl::executeOnScanMessage(
env->SetIntArrayRegion(cellIds, 0, jintV.size(), reinterpret_cast<jint *>(&jintV[0]));
env->CallVoidMethod(frontend, env->GetMethodID(clazz, "onDvbtCellIdsReported", "([I)V"),
cellIds);
+ env->DeleteLocalRef(cellIds);
break;
}
default:
@@ -1673,6 +1704,7 @@ jobjectArray JTuner::getFrontendStatusReadiness(jintArray types) {
for (int i = 0; i < size; i++) {
jobject readinessObj = env->NewObject(clazz, init, intTypes[i], readiness[i]);
env->SetObjectArrayElement(valObj, i, readinessObj);
+ env->DeleteLocalRef(readinessObj);
}
return valObj;
}
@@ -2081,6 +2113,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isDemodLocked>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::snr: {
@@ -2088,6 +2121,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::snr>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::ber: {
@@ -2095,6 +2129,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::ber>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::per: {
@@ -2102,6 +2137,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::per>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::preBer: {
@@ -2109,6 +2145,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::preBer>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::signalQuality: {
@@ -2116,6 +2153,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newIntegerObj = env->NewObject(intClazz, initInt,
s.get<FrontendStatus::Tag::signalQuality>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::signalStrength: {
@@ -2124,6 +2162,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->NewObject(intClazz, initInt,
s.get<FrontendStatus::Tag::signalStrength>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::symbolRate: {
@@ -2131,6 +2170,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::symbolRate>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::innerFec: {
@@ -2141,6 +2181,8 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->NewObject(longClazz, initLong,
static_cast<long>(s.get<FrontendStatus::Tag::innerFec>()));
env->SetObjectField(statusObj, field, newLongObj);
+ env->DeleteLocalRef(longClazz);
+ env->DeleteLocalRef(newLongObj);
break;
}
case FrontendStatus::Tag::modulationStatus: {
@@ -2183,6 +2225,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intModulation);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2192,6 +2235,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->NewObject(intClazz, initInt,
static_cast<jint>(s.get<FrontendStatus::Tag::inversion>()));
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::lnbVoltage: {
@@ -2200,6 +2244,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->NewObject(intClazz, initInt,
static_cast<jint>(s.get<FrontendStatus::Tag::lnbVoltage>()));
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::plpId: {
@@ -2207,6 +2252,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::plpId>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::isEWBS: {
@@ -2214,6 +2260,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isEWBS>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::agc: {
@@ -2221,6 +2268,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::agc>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::isLnaOn: {
@@ -2228,6 +2276,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isLnaOn>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::isLayerError: {
@@ -2241,6 +2290,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->SetBooleanArrayRegion(valObj, i, 1, &x);
}
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::mer: {
@@ -2248,6 +2298,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::mer>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::freqOffset: {
@@ -2255,6 +2306,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newLongObj = env->NewObject(longClazz, initLong,
s.get<FrontendStatus::Tag::freqOffset>());
env->SetObjectField(statusObj, field, newLongObj);
+ env->DeleteLocalRef(newLongObj);
break;
}
case FrontendStatus::Tag::hierarchy: {
@@ -2263,6 +2315,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->NewObject(intClazz, initInt,
static_cast<jint>(s.get<FrontendStatus::Tag::hierarchy>()));
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::isRfLocked: {
@@ -2270,6 +2323,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isRfLocked>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::plpInfo: {
@@ -2289,9 +2343,12 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject plpObj = env->NewObject(plpClazz, initPlp, plpId, isLocked, uec);
env->SetObjectArrayElement(valObj, i, plpObj);
+ env->DeleteLocalRef(plpObj);
}
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(plpClazz);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::modulations: {
@@ -2374,6 +2431,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
if (valid) {
env->SetObjectField(statusObj, field, valObj);
}
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::bers: {
@@ -2384,6 +2442,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::codeRates: {
@@ -2394,6 +2453,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::bandwidth: {
@@ -2434,6 +2494,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intBandwidth);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2465,6 +2526,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intInterval);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2497,6 +2559,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intTransmissionMode);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2505,6 +2568,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::uec>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::systemId: {
@@ -2512,6 +2576,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::systemId>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::interleaving: {
@@ -2558,6 +2623,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
if (valid) {
env->SetObjectField(statusObj, field, valObj);
}
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::isdbtSegment: {
@@ -2568,6 +2634,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint*>(&v[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::tsDataRate: {
@@ -2578,6 +2645,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&v[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::rollOff: {
@@ -2605,6 +2673,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
if (valid) {
jobject newIntegerObj = env->NewObject(intClazz, initInt, intRollOff);
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
}
break;
}
@@ -2613,6 +2682,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isMiso>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::isLinear: {
@@ -2620,6 +2690,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isLinear>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::isShortFrames: {
@@ -2627,6 +2698,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newBooleanObj = env->NewObject(booleanClazz, initBoolean,
s.get<FrontendStatus::Tag::isShortFrames>());
env->SetObjectField(statusObj, field, newBooleanObj);
+ env->DeleteLocalRef(newBooleanObj);
break;
}
case FrontendStatus::Tag::isdbtMode: {
@@ -2634,6 +2706,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject newIntegerObj =
env->NewObject(intClazz, initInt, s.get<FrontendStatus::Tag::isdbtMode>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::partialReceptionFlag: {
@@ -2643,6 +2716,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->NewObject(intClazz, initInt,
s.get<FrontendStatus::Tag::partialReceptionFlag>());
env->SetObjectField(statusObj, field, newIntegerObj);
+ env->DeleteLocalRef(newIntegerObj);
break;
}
case FrontendStatus::Tag::streamIdList: {
@@ -2653,6 +2727,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::dvbtCellIds: {
@@ -2663,6 +2738,7 @@ jobject JTuner::getFrontendStatus(jintArray types) {
env->SetIntArrayRegion(valObj, 0, v.size(), reinterpret_cast<jint *>(&ids[0]));
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(valObj);
break;
}
case FrontendStatus::Tag::allPlpInfo: {
@@ -2678,9 +2754,12 @@ jobject JTuner::getFrontendStatus(jintArray types) {
jobject plpObj = env->NewObject(plpClazz, initPlp, plpInfos[i].plpId,
plpInfos[i].bLlsFlag);
env->SetObjectArrayElement(valObj, i, plpObj);
+ env->DeleteLocalRef(plpObj);
}
env->SetObjectField(statusObj, field, valObj);
+ env->DeleteLocalRef(plpClazz);
+ env->DeleteLocalRef(valObj);
break;
}
}
@@ -2837,6 +2916,7 @@ static vector<FrontendAtsc3PlpSettings> getAtsc3PlpSettings(JNIEnv *env, const j
.fec = fec,
};
plps[i] = frontendAtsc3PlpSettings;
+ env->DeleteLocalRef(plp);
}
return plps;
}
@@ -3192,6 +3272,7 @@ static FrontendSettings getIsdbtFrontendSettings(JNIEnv *env, const jobject& set
env->GetIntField(layer, env->GetFieldID(layerClazz, "mCodeRate", "I")));
frontendIsdbtSettings.layerSettings[i].numOfSegment =
env->GetIntField(layer, env->GetFieldID(layerClazz, "mNumOfSegments", "I"));
+ env->DeleteLocalRef(layer);
}
frontendSettings.set<FrontendSettings::Tag::isdbt>(frontendIsdbtSettings);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
index 220aa33bf497..c524037fe444 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java
@@ -28,6 +28,7 @@ import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.KeyEvent;
+import android.webkit.URLUtil;
import android.webkit.WebView;
import com.android.phone.slice.SlicePurchaseController;
@@ -60,36 +61,38 @@ public class SlicePurchaseActivity extends Activity {
@NonNull private WebView mWebView;
@NonNull private Context mApplicationContext;
+ @NonNull private Intent mIntent;
+ @Nullable private URL mUrl;
private int mSubId;
@TelephonyManager.PremiumCapability protected int mCapability;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Intent intent = getIntent();
- mSubId = intent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
+ mIntent = getIntent();
+ mSubId = mIntent.getIntExtra(SlicePurchaseController.EXTRA_SUB_ID,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- mCapability = intent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
+ mCapability = mIntent.getIntExtra(SlicePurchaseController.EXTRA_PREMIUM_CAPABILITY,
SlicePurchaseController.PREMIUM_CAPABILITY_INVALID);
mApplicationContext = getApplicationContext();
- URL url = getUrl();
+ mUrl = getUrl();
logd("onCreate: subId=" + mSubId + ", capability="
+ TelephonyManager.convertPremiumCapabilityToString(mCapability)
- + ", url=" + url);
+ + ", url=" + mUrl);
// Cancel network boost notification
mApplicationContext.getSystemService(NotificationManager.class)
.cancel(SlicePurchaseBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
// Verify intent and values are valid
- if (!SlicePurchaseBroadcastReceiver.isIntentValid(intent)) {
- loge("Not starting SlicePurchaseActivity with an invalid Intent: " + intent);
+ if (!SlicePurchaseBroadcastReceiver.isIntentValid(mIntent)) {
+ loge("Not starting SlicePurchaseActivity with an invalid Intent: " + mIntent);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- intent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_REQUEST_FAILED);
finishAndRemoveTask();
return;
}
- if (url == null) {
+ if (mUrl == null) {
String error = "Unable to create a URL from carrier configs.";
loge(error);
Intent data = new Intent();
@@ -97,7 +100,7 @@ public class SlicePurchaseActivity extends Activity {
SlicePurchaseController.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, error);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
- getIntent(), SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
finishAndRemoveTask();
return;
}
@@ -105,7 +108,7 @@ public class SlicePurchaseActivity extends Activity {
loge("Unable to start the slice purchase application on the non-default data "
+ "subscription: " + mSubId);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- intent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_NOT_DEFAULT_DATA_SUBSCRIPTION);
finishAndRemoveTask();
return;
}
@@ -114,16 +117,7 @@ public class SlicePurchaseActivity extends Activity {
SlicePurchaseBroadcastReceiver.updateSlicePurchaseActivity(mCapability, this);
// Create and configure WebView
- mWebView = new WebView(this);
- // Enable JavaScript for the carrier purchase website to send results back to
- // the slice purchase application.
- mWebView.getSettings().setJavaScriptEnabled(true);
- mWebView.addJavascriptInterface(
- new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
-
- // Display WebView
- setContentView(mWebView);
- mWebView.loadUrl(url.toString());
+ setupWebView();
}
protected void onPurchaseSuccessful(long duration) {
@@ -134,7 +128,7 @@ public class SlicePurchaseActivity extends Activity {
Intent intent = new Intent();
intent.putExtra(SlicePurchaseController.EXTRA_PURCHASE_DURATION, duration);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
- getIntent(), SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_SUCCESS, intent);
finishAndRemoveTask();
}
@@ -147,7 +141,7 @@ public class SlicePurchaseActivity extends Activity {
data.putExtra(SlicePurchaseController.EXTRA_FAILURE_CODE, failureCode);
data.putExtra(SlicePurchaseController.EXTRA_FAILURE_REASON, failureReason);
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponseWithData(mApplicationContext,
- getIntent(), SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_CARRIER_ERROR, data);
finishAndRemoveTask();
}
@@ -166,7 +160,7 @@ public class SlicePurchaseActivity extends Activity {
protected void onDestroy() {
logd("onDestroy: User canceled the purchase by closing the application.");
SlicePurchaseBroadcastReceiver.sendSlicePurchaseAppResponse(
- getIntent(), SlicePurchaseController.EXTRA_INTENT_CANCELED);
+ mIntent, SlicePurchaseController.EXTRA_INTENT_CANCELED);
SlicePurchaseBroadcastReceiver.removeSlicePurchaseActivity(mCapability);
super.onDestroy();
}
@@ -175,14 +169,37 @@ public class SlicePurchaseActivity extends Activity {
String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
.getConfigForSubId(mSubId).getString(
CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
- try {
- return new URL(url);
- } catch (MalformedURLException e) {
- loge("Invalid URL: " + url);
+ boolean isUrlValid = URLUtil.isValidUrl(url);
+ if (URLUtil.isAssetUrl(url)) {
+ isUrlValid = url.equals(SlicePurchaseController.SLICE_PURCHASE_TEST_FILE);
}
+ if (isUrlValid) {
+ try {
+ return new URL(url);
+ } catch (MalformedURLException ignored) {
+ }
+ }
+ loge("Invalid URL: " + url);
return null;
}
+ private void setupWebView() {
+ // Create WebView
+ mWebView = new WebView(this);
+
+ // Enable JavaScript for the carrier purchase website to send results back to
+ // the slice purchase application.
+ mWebView.getSettings().setJavaScriptEnabled(true);
+ mWebView.addJavascriptInterface(
+ new SlicePurchaseWebInterface(this), "SlicePurchaseWebInterface");
+
+ // Display WebView
+ setContentView(mWebView);
+
+ // Load the URL
+ mWebView.loadUrl(mUrl.toString());
+ }
+
private static void logd(@NonNull String s) {
Log.d(TAG, s);
}
diff --git a/packages/CredentialManager/res/values/themes.xml b/packages/CredentialManager/res/values/themes.xml
index feec74608000..a58a0383b9b2 100644
--- a/packages/CredentialManager/res/values/themes.xml
+++ b/packages/CredentialManager/res/values/themes.xml
@@ -2,11 +2,12 @@
<resources>
<style name="Theme.CredentialSelector" parent="@android:style/ThemeOverlay.Material">
- <item name="android:statusBarColor">@color/purple_700</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="fontFamily">google-sans</item>
</style>
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 8bd7cf03008b..b848a47f37de 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -38,12 +38,15 @@ import android.os.Binder
import android.os.Bundle
import android.os.ResultReceiver
import com.android.credentialmanager.createflow.ActiveEntry
-import com.android.credentialmanager.createflow.CreatePasskeyUiState
+import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
+import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest.Companion.createFrom
+import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
+import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
// Consider repo per screen, similar to view model?
@@ -123,7 +126,7 @@ class CredentialManagerRepo(
)
}
- fun createPasskeyInitialUiState(): CreatePasskeyUiState {
+ fun createCredentialInitialUiState(): CreateCredentialUiState {
val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
// Handle runtime cast error
providerEnabledList as List<CreateCredentialProviderData>, context)
@@ -135,13 +138,22 @@ class CredentialManagerRepo(
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
- // TODO: covert from real requestInfo
- val requestDisplayInfo = RequestDisplayInfo(
+ // TODO: covert from real requestInfo for create passkey
+ var requestDisplayInfo = RequestDisplayInfo(
"Elisa Beckett",
"beckett-bakert@gmail.com",
TYPE_PUBLIC_KEY_CREDENTIAL,
"tribank")
- return CreatePasskeyUiState(
+ val createCredentialRequest = requestInfo.createCredentialRequest
+ val createCredentialRequestJetpack = createCredentialRequest?.let { createFrom(it) }
+ if (createCredentialRequestJetpack is CreatePasswordRequest) {
+ requestDisplayInfo = RequestDisplayInfo(
+ createCredentialRequestJetpack.id,
+ createCredentialRequestJetpack.password,
+ TYPE_PASSWORD_CREDENTIAL,
+ "tribank")
+ }
+ return CreateCredentialUiState(
enabledProviders = providerEnabledList,
disabledProviders = providerDisabledList,
if (hasDefault)
@@ -388,15 +400,15 @@ class CredentialManagerRepo(
}
private fun testCreateRequestInfo(): RequestInfo {
- val data = Bundle()
+ val data = toBundle("beckett-bakert@gmail.com", "password123")
return RequestInfo.newCreateRequestInfo(
Binder(),
CreateCredentialRequest(
- TYPE_PUBLIC_KEY_CREDENTIAL,
+ TYPE_PASSWORD_CREDENTIAL,
data
),
/*isFirstUsage=*/false,
- "tribank.us"
+ "tribank"
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 78edaa936bcd..1041a33333b3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -28,8 +28,8 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.android.credentialmanager.common.DialogType
import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ResultState
-import com.android.credentialmanager.createflow.CreatePasskeyScreen
-import com.android.credentialmanager.createflow.CreatePasskeyViewModel
+import com.android.credentialmanager.createflow.CreateCredentialScreen
+import com.android.credentialmanager.createflow.CreateCredentialViewModel
import com.android.credentialmanager.getflow.GetCredentialScreen
import com.android.credentialmanager.getflow.GetCredentialViewModel
import com.android.credentialmanager.ui.theme.CredentialSelectorTheme
@@ -63,12 +63,12 @@ class CredentialSelectorActivity : ComponentActivity() {
val dialogType = DialogType.toDialogType(operationType)
when (dialogType) {
DialogType.CREATE_PASSKEY -> {
- val viewModel: CreatePasskeyViewModel = viewModel()
+ val viewModel: CreateCredentialViewModel = viewModel()
viewModel.observeDialogResult().observe(
this@CredentialSelectorActivity,
onCancel
)
- CreatePasskeyScreen(viewModel = viewModel)
+ CreateCredentialScreen(viewModel = viewModel)
}
DialogType.GET_CREDENTIALS -> {
val viewModel: GetCredentialViewModel = viewModel()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 33fb154f44f0..df797436baf6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager
+import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.credentials.ui.Entry
@@ -146,9 +147,17 @@ class CreateFlowUtils {
): List<com.android.credentialmanager.createflow.EnabledProviderInfo> {
// TODO: get from the actual service info
val packageManager = context.packageManager
+
return providerDataList.map {
+ val componentName = ComponentName.unflattenFromString(it.providerFlattenedComponentName)
+ var packageName = componentName?.packageName
+ if (componentName == null) {
+ // TODO: Remove once test data is fixed
+ packageName = it.providerFlattenedComponentName
+ }
+
val pkgInfo = packageManager
- .getPackageInfo(it.providerFlattenedComponentName,
+ .getPackageInfo(packageName!!,
PackageManager.PackageInfoFlags.of(0))
com.android.credentialmanager.createflow.EnabledProviderInfo(
// TODO: decide what to do when failed to load a provider icon
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
index f1f453da4f38..61e11feff3c0 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
@@ -62,6 +62,7 @@ import com.android.credentialmanager.R
import com.android.credentialmanager.common.material.ModalBottomSheetValue.Expanded
import com.android.credentialmanager.common.material.ModalBottomSheetValue.HalfExpanded
import com.android.credentialmanager.common.material.ModalBottomSheetValue.Hidden
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import kotlin.math.max
@@ -318,7 +319,7 @@ fun ModalBottomSheetLayout(
rememberModalBottomSheetState(Hidden),
sheetShape: Shape = MaterialTheme.shapes.large,
sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
- sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+ sheetBackgroundColor: Color = ModalBottomSheetDefaults.scrimColor,
sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
scrimColor: Color = ModalBottomSheetDefaults.scrimColor,
content: @Composable () -> Unit
@@ -476,5 +477,5 @@ object ModalBottomSheetDefaults {
*/
val scrimColor: Color
@Composable
- get() = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.32f)
+ get() = LocalAndroidColorScheme.current.colorSurfaceHighlight
} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
new file mode 100644
index 000000000000..51a1cbbbf942
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.common.ui
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.SuggestionChip
+import androidx.compose.material3.SuggestionChipDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.credentialmanager.ui.theme.EntryShape
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun Entry(
+ onClick: () -> Unit,
+ label: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ icon: @Composable (() -> Unit)? = null,
+) {
+ SuggestionChip(
+ modifier = modifier.fillMaxWidth(),
+ onClick = onClick,
+ shape = EntryShape.FullSmallRoundedCorner,
+ label = label,
+ icon = icon,
+ border = null,
+ colors = SuggestionChipDefaults.suggestionChipColors(
+ containerColor = LocalAndroidColorScheme.current.colorSurface,
+ ),
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun TransparentBackgroundEntry(
+ onClick: () -> Unit,
+ label: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ icon: @Composable (() -> Unit)? = null,
+) {
+ SuggestionChip(
+ modifier = modifier.fillMaxWidth(),
+ onClick = onClick,
+ label = label,
+ icon = icon,
+ border = null,
+ colors = SuggestionChipDefaults.suggestionChipColors(
+ containerColor = Color.Transparent,
+ ),
+ )
+} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 67b704f5d787..27d366d55937 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -1,3 +1,5 @@
+@file:OptIn(ExperimentalMaterial3Api::class)
+
package com.android.credentialmanager.createflow
import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
@@ -15,10 +17,10 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.outlined.NewReleases
@@ -40,12 +42,14 @@ import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
import com.android.credentialmanager.common.ui.CancelButton
import com.android.credentialmanager.common.ui.ConfirmButton
+import com.android.credentialmanager.common.ui.Entry
+import com.android.credentialmanager.ui.theme.EntryShape
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun CreatePasskeyScreen(
- viewModel: CreatePasskeyViewModel,
+fun CreateCredentialScreen(
+ viewModel: CreateCredentialViewModel,
) {
val state = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Expanded,
@@ -91,7 +95,7 @@ fun CreatePasskeyScreen(
}
},
scrimColor = MaterialTheme.colorScheme.scrim,
- sheetShape = MaterialTheme.shapes.medium,
+ sheetShape = EntryShape.TopRoundedCorner,
) {}
LaunchedEffect(state.currentValue) {
if (state.currentValue == ModalBottomSheetValue.Hidden) {
@@ -100,6 +104,7 @@ fun CreatePasskeyScreen(
}
}
+@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ConfirmationCard(
onConfirm: () -> Unit,
@@ -179,7 +184,7 @@ fun ProviderSelectionCard(
color = Color.Transparent
)
Card(
- shape = MaterialTheme.shapes.large,
+ shape = MaterialTheme.shapes.medium,
modifier = Modifier
.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally),
@@ -243,14 +248,16 @@ fun MoreOptionsSelectionCard(
Icons.Filled.ArrowBack,
stringResource(R.string.accessibility_back_arrow_button))
}
- }
+ },
+ colors = TopAppBarDefaults.smallTopAppBarColors
+ (containerColor = Color.Transparent),
)
Divider(
thickness = 8.dp,
color = Color.Transparent
)
Card(
- shape = MaterialTheme.shapes.large,
+ shape = MaterialTheme.shapes.medium,
modifier = Modifier
.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally)
@@ -349,20 +356,17 @@ fun MoreOptionsRowIntroCard(
}
}
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProviderRow(providerInfo: ProviderInfo, onProviderSelected: (String) -> Unit) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
+ Entry(
onClick = {onProviderSelected(providerInfo.name)},
icon = {
- Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+ Image(modifier = Modifier.size(32.dp).padding(start = 10.dp),
bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
// painter = painterResource(R.drawable.ic_passkey),
// TODO: add description.
contentDescription = "")
},
- shape = MaterialTheme.shapes.large,
label = {
Text(
text = providerInfo.displayName,
@@ -391,7 +395,8 @@ fun CreationSelectionCard(
bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
contentDescription = null,
tint = Color.Unspecified,
- modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+ .padding(all = 24.dp).size(32.dp)
)
Text(
text = when (requestDisplayInfo.type) {
@@ -425,7 +430,7 @@ fun CreationSelectionCard(
)
}
Card(
- shape = MaterialTheme.shapes.large,
+ shape = MaterialTheme.shapes.medium,
modifier = Modifier
.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally),
@@ -491,27 +496,35 @@ fun PrimaryCreateOptionRow(
createOptionInfo: CreateOptionInfo,
onOptionSelected: () -> Unit
) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
+ Entry(
onClick = onOptionSelected,
icon = {
- Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+ Image(modifier = Modifier.size(32.dp).padding(start = 10.dp),
bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
contentDescription = null)
},
- shape = MaterialTheme.shapes.large,
label = {
Column() {
- Text(
- text = requestDisplayInfo.userName,
- style = MaterialTheme.typography.titleLarge,
- modifier = Modifier.padding(top = 16.dp)
- )
- Text(
- text = requestDisplayInfo.displayName,
- style = MaterialTheme.typography.bodyMedium,
- modifier = Modifier.padding(bottom = 16.dp)
- )
+ // TODO: Add the function to hide/view password when the type is create password
+ if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL ||
+ requestDisplayInfo.type == TYPE_PASSWORD_CREDENTIAL) {
+ Text(
+ text = requestDisplayInfo.title,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ Text(
+ text = requestDisplayInfo.subtitle,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ } else {
+ Text(
+ text = requestDisplayInfo.subtitle,
+ style = MaterialTheme.typography.titleLarge,
+ modifier = Modifier.padding(top = 16.dp, bottom = 16.dp)
+ )
+ }
}
}
)
@@ -524,15 +537,13 @@ fun MoreOptionsInfoRow(
createOptionInfo: CreateOptionInfo,
onOptionSelected: () -> Unit
) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
+ Entry(
onClick = onOptionSelected,
icon = {
- Image(modifier = Modifier.size(32.dp, 32.dp).padding(start = 16.dp),
+ Image(modifier = Modifier.size(32.dp).padding(start = 16.dp),
bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
contentDescription = null)
},
- shape = MaterialTheme.shapes.large,
label = {
Column() {
Text(
@@ -593,8 +604,7 @@ fun MoreOptionsDisabledProvidersRow(
disabledProviders: List<ProviderInfo>,
onDisabledPasswordManagerSelected: () -> Unit,
) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
+ Entry(
onClick = onDisabledPasswordManagerSelected,
icon = {
Icon(
@@ -603,7 +613,6 @@ fun MoreOptionsDisabledProvidersRow(
modifier = Modifier.padding(start = 16.dp)
)
},
- shape = MaterialTheme.shapes.large,
label = {
Column() {
Text(
@@ -626,8 +635,7 @@ fun MoreOptionsDisabledProvidersRow(
fun RemoteEntryRow(
onRemoteEntrySelected: () -> Unit,
) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
+ Entry(
onClick = onRemoteEntrySelected,
icon = {
Icon(
@@ -637,7 +645,6 @@ fun RemoteEntryRow(
modifier = Modifier.padding(start = 18.dp)
)
},
- shape = MaterialTheme.shapes.large,
label = {
Column() {
Text(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index af74b8ea4de1..6be019fa0882 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -27,7 +27,7 @@ import com.android.credentialmanager.CredentialManagerRepo
import com.android.credentialmanager.common.DialogResult
import com.android.credentialmanager.common.ResultState
-data class CreatePasskeyUiState(
+data class CreateCredentialUiState(
val enabledProviders: List<EnabledProviderInfo>,
val disabledProviders: List<DisabledProviderInfo>? = null,
val currentScreenState: CreateScreenState,
@@ -35,11 +35,11 @@ data class CreatePasskeyUiState(
val activeEntry: ActiveEntry? = null,
)
-class CreatePasskeyViewModel(
+class CreateCredentialViewModel(
credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance()
) : ViewModel() {
- var uiState by mutableStateOf(credManRepo.createPasskeyInitialUiState())
+ var uiState by mutableStateOf(credManRepo.createCredentialInitialUiState())
private set
val dialogResult: MutableLiveData<DialogResult> by lazy {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 123c3d454905..1ab234a0e0bc 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -62,8 +62,8 @@ class RemoteInfo(
) : EntryInfo(entryKey, entrySubkey)
data class RequestDisplayInfo(
- val userName: String,
- val displayName: String,
+ val title: String,
+ val subtitle: String,
val type: String,
val appDomainName: String,
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index dcdd71a283a8..6fd51dd3b69a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
@@ -33,7 +34,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
@@ -46,6 +46,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.R
@@ -53,6 +54,8 @@ import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.common.ui.Entry
+import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
@Composable
@@ -107,6 +110,9 @@ fun PrimarySelectionCard(
Card() {
Column() {
Text(
+ modifier = Modifier.padding(all = 24.dp),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.headlineSmall,
text = stringResource(
if (sortedUserNameToCredentialEntryList.size == 1) {
if (sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList
@@ -117,12 +123,10 @@ fun PrimarySelectionCard(
} else R.string.get_dialog_title_choose_sign_in_for,
requestDisplayInfo.appDomainName
),
- style = MaterialTheme.typography.titleMedium,
- modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
)
Card(
- shape = MaterialTheme.shapes.large,
+ shape = MaterialTheme.shapes.medium,
modifier = Modifier
.padding(horizontal = 24.dp)
.align(alignment = Alignment.CenterHorizontally)
@@ -254,9 +258,14 @@ fun ActionChips(
modifier = Modifier.padding(vertical = 8.dp)
)
// TODO: tweak padding.
- Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
- actionChips.forEach {
- ActionEntryRow(it, onEntrySelected)
+ Card(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ shape = MaterialTheme.shapes.medium,
+ ) {
+ Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+ actionChips.forEach {
+ ActionEntryRow(it, onEntrySelected)
+ }
}
}
}
@@ -271,8 +280,18 @@ fun LockedCredentials(
style = MaterialTheme.typography.labelLarge,
modifier = Modifier.padding(vertical = 8.dp)
)
- authenticationEntryList.forEach {
- AuthenticationEntryRow(it, onEntrySelected)
+ Card(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ shape = MaterialTheme.shapes.medium,
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ authenticationEntryList.forEach {
+ AuthenticationEntryRow(it, onEntrySelected)
+ }
+ }
}
}
@@ -287,8 +306,18 @@ fun PerUserNameCredentials(
style = MaterialTheme.typography.labelLarge,
modifier = Modifier.padding(vertical = 8.dp)
)
- perUserNameCredentialEntryList.sortedCredentialEntryList.forEach {
- CredentialEntryRow(it, onEntrySelected)
+ Card(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ shape = MaterialTheme.shapes.medium,
+ ) {
+ Column(
+ modifier = Modifier.fillMaxWidth().wrapContentHeight(),
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ perUserNameCredentialEntryList.sortedCredentialEntryList.forEach {
+ CredentialEntryRow(it, onEntrySelected)
+ }
+ }
}
}
@@ -298,16 +327,14 @@ fun CredentialEntryRow(
credentialEntryInfo: CredentialEntryInfo,
onEntrySelected: (EntryInfo) -> Unit,
) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
+ Entry(
onClick = {onEntrySelected(credentialEntryInfo)},
icon = {
- Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+ Image(modifier = Modifier.padding(start = 10.dp).size(32.dp),
bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
// TODO: add description.
contentDescription = "")
},
- shape = MaterialTheme.shapes.large,
label = {
Column() {
// TODO: fix the text values.
@@ -338,16 +365,14 @@ fun AuthenticationEntryRow(
authenticationEntryInfo: AuthenticationEntryInfo,
onEntrySelected: (EntryInfo) -> Unit,
) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
+ Entry(
onClick = {onEntrySelected(authenticationEntryInfo)},
icon = {
- Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+ Image(modifier = Modifier.padding(start = 10.dp).size(32.dp),
bitmap = authenticationEntryInfo.icon.toBitmap().asImageBitmap(),
// TODO: add description.
contentDescription = "")
},
- shape = MaterialTheme.shapes.large,
label = {
Column() {
// TODO: fix the text values.
@@ -372,16 +397,13 @@ fun ActionEntryRow(
actionEntryInfo: ActionEntryInfo,
onEntrySelected: (EntryInfo) -> Unit,
) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
- onClick = { onEntrySelected(actionEntryInfo) },
+ TransparentBackgroundEntry(
icon = {
- Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+ Image(modifier = Modifier.padding(start = 10.dp).size(32.dp),
bitmap = actionEntryInfo.icon.toBitmap().asImageBitmap(),
// TODO: add description.
contentDescription = "")
},
- shape = MaterialTheme.shapes.large,
label = {
Column() {
Text(
@@ -395,17 +417,16 @@ fun ActionEntryRow(
)
}
}
- }
+ },
+ onClick = { onEntrySelected(actionEntryInfo) },
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SignInAnotherWayRow(onSelect: () -> Unit) {
- SuggestionChip(
- modifier = Modifier.fillMaxWidth(),
+ Entry(
onClick = onSelect,
- shape = MaterialTheme.shapes.large,
label = {
Text(
text = stringResource(R.string.get_dialog_use_saved_passkey_for),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index c1d9ea9b9188..c541e08ffbb4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -64,8 +64,6 @@ class CredentialEntryInfo(
val lastUsedTimeMillis: Long?,
) : EntryInfo(providerId, entryKey, entrySubkey)
-// TODO: handle sub credential type values like password obfuscation.
-
class AuthenticationEntryInfo(
providerId: String,
entryKey: String,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
new file mode 100644
index 000000000000..15ae3295416b
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.ui.theme
+
+import android.annotation.ColorInt
+import android.content.Context
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import com.android.internal.R
+
+/** CompositionLocal used to pass [AndroidColorScheme] down the tree. */
+val LocalAndroidColorScheme =
+ staticCompositionLocalOf<AndroidColorScheme> {
+ throw IllegalStateException(
+ "No AndroidColorScheme configured. Make sure to use LocalAndroidColorScheme in a " +
+ "Composable surrounded by a CredentialSelectorTheme {}."
+ )
+ }
+
+/**
+ * The Android color scheme.
+ *
+ * Important: Use M3 colors from MaterialTheme.colorScheme whenever possible instead. In the future,
+ * most of the colors in this class will be removed in favor of their M3 counterpart.
+ */
+class AndroidColorScheme internal constructor(context: Context) {
+
+ val colorPrimary = getColor(context, R.attr.colorPrimary)
+ val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
+ val colorAccent = getColor(context, R.attr.colorAccent)
+ val colorAccentPrimary = getColor(context, R.attr.colorAccentPrimary)
+ val colorAccentSecondary = getColor(context, R.attr.colorAccentSecondary)
+ val colorAccentTertiary = getColor(context, R.attr.colorAccentTertiary)
+ val colorAccentPrimaryVariant = getColor(context, R.attr.colorAccentPrimaryVariant)
+ val colorAccentSecondaryVariant = getColor(context, R.attr.colorAccentSecondaryVariant)
+ val colorAccentTertiaryVariant = getColor(context, R.attr.colorAccentTertiaryVariant)
+ val colorSurface = getColor(context, R.attr.colorSurface)
+ val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight)
+ val colorSurfaceVariant = getColor(context, R.attr.colorSurfaceVariant)
+ val colorSurfaceHeader = getColor(context, R.attr.colorSurfaceHeader)
+ val colorError = getColor(context, R.attr.colorError)
+ val colorBackground = getColor(context, R.attr.colorBackground)
+ val colorBackgroundFloating = getColor(context, R.attr.colorBackgroundFloating)
+ val panelColorBackground = getColor(context, R.attr.panelColorBackground)
+ val textColorPrimary = getColor(context, R.attr.textColorPrimary)
+ val textColorSecondary = getColor(context, R.attr.textColorSecondary)
+ val textColorTertiary = getColor(context, R.attr.textColorTertiary)
+ val textColorPrimaryInverse = getColor(context, R.attr.textColorPrimaryInverse)
+ val textColorSecondaryInverse = getColor(context, R.attr.textColorSecondaryInverse)
+ val textColorTertiaryInverse = getColor(context, R.attr.textColorTertiaryInverse)
+ val textColorOnAccent = getColor(context, R.attr.textColorOnAccent)
+ val colorForeground = getColor(context, R.attr.colorForeground)
+ val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
+
+ private fun getColor(context: Context, attr: Int): Color {
+ val ta = context.obtainStyledAttributes(intArrayOf(attr))
+ @ColorInt val color = ta.getColor(0, 0)
+ ta.recycle()
+ return Color(color)
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
index 5ea69930e334..d8a8f162ba40 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Shape.kt
@@ -6,6 +6,15 @@ import androidx.compose.ui.unit.dp
val Shapes = Shapes(
small = RoundedCornerShape(100.dp),
- medium = RoundedCornerShape(20.dp),
+ medium = RoundedCornerShape(28.dp),
large = RoundedCornerShape(0.dp)
)
+
+object EntryShape {
+ val TopRoundedCorner = RoundedCornerShape(28.dp, 28.dp, 0.dp, 0.dp)
+ val BottomRoundedCorner = RoundedCornerShape(0.dp, 0.dp, 28.dp, 28.dp)
+ // Used for middle entries.
+ val FullSmallRoundedCorner = RoundedCornerShape(4.dp, 4.dp, 4.dp, 4.dp)
+ // Used for when there's a single entry.
+ val FullMediumRoundedCorner = RoundedCornerShape(28.dp, 28.dp, 28.dp, 28.dp)
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
index 248df92bac59..3ca0e4494ab6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/Theme.kt
@@ -2,37 +2,37 @@ package com.android.credentialmanager.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.lightColorScheme
-import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
-
-private val AppDarkColorScheme = darkColorScheme(
- primary = Purple200,
- secondary = Purple700,
- tertiary = Teal200
-)
-
-private val AppLightColorScheme = lightColorScheme(
- primary = Purple500,
- secondary = Purple700,
- tertiary = Teal200
-)
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
@Composable
fun CredentialSelectorTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
- val AppColorScheme = if (darkTheme) {
- AppDarkColorScheme
- } else {
- AppLightColorScheme
- }
+ val context = LocalContext.current
+
+ val colorScheme =
+ if (darkTheme) {
+ dynamicDarkColorScheme(context)
+ } else {
+ dynamicLightColorScheme(context)
+ }
+ val androidColorScheme = AndroidColorScheme(context)
+ val typography = Typography
MaterialTheme(
- colorScheme = AppColorScheme,
- typography = Typography,
- shapes = Shapes,
- content = content
- )
+ colorScheme,
+ typography = typography,
+ shapes = Shapes
+ ) {
+ CompositionLocalProvider(
+ LocalAndroidColorScheme provides androidColorScheme,
+ ) {
+ content()
+ }
+ }
}
diff --git a/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml b/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml
index 62dba985f715..08672938ff77 100644
--- a/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values-en-rCA/strings.xml
@@ -3,8 +3,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="keyguard_description" msgid="8582605799129954556">"Please enter your password and continue to Dynamic System Updates"</string>
<string name="notification_install_completed" msgid="6252047868415172643">"Dynamic system is ready. To start using it, restart your device."</string>
- <string name="notification_install_inprogress" msgid="7383334330065065017">"Installation in progress"</string>
- <string name="notification_install_failed" msgid="4066039210317521404">"Installation failed"</string>
+ <string name="notification_install_inprogress" msgid="7383334330065065017">"Install in progress"</string>
+ <string name="notification_install_failed" msgid="4066039210317521404">"Install failed"</string>
<string name="notification_image_validation_failed" msgid="2720357826403917016">"Image validation failed. Abort installation."</string>
<string name="notification_dynsystem_in_use" msgid="1053194595682188396">"Currently running a dynamic system. Restart to use the original Android version."</string>
<string name="notification_action_cancel" msgid="5929299408545961077">"Cancel"</string>
diff --git a/packages/InputDevices/res/values-en-rCA/strings.xml b/packages/InputDevices/res/values-en-rCA/strings.xml
index ab48729d5006..116178300348 100644
--- a/packages/InputDevices/res/values-en-rCA/strings.xml
+++ b/packages/InputDevices/res/values-en-rCA/strings.xml
@@ -19,7 +19,7 @@
<string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Swiss German"</string>
<string name="keyboard_layout_belgian" msgid="2011984572838651558">"Belgian"</string>
<string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Bulgarian"</string>
- <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarian, phonetic"</string>
+ <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Bulgarian, Phonetic"</string>
<string name="keyboard_layout_italian" msgid="6497079660449781213">"Italian"</string>
<string name="keyboard_layout_danish" msgid="8036432066627127851">"Danish"</string>
<string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Norwegian"</string>
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 2eaa73e8b21c..eb7aaa71fc7b 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -40,7 +40,6 @@ android_library {
],
kotlincflags: [
"-Xjvm-default=all",
- "-opt-in=kotlin.RequiresOptIn",
],
min_sdk_version: "31",
}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 4944784c190c..854359641bc0 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -44,7 +44,7 @@ android {
}
kotlinOptions {
jvmTarget = '1.8'
- freeCompilerArgs = ["-Xjvm-default=all", "-opt-in=kotlin.RequiresOptIn"]
+ freeCompilerArgs = ["-Xjvm-default=all"]
}
buildFeatures {
compose true
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index cbfbb9ccbe90..3e50b2990309 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -42,7 +42,7 @@ android {
}
kotlinOptions {
jvmTarget = '1.8'
- freeCompilerArgs = ["-Xjvm-default=all", "-opt-in=kotlin.RequiresOptIn"]
+ freeCompilerArgs = ["-Xjvm-default=all"]
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index 18ae09ea4e1e..4a7418fd101d 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -30,7 +30,6 @@ android_library {
],
kotlincflags: [
"-Xjvm-default=all",
- "-opt-in=kotlin.RequiresOptIn",
],
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
index 12955c887480..5cd74e30c207 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -39,7 +39,4 @@ android_test {
"mockito-target-minus-junit4",
"truth-prebuilt",
],
- kotlincflags: [
- "-opt-in=kotlin.RequiresOptIn",
- ],
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 950ee21ae7b5..9583a59148fc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -668,6 +668,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
* @param bluetoothProfile the Bluetooth profile
*/
public void onActiveDeviceChanged(boolean isActive, int bluetoothProfile) {
+ if (BluetoothUtils.D) {
+ Log.d(TAG, "onActiveDeviceChanged: "
+ + "profile " + BluetoothProfile.getProfileName(bluetoothProfile)
+ + ", device " + mDevice.getAnonymizedAddress()
+ + ", isActive " + isActive);
+ }
boolean changed = false;
switch (bluetoothProfile) {
case BluetoothProfile.A2DP:
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index fa96a2f0ee7f..0b7b2f935e91 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -112,10 +112,10 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.util.FrameworkStatsLog;
import com.android.providers.settings.SettingsState.Setting;
-import libcore.util.HexEncoding;
-
import com.google.android.collect.Sets;
+import libcore.util.HexEncoding;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
@@ -1144,7 +1144,7 @@ public class SettingsProvider extends ContentProvider {
Slog.v(LOG_TAG, "getConfigSetting(" + name + ")");
}
- DeviceConfig.enforceReadPermission(getContext(), /*namespace=*/name.split("/")[0]);
+ DeviceConfig.enforceReadPermission(/*namespace=*/name.split("/")[0]);
// Get the value.
synchronized (mLock) {
@@ -1317,7 +1317,7 @@ public class SettingsProvider extends ContentProvider {
Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
}
- DeviceConfig.enforceReadPermission(getContext(),
+ DeviceConfig.enforceReadPermission(
prefix != null ? prefix.split("/")[0] : null);
synchronized (mLock) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2e4a245df6a6..01c080990cfd 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -771,6 +771,9 @@
<!-- Permissions required for CTS test - CtsAppFgsTestCases -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
+ <!-- Permission required for CTS test - ApplicationExemptionsTests -->
+ <uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/Shell/res/values-en-rCA/strings.xml b/packages/Shell/res/values-en-rCA/strings.xml
index 546281360131..65ab725da36e 100644
--- a/packages/Shell/res/values-en-rCA/strings.xml
+++ b/packages/Shell/res/values-en-rCA/strings.xml
@@ -28,7 +28,7 @@
<string name="bugreport_finished_pending_screenshot_text" product="tv" msgid="2343263822812016950">"Select to share your bug report without a screenshot or wait for the screenshot to finish"</string>
<string name="bugreport_finished_pending_screenshot_text" product="watch" msgid="1474435374470177193">"Tap to share your bug report without a screenshot or wait for the screenshot to finish"</string>
<string name="bugreport_finished_pending_screenshot_text" product="default" msgid="1474435374470177193">"Tap to share your bug report without a screenshot or wait for the screenshot to finish"</string>
- <string name="bugreport_confirm" msgid="5917407234515812495">"Bug reports contain data from the system\'s various log files, which may include data that you consider sensitive (such as app-usage and location data). Only share bug reports with people and apps that you trust."</string>
+ <string name="bugreport_confirm" msgid="5917407234515812495">"Bug reports contain data from the system\'s various log files, which may include data you consider sensitive (such as app-usage and location data). Only share bug reports with people and apps you trust."</string>
<string name="bugreport_confirm_dont_repeat" msgid="6179945398364357318">"Don\'t show again"</string>
<string name="bugreport_storage_title" msgid="5332488144740527109">"Bug reports"</string>
<string name="bugreport_unreadable_text" msgid="586517851044535486">"Bug report file could not be read"</string>
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
index 1d808ba7ee16..74e6d85f5374 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceOnMainThreadDetector.kt
@@ -59,11 +59,11 @@ class BindServiceOnMainThreadDetector : Detector(), SourceCodeScanner {
!hasWorkerThreadAnnotation(context, node.getParentOfType(UClass::class.java))
) {
context.report(
- ISSUE,
- method,
- context.getLocation(node),
- "This method should be annotated with `@WorkerThread` because " +
- "it calls ${method.name}",
+ issue = ISSUE,
+ location = context.getLocation(node),
+ message =
+ "This method should be annotated with `@WorkerThread` because " +
+ "it calls ${method.name}",
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
index 112992913661..344d0a3f3187 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
@@ -52,10 +52,9 @@ class BroadcastSentViaContextDetector : Detector(), SourceCodeScanner {
val evaluator = context.evaluator
if (evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "`Context.${method.name}()` should be replaced with " +
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "`Context.${method.name}()` should be replaced with " +
"`BroadcastSender.${method.name}()`"
)
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
index bab76ab4bce2..14099ebef56c 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedMainThreadDetector.kt
@@ -38,10 +38,9 @@ class NonInjectedMainThreadDetector : Detector(), SourceCodeScanner {
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Replace with injected `@Main Executor`."
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "Replace with injected `@Main Executor`."
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
index b62290025437..aa4b2f766bf0 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/NonInjectedServiceDetector.kt
@@ -44,11 +44,11 @@ class NonInjectedServiceDetector : Detector(), SourceCodeScanner {
method.containingClass?.qualifiedName == CLASS_CONTEXT
) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Use `@Inject` to get system-level service handles instead of " +
- "`Context.getSystemService()`"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message =
+ "Use `@Inject` to get system-level service handles instead of " +
+ "`Context.getSystemService()`"
)
} else if (
evaluator.isStatic(method) &&
@@ -56,10 +56,10 @@ class NonInjectedServiceDetector : Detector(), SourceCodeScanner {
method.containingClass?.qualifiedName == "android.accounts.AccountManager"
) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Replace `AccountManager.get()` with an injected instance of `AccountManager`"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message =
+ "Replace `AccountManager.get()` with an injected instance of `AccountManager`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
index 4ba3afc7f7e2..5840e8f8dfb6 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt
@@ -38,10 +38,10 @@ class RegisterReceiverViaContextDetector : Detector(), SourceCodeScanner {
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (context.evaluator.isMemberInSubClassOf(method, CLASS_CONTEXT)) {
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "Register `BroadcastReceiver` using `BroadcastDispatcher` instead of `Context`"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "Register `BroadcastReceiver` using `BroadcastDispatcher` instead " +
+ "of `Context`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
index 7be21a512f89..b15a41b226df 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SlowUserQueryDetector.kt
@@ -46,10 +46,10 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner {
method.containingClass?.qualifiedName == "android.app.ActivityManager"
) {
context.report(
- ISSUE_SLOW_USER_ID_QUERY,
- method,
- context.getNameLocation(node),
- "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`"
+ issue = ISSUE_SLOW_USER_ID_QUERY,
+ location = context.getNameLocation(node),
+ message =
+ "Use `UserTracker.getUserId()` instead of `ActivityManager.getCurrentUser()`"
)
}
if (
@@ -58,10 +58,9 @@ class SlowUserQueryDetector : Detector(), SourceCodeScanner {
method.containingClass?.qualifiedName == "android.os.UserManager"
) {
context.report(
- ISSUE_SLOW_USER_INFO_QUERY,
- method,
- context.getNameLocation(node),
- "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`"
+ issue = ISSUE_SLOW_USER_INFO_QUERY,
+ location = context.getNameLocation(node),
+ message = "Use `UserTracker.getUserInfo()` instead of `UserManager.getUserInfo()`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
index 4b9aa13c0240..bf025894d66f 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt
@@ -44,10 +44,9 @@ class SoftwareBitmapDetector : Detector(), SourceCodeScanner {
val evaluator = context.evaluator
if (evaluator.isMemberInClass(referenced as? PsiField, "android.graphics.Bitmap.Config")) {
context.report(
- ISSUE,
- referenced,
- context.getNameLocation(reference),
- "Replace software bitmap with `Config.HARDWARE`"
+ issue = ISSUE,
+ location = context.getNameLocation(reference),
+ message = "Replace software bitmap with `Config.HARDWARE`"
)
}
}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
index 1db072548a76..22f15bdcb5bd 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/StaticSettingsProviderDetector.kt
@@ -66,10 +66,9 @@ class StaticSettingsProviderDetector : Detector(), SourceCodeScanner {
val subclassName = className.substring(CLASS_SETTINGS.length + 1)
context.report(
- ISSUE,
- method,
- context.getNameLocation(node),
- "`@Inject` a ${subclassName}Settings instead"
+ issue = ISSUE,
+ location = context.getNameLocation(node),
+ message = "`@Inject` a ${subclassName}Settings instead"
)
}
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
index c35ac61a6543..426211e0f327 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BindServiceOnMainThreadDetectorTest.kt
@@ -126,6 +126,32 @@ class BindServiceOnMainThreadDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressUnbindService() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.content.ServiceConnection;
+
+ @SuppressLint("BindServiceOnMainThread")
+ public class TestClass {
+ public void unbind(Context context, ServiceConnection connection) {
+ context.unbindService(connection);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BindServiceOnMainThreadDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testWorkerMethod() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
index 376acb56fac9..30b68f7e7a75 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/BroadcastSentViaContextDetectorTest.kt
@@ -129,6 +129,34 @@ class BroadcastSentViaContextDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressSendBroadcastInActivity() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.app.Activity;
+ import android.os.UserHandle;
+
+ public class TestClass {
+ @SuppressWarnings("BroadcastSentViaContext")
+ public void send(Activity activity) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ activity.sendBroadcastAsUser(intent, UserHandle.ALL, "permission");
+ }
+
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(BroadcastSentViaContextDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testSendBroadcastInBroadcastSender() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
index 301c338f9b42..ed3d14a1f33f 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedMainThreadDetectorTest.kt
@@ -61,6 +61,32 @@ class NonInjectedMainThreadDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressGetMainThreadHandler() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.Handler;
+
+ @SuppressWarnings("NonInjectedMainThread")
+ public class TestClass {
+ public void test(Context context) {
+ Handler mainThreadHandler = context.getMainThreadHandler();
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedMainThreadDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testGetMainLooper() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
index 0a74bfcfee57..846129aa12c1 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/NonInjectedServiceDetectorTest.kt
@@ -91,6 +91,32 @@ class NonInjectedServiceDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressGetServiceWithClass() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.Context;
+ import android.os.UserManager;
+
+ public class TestClass {
+ @SuppressLint("NonInjectedService")
+ public void getSystemServiceWithoutDagger(Context context) {
+ context.getSystemService(UserManager.class);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(NonInjectedServiceDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testGetAccountManager() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
index 9ed7aa029b1d..0ac8f8e7c672 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/RegisterReceiverViaContextDetectorTest.kt
@@ -63,6 +63,34 @@ class RegisterReceiverViaContextDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressRegisterReceiver() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.content.BroadcastReceiver;
+ import android.content.Context;
+ import android.content.IntentFilter;
+
+ @SuppressWarnings("RegisterReceiverViaContext")
+ public class TestClass {
+ public void bind(Context context, BroadcastReceiver receiver,
+ IntentFilter filter) {
+ context.registerReceiver(receiver, filter, 0);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(RegisterReceiverViaContextDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testRegisterReceiverAsUser() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
index 54cac7b35598..34a424918a79 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SlowUserQueryDetectorTest.kt
@@ -76,7 +76,7 @@ class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
import android.os.UserManager;
public class TestClass {
- public void slewlyGetUserInfo(UserManager userManager) {
+ public void slowlyGetUserInfo(UserManager userManager) {
userManager.getUserInfo();
}
}
@@ -101,6 +101,34 @@ class SlowUserQueryDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressGetUserInfo() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.os.UserManager;
+
+ public class TestClass {
+ @SuppressWarnings("SlowUserInfoQuery")
+ public void slowlyGetUserInfo(UserManager userManager) {
+ userManager.getUserInfo();
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(
+ SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY,
+ SlowUserQueryDetector.ISSUE_SLOW_USER_INFO_QUERY
+ )
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testUserTrackerGetUserId() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
index c632636eb9c8..34becc6a5b04 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SoftwareBitmapDetectorTest.kt
@@ -63,6 +63,31 @@ class SoftwareBitmapDetectorTest : SystemUILintDetectorTest() {
}
@Test
+ fun testSuppressSoftwareBitmap() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ import android.graphics.Bitmap;
+
+ @SuppressWarnings("SoftwareBitmap")
+ public class TestClass {
+ public void test() {
+ Bitmap.createBitmap(300, 300, Bitmap.Config.RGB_565);
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(SoftwareBitmapDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
fun testHardwareBitmap() {
lint()
.files(
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
index b83ed7067bc3..efe4c90ec44f 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/StaticSettingsProviderDetectorTest.kt
@@ -28,7 +28,7 @@ class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
override fun getIssues(): List<Issue> = listOf(StaticSettingsProviderDetector.ISSUE)
@Test
- fun testGetServiceWithString() {
+ fun testSuppressGetServiceWithString() {
lint()
.files(
TestFiles.java(
@@ -204,5 +204,34 @@ class StaticSettingsProviderDetectorTest : SystemUILintDetectorTest() {
)
}
+ @Test
+ fun testGetServiceWithString() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+
+ import android.provider.Settings;
+ import android.provider.Settings.Global;
+ import android.provider.Settings.Secure;
+
+ public class TestClass {
+ @SuppressWarnings("StaticSettingsProvider")
+ public void getSystemServiceWithoutDagger(Context context) {
+ final ContentResolver cr = mContext.getContentResolver();
+ Global.getFloat(cr, Settings.Global.UNLOCK_SOUND);
+ }
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(StaticSettingsProviderDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
private val stubs = androidStubs
}
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index a3b4b385f5bd..69767867ebd7 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -25,9 +25,15 @@
-keep class ** extends androidx.preference.PreferenceFragment
-keep class com.android.systemui.tuner.*
+
+# The plugins and animation subpackages both act as shared libraries that might be referenced in
+# dynamically-loaded plugin APKs.
-keep class com.android.systemui.plugins.** {
*;
}
+-keep class !com.android.systemui.animation.R$**,com.android.systemui.animation.** {
+ *;
+}
-keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
*;
}
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index c8ba237088f2..a948c04020f3 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -23,7 +23,7 @@
<string name="keyguard_enter_your_pin" msgid="5429932527814874032">"Enter your PIN"</string>
<string name="keyguard_enter_your_pattern" msgid="351503370332324745">"Enter your pattern"</string>
<string name="keyguard_enter_your_password" msgid="7225626204122735501">"Enter your password"</string>
- <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Invalid card."</string>
+ <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Invalid Card."</string>
<string name="keyguard_charged" msgid="5478247181205188995">"Charged"</string>
<string name="keyguard_plugged_in_wireless" msgid="2537874724955057383">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging wirelessly"</string>
<string name="keyguard_plugged_in_dock" msgid="2122073051904360987">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Charging"</string>
@@ -70,7 +70,7 @@
<string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"Incorrect SIM PIN code you must now contact your carrier to unlock your device."</string>
<string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{Incorrect SIM PIN code, you have # remaining attempt before you must contact your carrier to unlock your device.}other{Incorrect SIM PIN code, you have # remaining attempts. }}"</string>
<string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"SIM is unusable. Contact your carrier."</string>
- <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code; you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string>
+ <string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code, you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string>
<string name="kg_password_pin_failed" msgid="5136259126330604009">"SIM PIN operation failed!"</string>
<string name="kg_password_puk_failed" msgid="6778867411556937118">"SIM PUK operation failed!"</string>
<string name="accessibility_ime_switch_button" msgid="9082358310194861329">"Switch input method"</string>
@@ -83,12 +83,12 @@
<string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string>
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
- <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
+ <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognized"</string>
<string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"To use Face Unlock, turn on camera access in Settings"</string>
<string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Enter SIM PIN. You have # remaining attempt before you must contact your carrier to unlock your device.}other{Enter SIM PIN. You have # remaining attempts.}}"</string>
<string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{SIM is now disabled. Enter PUK code to continue. You have # remaining attempt before SIM becomes permanently unusable. Contact carrier for details.}other{SIM is now disabled. Enter PUK code to continue. You have # remaining attempts before SIM becomes permanently unusable. Contact carrier for details.}}"</string>
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
- <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
+ <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
<string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string>
</resources>
diff --git a/packages/SystemUI/res/drawable/ic_watch.xml b/packages/SystemUI/res/drawable/ic_watch.xml
new file mode 100644
index 000000000000..8ff880cef029
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_watch.xml
@@ -0,0 +1,29 @@
+<?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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M16,0L8,0l-0.95,5.73C5.19,7.19 4,9.45 4,12s1.19,4.81 3.05,6.27L8,24
+ h8l0.96,-5.73C18.81,16.81 20,14.54 20,12s-1.19,-4.81 -3.04,-6.27L16,0z
+ M12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index d27fa192e741..8b8594032816 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -34,30 +34,13 @@
android:paddingTop="@dimen/status_bar_padding_top"
android:layout_alignParentEnd="true"
android:gravity="center_vertical|end" >
- <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+
+ <include
android:id="@+id/user_switcher_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="horizontal"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:background="@drawable/status_bar_user_chip_bg"
- android:visibility="visible" >
- <ImageView android:id="@+id/current_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_keyguard_size"
- android:layout_height="@dimen/multi_user_avatar_keyguard_size"
- android:scaleType="centerInside"
- android:paddingEnd="4dp" />
-
- <TextView android:id="@+id/current_user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- />
- </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+ layout="@layout/status_bar_user_chip_container" />
<FrameLayout android:id="@+id/system_icons_container"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 80e65a3b3295..f7600e606731 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -136,31 +136,12 @@
android:gravity="center_vertical|end"
android:clipChildren="false">
- <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+ <include
android:id="@+id/user_switcher_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:gravity="center"
- android:orientation="horizontal"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:layout_marginEnd="16dp"
- android:background="@drawable/status_bar_user_chip_bg"
- android:visibility="visible" >
- <ImageView android:id="@+id/current_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_keyguard_size"
- android:layout_height="@dimen/multi_user_avatar_keyguard_size"
- android:scaleType="centerInside"
- android:paddingEnd="4dp" />
-
- <TextView android:id="@+id/current_user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- />
- </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+ layout="@layout/status_bar_user_chip_container" />
<include layout="@layout/system_icons" />
</com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
new file mode 100644
index 000000000000..b374074958cb
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml
@@ -0,0 +1,40 @@
+<?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.
+ -->
+
+<com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/user_switcher_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin"
+ android:background="@drawable/status_bar_user_chip_bg"
+ android:visibility="visible" >
+ <ImageView android:id="@+id/current_user_avatar"
+ android:layout_width="@dimen/status_bar_user_chip_avatar_size"
+ android:layout_height="@dimen/status_bar_user_chip_avatar_size"
+ android:layout_margin="4dp"
+ android:scaleType="centerInside" />
+
+ <TextView android:id="@+id/current_user_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="8dp"
+ android:textAppearance="@style/TextAppearance.StatusBar.UserChip"
+ />
+</com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 8388b67c1fa1..bafdb11ab211 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -26,12 +26,12 @@
android:fitsSystemWindows="true">
<com.android.systemui.statusbar.BackDropView
- android:id="@+id/backdrop"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- sysui:ignoreRightInset="true"
- >
+ android:id="@+id/backdrop"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ sysui:ignoreRightInset="true"
+ >
<ImageView android:id="@+id/backdrop_back"
android:layout_width="match_parent"
android:scaleType="centerCrop"
@@ -49,7 +49,7 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
- />
+ />
<com.android.systemui.scrim.ScrimView
android:id="@+id/scrim_notifications"
@@ -57,17 +57,17 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
- />
+ />
<com.android.systemui.statusbar.LightRevealScrim
- android:id="@+id/light_reveal_scrim"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:id="@+id/light_reveal_scrim"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
<include layout="@layout/status_bar_expanded"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="invisible" />
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible" />
<include layout="@layout/brightness_mirror_container" />
diff --git a/packages/SystemUI/res/values/bools.xml b/packages/SystemUI/res/values/bools.xml
index 8221d78fbfd7..04fc4b8c45a8 100644
--- a/packages/SystemUI/res/values/bools.xml
+++ b/packages/SystemUI/res/values/bools.xml
@@ -25,6 +25,9 @@
<!-- Whether to enable clipping on Quick Settings -->
<bool name="qs_enable_clipping">true</bool>
+ <!-- Whether to enable clipping on Notification Views -->
+ <bool name="notification_enable_clipping">true</bool>
+
<!-- Whether to enable transparent background for notification scrims -->
<bool name="notification_scrim_transparent">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index f4d802bf745e..7a362040427a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -806,4 +806,24 @@
<!-- Whether the device should display hotspot UI. If true, UI will display only when tethering
is available. If false, UI will never show regardless of tethering availability" -->
<bool name="config_show_wifi_tethering">true</bool>
+
+ <!-- A collection of "slots" for placing quick affordance actions on the lock screen when the
+ device is locked. Each item is a string consisting of two parts, separated by the ':' character.
+ The first part is the unique ID for the slot, it is not a human-visible name, but should still
+ be unique across all slots specified. The second part is the capacity and must be a positive
+ integer; this is how many quick affordance actions that user is allowed to add to the slot. -->
+ <string-array name="config_keyguardQuickAffordanceSlots" translatable="false">
+ <item>bottom_start:1</item>
+ <item>bottom_end:1</item>
+ </string-array>
+
+ <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
+ string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
+ separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
+ default is displayed by System UI as long as the user hasn't made a different choice for that
+ slot. If the user did make a choice, even if the choice is the "None" option, the default is
+ ignored. -->
+ <string-array name="config_keyguardQuickAffordanceDefaults" translatable="false">
+ </string-array>
+
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fbdccff38731..437d89beaa9e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1422,6 +1422,11 @@
<dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
<dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
+ <!-- Status bar user chip -->
+ <dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
+ <dimen name="status_bar_user_chip_end_margin">12dp</dimen>
+ <dimen name="status_bar_user_chip_text_size">12sp</dimen>
+
<!-- Internet panel related dimensions -->
<dimen name="internet_dialog_list_max_height">662dp</dimen>
<!-- The height of the WiFi network in Internet panel. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index eb291fd5f12a..9eafdb959f07 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -439,16 +439,16 @@
<string name="accessibility_battery_level">Battery <xliff:g id="number">%d</xliff:g> percent.</string>
<!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery (not shown on the screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage</string>
+ <string name="accessibility_battery_level_with_estimate">Battery <xliff:g id="percentage" example="95%">%1$d</xliff:g> percent, <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g></string>
<!-- Content description of the battery level icon for accessibility while the device is charging (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_battery_level_charging">Battery charging, <xliff:g id="battery_percentage">%d</xliff:g> percent.</string>
<!-- Content description of the battery level icon for accessibility, with information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent. Charging paused for battery protection.</string>
+ <string name="accessibility_battery_level_charging_paused">Battery <xliff:g id="percentage" example="90%">%d</xliff:g> percent, charging paused for battery protection.</string>
<!-- Content description of the battery level icon for accessibility, including the estimated time remaining before the phone runs out of battery *and* information that the device charging is paused in order to protect the lifetime of the battery (not shown on screen). [CHAR LIMIT=NONE] -->
- <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, about <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g> left based on your usage. Charging paused for battery protection.</string>
+ <string name="accessibility_battery_level_charging_paused_with_estimate">Battery <xliff:g id="percentage" example="90%">%1$d</xliff:g> percent, <xliff:g id="time" example="Until 3:15pm">%2$s</xliff:g>, charging paused for battery protection.</string>
<!-- Content description of overflow icon container of the notifications for accessibility (not shown on the screen)[CHAR LIMIT=NONE] -->
<string name="accessibility_overflow_action">See all notifications</string>
@@ -1996,6 +1996,9 @@
<!-- SysUI Tuner: Summary of no shortcut being selected [CHAR LIMIT=60] -->
<string name="lockscreen_none">None</string>
+ <!-- ClockId to use when none is set by user -->
+ <string name="lockscreen_clock_id_fallback" translatable="false">DEFAULT</string>
+
<!-- SysUI Tuner: Format string for describing launching an app [CHAR LIMIT=60] -->
<string name="tuner_launch_app">Launch <xliff:g id="app" example="Settings">%1$s</xliff:g></string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ae80070dfa97..fe4f639c307e 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -23,6 +23,12 @@
<item name="android:textColor">@color/status_bar_clock_color</item>
</style>
+ <style name="TextAppearance.StatusBar.UserChip" parent="@*android:style/TextAppearance.StatusBar.Icon">
+ <item name="android:textSize">@dimen/status_bar_user_chip_text_size</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ <item name="android:textColor">@color/status_bar_clock_color</item>
+ </style>
+
<style name="TextAppearance.StatusBar.Expanded" parent="@*android:style/TextAppearance.StatusBar">
<item name="android:textColor">?android:attr/textColorTertiary</item>
</style>
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 0b0595f4405f..36ac1ff9ad30 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -34,6 +34,7 @@ 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.PathConfig
import platform.test.screenshot.ScreenshotTestRule
import platform.test.screenshot.getEmulatedDevicePathConfig
import platform.test.screenshot.matchers.BitmapMatcher
@@ -41,13 +42,19 @@ import platform.test.screenshot.matchers.BitmapMatcher
/** A rule for View screenshot diff unit tests. */
class ViewScreenshotTestRule(
emulationSpec: DeviceEmulationSpec,
- private val matcher: BitmapMatcher = UnitTestBitmapMatcher
+ private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
+ pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec),
+ assetsPathRelativeToRepo: String = ""
) : TestRule {
private val colorsRule = MaterialYouColorsRule()
private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
private val screenshotRule =
ScreenshotTestRule(
- SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ if (assetsPathRelativeToRepo.isBlank()) {
+ SystemUIGoldenImagePathManager(pathConfig)
+ } else {
+ SystemUIGoldenImagePathManager(pathConfig, assetsPathRelativeToRepo)
+ }
)
private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
private val delegateRule =
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 601cb66d99c2..5c2c27a36e59 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -41,6 +41,7 @@ open class ClockRegistry(
val isEnabled: Boolean,
userHandle: Int,
defaultClockProvider: ClockProvider,
+ val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
) {
// Usually this would be a typealias, but a SAM provides better java interop
fun interface ClockChangeListener {
@@ -69,10 +70,13 @@ open class ClockRegistry(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
)
- ClockSetting.deserialize(json)?.clockId ?: DEFAULT_CLOCK_ID
+ if (json == null || json.isEmpty()) {
+ return fallbackClockId
+ }
+ ClockSetting.deserialize(json).clockId
} catch (ex: Exception) {
Log.e(TAG, "Failed to parse clock setting", ex)
- DEFAULT_CLOCK_ID
+ fallbackClockId
}
}
set(value) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 599cd23f6616..23a7271afbdb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -142,7 +142,9 @@ class DefaultClockController(
currentColor = color
view.setColors(DOZE_COLOR, color)
- view.animateAppearOnLockscreen()
+ if (!animations.dozeState.isActive) {
+ view.animateAppearOnLockscreen()
+ }
}
}
@@ -197,7 +199,7 @@ class DefaultClockController(
dozeFraction: Float,
foldFraction: Float,
) : ClockAnimations {
- private val dozeState = AnimationState(dozeFraction)
+ internal val dozeState = AnimationState(dozeFraction)
private val foldState = AnimationState(foldFraction)
init {
@@ -238,7 +240,7 @@ class DefaultClockController(
get() = true
}
- private class AnimationState(
+ class AnimationState(
var fraction: Float,
) {
var isActive: Boolean = fraction > 0.5f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
index c2658a9e61b1..f60db2ad2687 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
@@ -108,4 +108,30 @@ object KeyguardQuickAffordanceProviderContract {
const val AFFORDANCE_ID = "affordance_id"
}
}
+
+ /**
+ * Table for flags.
+ *
+ * Flags are key-value pairs.
+ *
+ * Supported operations:
+ * - Query - to know the values of flags, query the [FlagsTable.URI] [Uri]. The result set will
+ * contain rows, each of which with the columns from [FlagsTable.Columns].
+ */
+ object FlagsTable {
+ const val TABLE_NAME = "flags"
+ val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
+
+ /**
+ * Flag denoting whether the customizable lock screen quick affordances feature is enabled.
+ */
+ const val FLAG_NAME_FEATURE_ENABLED = "is_feature_enabled"
+
+ object Columns {
+ /** String. Unique ID for the flag. */
+ const val NAME = "name"
+ /** Int. Value of the flag. `1` means `true` and `0` means `false`. */
+ const val VALUE = "value"
+ }
+ }
}
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 a790d89ac1ae..f45887cf7630 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
@@ -71,14 +71,20 @@ public class PreviewPositionHelper {
int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
RectF thumbnailClipHint = new RectF();
- float scaledTaskbarSize = 0;
+ float scaledTaskbarSize;
+ float canvasScreenRatio;
if (mSplitBounds != null) {
float fullscreenTaskWidth;
float fullscreenTaskHeight;
- float canvasScreenRatio;
float taskPercent;
- if (!mSplitBounds.appsStackedVertically) {
+ if (mSplitBounds.appsStackedVertically) {
+ taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
+ ? mSplitBounds.topTaskPercent
+ : (1 - (mSplitBounds.topTaskPercent + mSplitBounds.dividerHeightPercent));
+ fullscreenTaskHeight = screenHeightPx * taskPercent;
+ canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
+ } else {
// For landscape, scale the width
taskPercent = mDesiredStagePosition == STAGE_POSITION_TOP_OR_LEFT
? mSplitBounds.leftTaskPercent
@@ -86,17 +92,12 @@ public class PreviewPositionHelper {
// Scale landscape width to that of actual screen
fullscreenTaskWidth = screenWidthPx * taskPercent;
canvasScreenRatio = canvasWidth / fullscreenTaskWidth;
- } else {
- taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
- ? mSplitBounds.leftTaskPercent
- : (1 - (mSplitBounds.leftTaskPercent + mSplitBounds.dividerWidthPercent));
- // Scale landscape width to that of actual screen
- fullscreenTaskHeight = screenHeightPx * taskPercent;
- canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
}
- scaledTaskbarSize = taskbarSize * canvasScreenRatio;
- thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
+ } else {
+ canvasScreenRatio = (float) canvasWidth / screenWidthPx;
}
+ scaledTaskbarSize = taskbarSize * canvasScreenRatio;
+ thumbnailClipHint.bottom = isTablet ? scaledTaskbarSize : 0;
float scale = thumbnailData.scale;
final float thumbnailScale;
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index 74519c21820b..05372fec7211 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -22,7 +22,11 @@ object FlagsFactory {
private val flagMap = mutableMapOf<String, Flag<*>>()
val knownFlags: Map<String, Flag<*>>
- get() = flagMap
+ get() {
+ // We need to access Flags in order to initialize our map.
+ assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+ return flagMap
+ }
fun unreleasedFlag(
id: Int,
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 7b216017df7d..8323d0971ad7 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -34,6 +34,9 @@ abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags
+ @Binds
+ abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter
+
@Module
companion object {
@JvmStatic
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index 89c0786af6e3..27c5699df70f 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -22,7 +22,11 @@ object FlagsFactory {
private val flagMap = mutableMapOf<String, Flag<*>>()
val knownFlags: Map<String, Flag<*>>
- get() = flagMap
+ get() {
+ // We need to access Flags in order to initialize our map.
+ assert(flagMap.contains(Flags.TEAMFOOD.name)) { "Where is teamfood?" }
+ return flagMap
+ }
fun unreleasedFlag(
id: Int,
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index aef887667527..87beff76290d 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -27,4 +27,7 @@ import dagger.Module
abstract class FlagsModule {
@Binds
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
+
+ @Binds
+ abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 8fa7b11e2664..2b660dee4f16 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -21,7 +21,6 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.media.AudioManager;
import android.os.SystemClock;
-import android.service.trust.TrustAgentService;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.MathUtils;
@@ -68,30 +67,24 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
private final KeyguardUpdateMonitorCallback mUpdateCallback =
new KeyguardUpdateMonitorCallback() {
@Override
- public void onTrustGrantedWithFlags(int flags, int userId, String message) {
- if (userId != KeyguardUpdateMonitor.getCurrentUser()) return;
- boolean bouncerVisible = mView.isVisibleToUser();
- boolean temporaryAndRenewable =
- (flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)
- != 0;
- boolean initiatedByUser =
- (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
- boolean dismissKeyguard =
- (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
-
- if (initiatedByUser || dismissKeyguard) {
- if ((mViewMediatorCallback.isScreenOn() || temporaryAndRenewable)
- && (bouncerVisible || dismissKeyguard)) {
- if (!bouncerVisible) {
- // The trust agent dismissed the keyguard without the user proving
- // that they are present (by swiping up to show the bouncer). That's
- // fine if the user proved presence via some other way to the trust
- //agent.
- Log.i(TAG, "TrustAgent dismissed Keyguard.");
- }
- mSecurityCallback.dismiss(false /* authenticated */, userId,
- /* bypassSecondaryLockScreen */ false, SecurityMode.Invalid);
- } else {
+ public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+ TrustGrantFlags flags, String message) {
+ if (dismissKeyguard) {
+ if (!mView.isVisibleToUser()) {
+ // The trust agent dismissed the keyguard without the user proving
+ // that they are present (by swiping up to show the bouncer). That's
+ // fine if the user proved presence via some other way to the trust
+ // agent.
+ Log.i(TAG, "TrustAgent dismissed Keyguard.");
+ }
+ mSecurityCallback.dismiss(
+ false /* authenticated */,
+ KeyguardUpdateMonitor.getCurrentUser(),
+ /* bypassSecondaryLockScreen */ false,
+ SecurityMode.Invalid
+ );
+ } else {
+ if (flags.isInitiatedByUser() || flags.dismissKeyguardRequested()) {
mViewMediatorCallback.playTrustedSound();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index a0206f1f1e70..819768544b0c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -50,10 +50,9 @@ data class KeyguardFaceListenModel(
override val listening: Boolean,
// keep sorted
val authInterruptActive: Boolean,
- val becauseCannotSkipBouncer: Boolean,
val biometricSettingEnabledForUser: Boolean,
val bouncerFullyShown: Boolean,
- val faceAuthenticated: Boolean,
+ val faceAndFpNotAuthenticated: Boolean,
val faceDisabled: Boolean,
val faceLockedOut: Boolean,
val fpLockedOut: Boolean,
@@ -67,7 +66,9 @@ data class KeyguardFaceListenModel(
val secureCameraLaunched: Boolean,
val switchingUser: Boolean,
val udfpsBouncerShowing: Boolean,
-) : KeyguardListenModel()
+ val udfpsFingerDown: Boolean,
+ val userNotTrustedOrDetectionIsNeeded: Boolean,
+ ) : KeyguardListenModel()
/**
* Verbose debug information associated with [KeyguardUpdateMonitor.shouldTriggerActiveUnlock].
*/
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index d694dc0d7bf6..ce22a81befb5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -472,10 +472,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
FACE_AUTH_TRIGGERED_TRUST_DISABLED);
}
- String message = null;
- if (KeyguardUpdateMonitor.getCurrentUser() == userId) {
- final boolean userHasTrust = getUserHasTrust(userId);
- if (userHasTrust && trustGrantedMessages != null) {
+ if (enabled) {
+ String message = null;
+ if (KeyguardUpdateMonitor.getCurrentUser() == userId
+ && trustGrantedMessages != null) {
+ // Show the first non-empty string provided by a trust agent OR intentionally pass
+ // an empty string through (to prevent the default trust agent string from showing)
for (String msg : trustGrantedMessages) {
message = msg;
if (!TextUtils.isEmpty(message)) {
@@ -483,21 +485,39 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
}
+
+ mLogger.logTrustGrantedWithFlags(flags, userId, message);
+ if (userId == getCurrentUser()) {
+ final TrustGrantFlags trustGrantFlags = new TrustGrantFlags(flags);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onTrustGrantedForCurrentUser(
+ shouldDismissKeyguardOnTrustGrantedWithCurrentUser(trustGrantFlags),
+ trustGrantFlags, message);
+ }
+ }
+ }
}
+
mLogger.logTrustChanged(wasTrusted, enabled, userId);
- if (message != null) {
- mLogger.logShowTrustGrantedMessage(message.toString());
- }
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
cb.onTrustChanged(userId);
- if (enabled) {
- cb.onTrustGrantedWithFlags(flags, userId, message);
- }
}
}
+ }
+ /**
+ * Whether the trust granted call with its passed flags should dismiss keyguard.
+ * It's assumed that the trust was granted for the current user.
+ */
+ private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) {
+ final boolean isBouncerShowing = mPrimaryBouncerIsOrWillBeShowing || mUdfpsBouncerShowing;
+ return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested())
+ && (mDeviceInteractive || flags.temporaryAndRenewable())
+ && (isBouncerShowing || flags.dismissKeyguardRequested());
}
@Override
@@ -774,9 +794,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
// Don't send cancel if authentication succeeds
mFingerprintCancelSignal = null;
+ mLogger.logFingerprintSuccess(userId, isStrongBiometric);
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_FP_AUTHENTICATED);
- mLogger.logFingerprintSuccess(userId, isStrongBiometric);
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -2691,9 +2711,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean canBypass = mKeyguardBypassController != null
&& mKeyguardBypassController.canBypass();
// There's no reason to ask the HAL for authentication when the user can dismiss the
- // bouncer, unless we're bypassing and need to auto-dismiss the lock screen even when
- // TrustAgents or biometrics are keeping the device unlocked.
- final boolean becauseCannotSkipBouncer = !getUserCanSkipBouncer(user) || canBypass;
+ // bouncer because the user is trusted, unless we're bypassing and need to auto-dismiss
+ // the lock screen even when TrustAgents are keeping the device unlocked.
+ final boolean userNotTrustedOrDetectionIsNeeded = !getUserHasTrust(user) || canBypass;
// Scan even when encrypted or timeout to show a preemptive bouncer when bypassing.
// Lock-down mode shouldn't scan, since it is more explicit.
@@ -2710,11 +2730,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
strongAuthAllowsScanning = false;
}
- // If the face has recently been authenticated do not attempt to authenticate again.
- final boolean faceAuthenticated = getIsFaceAuthenticated();
+ // If the face or fp has recently been authenticated do not attempt to authenticate again.
+ final boolean faceAndFpNotAuthenticated = !getUserUnlockedWithBiometric(user);
final boolean faceDisabledForUser = isFaceDisabled(user);
final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
+ final boolean isUdfpsFingerDown = mAuthController.isUdfpsFingerDown();
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
@@ -2724,13 +2745,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| mOccludingAppRequestingFace
|| awakeKeyguard
|| shouldListenForFaceAssistant
- || mAuthController.isUdfpsFingerDown()
+ || isUdfpsFingerDown
|| mUdfpsBouncerShowing)
- && !mSwitchingUser && !faceDisabledForUser && becauseCannotSkipBouncer
+ && !mSwitchingUser && !faceDisabledForUser && userNotTrustedOrDetectionIsNeeded
&& !mKeyguardGoingAway && biometricEnabledForUser
&& strongAuthAllowsScanning && mIsPrimaryUser
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
- && !faceAuthenticated
+ && faceAndFpNotAuthenticated
&& !mGoingToSleep
// We only care about fp locked out state and not face because we still trigger
// face auth even when face is locked out to show the user a message that face
@@ -2744,10 +2765,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
user,
shouldListen,
mAuthInterruptActive,
- becauseCannotSkipBouncer,
biometricEnabledForUser,
mPrimaryBouncerFullyShown,
- faceAuthenticated,
+ faceAndFpNotAuthenticated,
faceDisabledForUser,
isFaceLockedOut(),
fpLockedOut,
@@ -2760,7 +2780,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
strongAuthAllowsScanning,
mSecureCameraLaunched,
mSwitchingUser,
- mUdfpsBouncerShowing));
+ mUdfpsBouncerShowing,
+ isUdfpsFingerDown,
+ userNotTrustedOrDetectionIsNeeded));
return shouldListen;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index c5142f309a46..1d58fc9cf94b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -19,6 +19,7 @@ import android.hardware.biometrics.BiometricSourceType;
import android.telephony.TelephonyManager;
import android.view.WindowManagerPolicyConstants;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -175,11 +176,13 @@ public class KeyguardUpdateMonitorCallback {
/**
* Called after trust was granted.
- * @param userId of the user that has been granted trust
+ * @param dismissKeyguard whether the keyguard should be dismissed as a result of the
+ * trustGranted
* @param message optional message the trust agent has provided to show that should indicate
* why trust was granted.
*/
- public void onTrustGrantedWithFlags(int flags, int userId, @Nullable String message) { }
+ public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+ @NonNull TrustGrantFlags flags, @Nullable String message) { }
/**
* Called when a biometric has been acquired.
diff --git a/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java b/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java
new file mode 100644
index 000000000000..d33732cd5536
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/TrustGrantFlags.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard;
+
+import android.service.trust.TrustAgentService;
+
+import java.util.Objects;
+
+/**
+ * Translating {@link android.service.trust.TrustAgentService.GrantTrustFlags} to a more
+ * parsable object. These flags are requested by a TrustAgent.
+ */
+public class TrustGrantFlags {
+ final int mFlags;
+
+ public TrustGrantFlags(int flags) {
+ this.mFlags = flags;
+ }
+
+ /** {@link TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER} */
+ public boolean isInitiatedByUser() {
+ return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
+ }
+
+ /**
+ * Trust agent is requesting to dismiss the keyguard.
+ * See {@link TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD}.
+ *
+ * This does not guarantee that the keyguard is dismissed.
+ * KeyguardUpdateMonitor makes the final determination whether the keyguard should be dismissed.
+ * {@link KeyguardUpdateMonitorCallback#onTrustGrantedForCurrentUser(
+ * boolean, TrustGrantFlags, String).
+ */
+ public boolean dismissKeyguardRequested() {
+ return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
+ }
+
+ /** {@link TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE} */
+ public boolean temporaryAndRenewable() {
+ return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0;
+ }
+
+ /** {@link TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE} */
+ public boolean displayMessage() {
+ return (mFlags & TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof TrustGrantFlags)) {
+ return false;
+ }
+
+ return ((TrustGrantFlags) o).mFlags == this.mFlags;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFlags);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ sb.append(mFlags);
+ sb.append("]=");
+
+ if (isInitiatedByUser()) {
+ sb.append("initiatedByUser|");
+ }
+ if (dismissKeyguardRequested()) {
+ sb.append("dismissKeyguard|");
+ }
+ if (temporaryAndRenewable()) {
+ sb.append("temporaryAndRenewable|");
+ }
+ if (displayMessage()) {
+ sb.append("displayMessage|");
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 9767313331d3..b514f60efc7d 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.os.Handler;
import android.os.UserHandle;
+import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
@@ -50,6 +51,7 @@ public abstract class ClockRegistryModule {
handler,
featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS),
UserHandle.USER_ALL,
- defaultClockProvider);
+ defaultClockProvider,
+ context.getString(R.string.lockscreen_clock_id_fallback));
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
index 8fc86004c400..a7d4455b43c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewModule.java
@@ -21,10 +21,7 @@ import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
@@ -50,10 +47,4 @@ public abstract class KeyguardStatusBarViewModule {
static StatusBarUserSwitcherContainer getUserSwitcherContainer(KeyguardStatusBarView view) {
return view.findViewById(R.id.user_switcher_container);
}
-
- /** */
- @Binds
- @KeyguardStatusBarViewScope
- abstract StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
- StatusBarUserSwitcherControllerImpl controller);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 81b8dfed36a8..676370093aee 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -25,6 +25,7 @@ import com.android.keyguard.ActiveUnlockConfig
import com.android.keyguard.FaceAuthUiEvent
import com.android.keyguard.KeyguardListenModel
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.TrustGrantFlags
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel
import com.android.systemui.plugins.log.LogLevel.DEBUG
@@ -368,12 +369,16 @@ class KeyguardUpdateMonitorLogger @Inject constructor(
}, { "reportUserRequestedUnlock origin=$str1 reason=$str2 dismissKeyguard=$bool1" })
}
- fun logShowTrustGrantedMessage(
+ fun logTrustGrantedWithFlags(
+ flags: Int,
+ userId: Int,
message: String?
) {
logBuffer.log(TAG, DEBUG, {
+ int1 = flags
+ int2 = userId
str1 = message
- }, { "showTrustGrantedMessage message$str1" })
+ }, { "trustGrantedWithFlags[user=$int2] flags=${TrustGrantFlags(int1)} message=$str1" })
}
fun logTrustChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
new file mode 100644
index 000000000000..794eba4d8de9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/TEST_MAPPING
@@ -0,0 +1,16 @@
+{
+ "presubmit": [
+ {
+ // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
+ "name": "SystemUIGoogleBiometricsScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
index ad966125b9e8..bdad41348c95 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationView.java
@@ -37,6 +37,9 @@ public abstract class UdfpsAnimationView extends FrameLayout {
private float mDialogSuggestedAlpha = 1f;
private float mNotificationShadeExpansion = 0f;
+ // Used for Udfps ellipse detection when flag is true, set by AnimationViewController
+ boolean mUseExpandedOverlay = false;
+
// mAlpha takes into consideration the status bar expansion amount and dialog suggested alpha
private int mAlpha;
boolean mPauseAuth;
@@ -118,6 +121,24 @@ public abstract class UdfpsAnimationView extends FrameLayout {
}
/**
+ * Converts coordinates of RectF relative to the screen to coordinates relative to this view.
+ *
+ * @param bounds RectF based off screen coordinates in current orientation
+ */
+ RectF getBoundsRelativeToView(RectF bounds) {
+ int[] pos = getLocationOnScreen();
+
+ RectF output = new RectF(
+ bounds.left - pos[0],
+ bounds.top - pos[1],
+ bounds.right - pos[0],
+ bounds.bottom - pos[1]
+ );
+
+ return output;
+ }
+
+ /**
* Set the suggested alpha based on whether a dialog was recently shown or hidden.
* @param dialogSuggestedAlpha value from 0f to 1f.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 5469d298227c..1d4281fbf451 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -166,6 +166,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
// The current request from FingerprintService. Null if no current request.
@Nullable UdfpsControllerOverlay mOverlay;
+ @Nullable private UdfpsEllipseDetection mUdfpsEllipseDetection;
// The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when
// to turn off high brightness mode. To get around this limitation, the state of the AOD
@@ -354,6 +355,10 @@ public class UdfpsController implements DozeReceiver, Dumpable {
if (!mOverlayParams.equals(overlayParams)) {
mOverlayParams = overlayParams;
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+ mUdfpsEllipseDetection.updateOverlayParams(overlayParams);
+ }
+
final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateBouncer();
// When the bounds change it's always necessary to re-create the overlay's window with
@@ -493,8 +498,23 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mVelocityTracker.clear();
}
- boolean withinSensorArea =
+ boolean withinSensorArea;
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+ // Ellipse detection
+ withinSensorArea = mUdfpsEllipseDetection.isGoodEllipseOverlap(event);
+ } else {
+ // Centroid with expanded overlay
+ withinSensorArea =
+ isWithinSensorArea(udfpsView, event.getRawX(),
+ event.getRawY(), fromUdfpsView);
+ }
+ } else {
+ // Centroid with sensor sized view
+ withinSensorArea =
isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView);
+ }
+
if (withinSensorArea) {
Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0);
Log.v(TAG, "onTouch | action down");
@@ -525,9 +545,25 @@ public class UdfpsController implements DozeReceiver, Dumpable {
? event.getPointerId(0)
: event.findPointerIndex(mActivePointerId);
if (idx == event.getActionIndex()) {
- boolean actionMoveWithinSensorArea =
- isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx),
- fromUdfpsView);
+ boolean actionMoveWithinSensorArea;
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+ // Ellipse detection
+ actionMoveWithinSensorArea =
+ mUdfpsEllipseDetection.isGoodEllipseOverlap(event);
+ } else {
+ // Centroid with expanded overlay
+ actionMoveWithinSensorArea =
+ isWithinSensorArea(udfpsView, event.getRawX(idx),
+ event.getRawY(idx), fromUdfpsView);
+ }
+ } else {
+ // Centroid with sensor sized view
+ actionMoveWithinSensorArea =
+ isWithinSensorArea(udfpsView, event.getX(idx),
+ event.getY(idx), fromUdfpsView);
+ }
+
if ((fromUdfpsView || actionMoveWithinSensorArea)
&& shouldTryToDismissKeyguard()) {
Log.v(TAG, "onTouch | dismiss keyguard ACTION_MOVE");
@@ -725,6 +761,10 @@ public class UdfpsController implements DozeReceiver, Dumpable {
udfpsHapticsSimulator.setUdfpsController(this);
udfpsShell.setUdfpsOverlayController(mUdfpsOverlayController);
+
+ if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) {
+ mUdfpsEllipseDetection = new UdfpsEllipseDetection(mOverlayParams);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 0bb24f8663ec..8db4927ee059 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -49,6 +49,7 @@ import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionStateManager
@@ -103,6 +104,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
private set
private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams()
+ private var sensorBounds: Rect = Rect()
private var overlayTouchListener: TouchExplorationStateChangeListener? = null
@@ -120,6 +122,10 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
// Avoid announcing window title.
accessibilityTitle = " "
+
+ if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+ }
}
/** A helper if the [requestReason] was due to enrollment. */
@@ -160,6 +166,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean {
if (overlayView == null) {
overlayParams = params
+ sensorBounds = Rect(params.sensorBounds)
try {
overlayView = (inflater.inflate(
R.layout.udfps_view, null, false
@@ -178,6 +185,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
}
windowManager.addView(this, coreLayoutParams.updateDimensions(animation))
+ sensorRect = sensorBounds
touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled
overlayTouchListener = TouchExplorationStateChangeListener {
if (accessibilityManager.isTouchExplorationEnabled) {
@@ -194,6 +202,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
overlayTouchListener!!
)
overlayTouchListener?.onTouchExplorationStateChanged(true)
+ useExpandedOverlay = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
}
} catch (e: RuntimeException) {
Log.e(TAG, "showUdfpsOverlay | failed to add window", e)
@@ -225,13 +234,14 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
REASON_ENROLL_ENROLLING -> {
UdfpsEnrollViewController(
view.addUdfpsView(R.layout.udfps_enroll_view) {
- updateSensorLocation(overlayParams.sensorBounds)
+ updateSensorLocation(sensorBounds)
},
enrollHelper ?: throw IllegalStateException("no enrollment helper"),
statusBarStateController,
shadeExpansionStateManager,
dialogManager,
dumpManager,
+ featureFlags,
overlayParams.scaleFactor
)
}
@@ -420,7 +430,12 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
}
// Original sensorBounds assume portrait mode.
- val rotatedSensorBounds = Rect(overlayParams.sensorBounds)
+ var rotatedBounds =
+ if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ Rect(overlayParams.overlayBounds)
+ } else {
+ Rect(overlayParams.sensorBounds)
+ }
val rot = overlayParams.rotation
if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
@@ -434,18 +449,27 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
} else {
Log.v(TAG, "Rotate UDFPS bounds " + Surface.rotationToString(rot))
RotationUtils.rotateBounds(
- rotatedSensorBounds,
+ rotatedBounds,
overlayParams.naturalDisplayWidth,
overlayParams.naturalDisplayHeight,
rot
)
+
+ if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ RotationUtils.rotateBounds(
+ sensorBounds,
+ overlayParams.naturalDisplayWidth,
+ overlayParams.naturalDisplayHeight,
+ rot
+ )
+ }
}
}
- x = rotatedSensorBounds.left - paddingX
- y = rotatedSensorBounds.top - paddingY
- height = rotatedSensorBounds.height() + 2 * paddingX
- width = rotatedSensorBounds.width() + 2 * paddingY
+ x = rotatedBounds.left - paddingX
+ y = rotatedBounds.top - paddingY
+ height = rotatedBounds.height() + 2 * paddingX
+ width = rotatedBounds.width() + 2 * paddingY
return this
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt
new file mode 100644
index 000000000000..8ae4775467df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.RotationUtils
+import android.view.MotionEvent
+import kotlin.math.cos
+import kotlin.math.pow
+import kotlin.math.sin
+
+private const val TAG = "UdfpsEllipseDetection"
+
+private const val NEEDED_POINTS = 2
+
+class UdfpsEllipseDetection(overlayParams: UdfpsOverlayParams) {
+ var sensorRect = Rect()
+ var points: Array<Point> = emptyArray()
+
+ init {
+ sensorRect = Rect(overlayParams.sensorBounds)
+
+ points = calculateSensorPoints(sensorRect)
+ }
+
+ fun updateOverlayParams(params: UdfpsOverlayParams) {
+ sensorRect = Rect(params.sensorBounds)
+
+ val rot = params.rotation
+ RotationUtils.rotateBounds(
+ sensorRect,
+ params.naturalDisplayWidth,
+ params.naturalDisplayHeight,
+ rot
+ )
+
+ points = calculateSensorPoints(sensorRect)
+ }
+
+ fun isGoodEllipseOverlap(event: MotionEvent): Boolean {
+ return points.count { checkPoint(event, it) } >= NEEDED_POINTS
+ }
+
+ private fun checkPoint(event: MotionEvent, point: Point): Boolean {
+ // Calculate if sensor point is within ellipse
+ // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE -
+ // yS))^2 / b^2) <= 1
+ val a: Float = cos(event.orientation) * (point.x - event.rawX)
+ val b: Float = sin(event.orientation) * (point.y - event.rawY)
+ val c: Float = sin(event.orientation) * (point.x - event.rawX)
+ val d: Float = cos(event.orientation) * (point.y - event.rawY)
+ val result =
+ (a + b).pow(2) / (event.touchMinor / 2).pow(2) +
+ (c - d).pow(2) / (event.touchMajor / 2).pow(2)
+
+ return result <= 1
+ }
+}
+
+fun calculateSensorPoints(sensorRect: Rect): Array<Point> {
+ val sensorX = sensorRect.centerX()
+ val sensorY = sensorRect.centerY()
+ val cornerOffset: Int = sensorRect.width() / 4
+ val sideOffset: Int = sensorRect.width() / 3
+
+ return arrayOf(
+ Point(sensorX - cornerOffset, sensorY - cornerOffset),
+ Point(sensorX, sensorY - sideOffset),
+ Point(sensorX + cornerOffset, sensorY - cornerOffset),
+ Point(sensorX - sideOffset, sensorY),
+ Point(sensorX, sensorY),
+ Point(sensorX + sideOffset, sensorY),
+ Point(sensorX - cornerOffset, sensorY + cornerOffset),
+ Point(sensorX, sensorY + sideOffset),
+ Point(sensorX + cornerOffset, sensorY + cornerOffset)
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 49e378e4a76f..af7e0b6244df 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -99,12 +99,11 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
mProgressColor = context.getColor(R.color.udfps_enroll_progress);
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
mIsAccessibilityEnabled = am.isTouchExplorationEnabled();
+ mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
if (!mIsAccessibilityEnabled) {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
- mOnFirstBucketFailedColor = context.getColor(R.color.udfps_moving_target_fill_error);
} else {
mHelpColor = context.getColor(R.color.udfps_enroll_progress_help_with_talkback);
- mOnFirstBucketFailedColor = mHelpColor;
}
mCheckmarkDrawable = context.getDrawable(R.drawable.udfps_enroll_checkmark);
mCheckmarkDrawable.mutate();
@@ -197,6 +196,7 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
}
}
+ mShowingHelp = showingHelp;
mRemainingSteps = remainingSteps;
mTotalSteps = totalSteps;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 69c37b2b9a62..87be42c3426e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -18,6 +18,7 @@ package com.android.systemui.biometrics;
import android.content.Context;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
@@ -41,6 +42,9 @@ public class UdfpsEnrollView extends UdfpsAnimationView {
@NonNull private ImageView mFingerprintView;
@NonNull private ImageView mFingerprintProgressView;
+ private LayoutParams mProgressParams;
+ private float mProgressBarRadius;
+
public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mFingerprintDrawable = new UdfpsEnrollDrawable(mContext);
@@ -57,6 +61,32 @@ public class UdfpsEnrollView extends UdfpsAnimationView {
}
@Override
+ void onSensorRectUpdated(RectF bounds) {
+ if (mUseExpandedOverlay) {
+ RectF converted = getBoundsRelativeToView(bounds);
+
+ mProgressParams = new LayoutParams(
+ (int) (converted.width() + mProgressBarRadius * 2),
+ (int) (converted.height() + mProgressBarRadius * 2));
+ mProgressParams.setMargins(
+ (int) (converted.left - mProgressBarRadius),
+ (int) (converted.top - mProgressBarRadius),
+ (int) (converted.right + mProgressBarRadius),
+ (int) (converted.bottom + mProgressBarRadius)
+ );
+
+ mFingerprintProgressView.setLayoutParams(mProgressParams);
+ super.onSensorRectUpdated(converted);
+ } else {
+ super.onSensorRectUpdated(bounds);
+ }
+ }
+
+ void setProgressBarRadius(float radius) {
+ mProgressBarRadius = radius;
+ }
+
+ @Override
public UdfpsDrawable getDrawable() {
return mFingerprintDrawable;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index e01273f2a092..40176654a8ec 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -21,6 +21,8 @@ import android.graphics.PointF;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
@@ -57,6 +59,7 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp
@NonNull ShadeExpansionStateManager shadeExpansionStateManager,
@NonNull SystemUIDialogManager systemUIDialogManager,
@NonNull DumpManager dumpManager,
+ @NonNull FeatureFlags featureFlags,
float scaleFactor) {
super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
dumpManager);
@@ -64,6 +67,11 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp
R.integer.config_udfpsEnrollProgressBar));
mEnrollHelper = enrollHelper;
mView.setEnrollHelper(mEnrollHelper);
+ mView.setProgressBarRadius(mEnrollProgressBarRadius);
+
+ if (featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) {
+ mView.mUseExpandedOverlay = true;
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index bc274a0af95f..339b8cafd8dd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -26,6 +26,7 @@ import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.MathUtils;
import android.view.View;
@@ -75,6 +76,8 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
private int mAnimationType = ANIMATION_NONE;
private boolean mFullyInflated;
+ private LayoutParams mParams;
+
public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mFingerprintDrawable = new UdfpsFpDrawable(context);
@@ -239,6 +242,22 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
updateAlpha();
}
+ @Override
+ void onSensorRectUpdated(RectF bounds) {
+ super.onSensorRectUpdated(bounds);
+
+ if (mUseExpandedOverlay) {
+ mParams = new LayoutParams((int) bounds.width(), (int) bounds.height());
+ RectF converted = getBoundsRelativeToView(bounds);
+ mParams.setMargins(
+ (int) converted.left,
+ (int) converted.top,
+ (int) converted.right,
+ (int) converted.bottom
+ );
+ }
+ }
+
/**
* Animates in the bg protection circle behind the fp icon to highlight the icon.
*/
@@ -277,6 +296,7 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
pw.println(" mUdfpsRequested=" + mUdfpsRequested);
pw.println(" mInterpolatedDarkAmount=" + mInterpolatedDarkAmount);
pw.println(" mAnimationType=" + mAnimationType);
+ pw.println(" mUseExpandedOverlay=" + mUseExpandedOverlay);
}
private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener =
@@ -291,7 +311,12 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
updatePadding();
updateColor();
updateAlpha();
- parent.addView(view);
+
+ if (mUseExpandedOverlay) {
+ parent.addView(view, mParams);
+ } else {
+ parent.addView(view);
+ }
// requires call to invalidate to update the color
mLockScreenFp.addValueCallback(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 91967f95c861..63144fcea761 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -52,7 +52,6 @@ import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
/** Class that coordinates non-HBM animations during keyguard authentication. */
@@ -82,6 +81,8 @@ constructor(
systemUIDialogManager,
dumpManager
) {
+ private val useExpandedOverlay: Boolean =
+ featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
private var showingUdfpsBouncer = false
private var udfpsRequested = false
@@ -233,7 +234,13 @@ constructor(
if (transitionToFullShadeProgress != 0f) {
return
}
- udfpsController.onTouch(event)
+
+ // Forwarding touches not needed with expanded overlay
+ if (useExpandedOverlay) {
+ return
+ } else {
+ udfpsController.onTouch(event)
+ }
}
}
@@ -322,6 +329,7 @@ constructor(
keyguardViewManager.setAlternateBouncer(mAlternateBouncer)
lockScreenShadeTransitionController.udfpsKeyguardViewController = this
activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+ view.mUseExpandedOverlay = useExpandedOverlay
}
override fun onViewDetached() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index a15456d46897..4a8877edfa53 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -20,6 +20,7 @@ import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PointF
+import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import android.util.Log
@@ -38,9 +39,12 @@ class UdfpsView(
attrs: AttributeSet?
) : FrameLayout(context, attrs), DozeReceiver {
+ // Use expanded overlay when feature flag is true, set by UdfpsViewController
+ var useExpandedOverlay: Boolean = false
+
// sensorRect may be bigger than the sensor. True sensor dimensions are defined in
// overlayParams.sensorBounds
- private val sensorRect = RectF()
+ var sensorRect = Rect()
private var mUdfpsDisplayMode: UdfpsDisplayModeProvider? = null
private val debugTextPaint = Paint().apply {
isAntiAlias = true
@@ -92,13 +96,19 @@ class UdfpsView(
val paddingX = animationViewController?.paddingX ?: 0
val paddingY = animationViewController?.paddingY ?: 0
- sensorRect.set(
- paddingX.toFloat(),
- paddingY.toFloat(),
- (overlayParams.sensorBounds.width() + paddingX).toFloat(),
- (overlayParams.sensorBounds.height() + paddingY).toFloat()
- )
- animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+ // Updates sensor rect in relation to the overlay view
+ if (useExpandedOverlay) {
+ animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+ } else {
+ sensorRect.set(
+ paddingX,
+ paddingY,
+ (overlayParams.sensorBounds.width() + paddingX),
+ (overlayParams.sensorBounds.height() + paddingY)
+ )
+
+ animationViewController?.onSensorRectUpdated(RectF(sensorRect))
+ }
}
fun onTouchOutsideView() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index 5110a9cfb33b..6fb8e345322c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -26,6 +26,7 @@ object CredentialPasswordViewBinder {
view: CredentialPasswordView,
host: CredentialView.Host,
viewModel: CredentialViewModel,
+ requestFocusForInput: Boolean,
) {
val imeManager = view.context.getSystemService(InputMethodManager::class.java)!!
@@ -34,8 +35,10 @@ object CredentialPasswordViewBinder {
val onBackInvokedCallback = OnBackInvokedCallback { host.onCredentialAborted() }
view.repeatWhenAttached {
- passwordField.requestFocus()
- passwordField.scheduleShowSoftInput()
+ if (requestFocusForInput) {
+ passwordField.requestFocus()
+ passwordField.scheduleShowSoftInput()
+ }
repeatOnLifecycle(Lifecycle.State.STARTED) {
// observe credential validation attempts and submit/cancel buttons
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
index 4765551df3f0..b692ad35caee 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPatternViewBinder.kt
@@ -9,7 +9,6 @@ import com.android.systemui.biometrics.ui.CredentialPatternView
import com.android.systemui.biometrics.ui.CredentialView
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
/** Sub-binder for the [CredentialPatternView]. */
@@ -30,7 +29,7 @@ object CredentialPatternViewBinder {
viewModel.header.collect { header ->
lockPatternView.setOnPatternListener(
OnPatternDetectedListener { pattern ->
- if (pattern.isPatternLongEnough()) {
+ if (pattern.isPatternTooShort()) {
// Pattern size is less than the minimum
// do not count it as a failed attempt
viewModel.showPatternTooShortError()
@@ -71,5 +70,5 @@ private class OnPatternDetectedListener(
}
}
-private fun List<LockPatternView.Cell>.isPatternLongEnough(): Boolean =
+private fun List<LockPatternView.Cell>.isPatternTooShort(): Boolean =
size < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index fcc948756972..e2d36dc6abe1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -17,7 +17,6 @@ import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -40,6 +39,7 @@ object CredentialViewBinder {
panelViewController: AuthPanelController,
animatePanel: Boolean,
maxErrorDuration: Long = 3_000L,
+ requestFocusForInput: Boolean = true,
) {
val titleView: TextView = view.requireViewById(R.id.title)
val subtitleView: TextView = view.requireViewById(R.id.subtitle)
@@ -110,7 +110,8 @@ object CredentialViewBinder {
// bind the auth widget
when (view) {
- is CredentialPasswordView -> CredentialPasswordViewBinder.bind(view, host, viewModel)
+ is CredentialPasswordView ->
+ CredentialPasswordViewBinder.bind(view, host, viewModel, requestFocusForInput)
is CredentialPatternView -> CredentialPatternViewBinder.bind(view, host, viewModel)
else -> throw IllegalStateException("unexpected view type: ${view.javaClass.name}")
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 115edd115ffe..c6428ef6eb8e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -91,11 +91,12 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
override var currentUserId = userTracker.userId
private set
- private val serviceListingCallback = ServiceListing.Callback {
+ private val serviceListingCallback = ServiceListing.Callback { list ->
+ Log.d(TAG, "ServiceConfig reloaded, count: ${list.size}")
+ val newServices = list.map { ControlsServiceInfo(userTracker.userContext, it) }
+ // After here, `list` is not captured, so we don't risk modifying it outside of the callback
backgroundExecutor.execute {
if (userChangeInProgress.get() > 0) return@execute
- Log.d(TAG, "ServiceConfig reloaded, count: ${it.size}")
- val newServices = it.map { ControlsServiceInfo(userTracker.userContext, it) }
if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
newServices.forEach(ControlsServiceInfo::resolvePanelActivity)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index d3555eec0243..b30e0c22e566 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -17,6 +17,7 @@
package com.android.systemui.dagger;
import com.android.systemui.keyguard.KeyguardQuickAffordanceProvider;
+import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
import dagger.Subcomponent;
@@ -28,6 +29,7 @@ import dagger.Subcomponent;
@Subcomponent(modules = {
DefaultComponentBinder.class,
DependencyProvider.class,
+ NotificationInsetsModule.class,
QsFrameTranslateModule.class,
SystemUIBinder.class,
SystemUIModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index a14b0ee04d8a..6dc4f5c60108 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -28,6 +28,7 @@ import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.people.PeopleProvider;
+import com.android.systemui.statusbar.NotificationInsetsModule;
import com.android.systemui.statusbar.QsFrameTranslateModule;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.FoldStateLogger;
@@ -65,6 +66,7 @@ import dagger.Subcomponent;
@Subcomponent(modules = {
DefaultComponentBinder.class,
DependencyProvider.class,
+ NotificationInsetsModule.class,
QsFrameTranslateModule.class,
SystemUIBinder.class,
SystemUIModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
index d537d4b51b6c..000bbe6afc50 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
@@ -54,6 +54,9 @@ class DemoModeController constructor(
private val receiverMap: Map<String, MutableList<DemoMode>>
init {
+ // Don't persist demo mode across restarts.
+ requestFinishDemoMode()
+
val m = mutableMapOf<String, MutableList<DemoMode>>()
DemoMode.COMMANDS.map { command ->
m.put(command, mutableListOf())
@@ -74,7 +77,6 @@ class DemoModeController constructor(
// content changes to know if the setting turned on or off
tracker.startTracking()
- // TODO: We should probably exit demo mode if we booted up with it on
isInDemoMode = tracker.isInDemoMode
val demoFilter = IntentFilter()
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index b69afeb37371..0c14ed5c2164 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -133,9 +133,9 @@ public class DozeLog implements Dumpable {
/**
* Appends fling event to the logs
*/
- public void traceFling(boolean expand, boolean aboveThreshold, boolean thresholdNeeded,
+ public void traceFling(boolean expand, boolean aboveThreshold,
boolean screenOnFromTouch) {
- mLogger.logFling(expand, aboveThreshold, thresholdNeeded, screenOnFromTouch);
+ mLogger.logFling(expand, aboveThreshold, screenOnFromTouch);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 18c8e01cbf76..b5dbe21a2cf7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -96,13 +96,11 @@ class DozeLogger @Inject constructor(
fun logFling(
expand: Boolean,
aboveThreshold: Boolean,
- thresholdNeeded: Boolean,
screenOnFromTouch: Boolean
) {
buffer.log(TAG, DEBUG, {
bool1 = expand
bool2 = aboveThreshold
- bool3 = thresholdNeeded
bool4 = screenOnFromTouch
}, {
"Fling expand=$bool1 aboveThreshold=$bool2 thresholdNeeded=$bool3 " +
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index b03ae59d0d61..81df4ed51d8d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -337,7 +337,6 @@ public class FeatureFlagsDebug implements FeatureFlags {
Log.i(TAG, "Android Restart Suppressed");
return;
}
- Log.i(TAG, "Restarting Android");
mRestarter.restart();
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
new file mode 100644
index 000000000000..3d9f62768a37
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import javax.inject.Inject
+
+/** Restarts SystemUI when the screen is locked. */
+class FeatureFlagsDebugRestarter
+@Inject
+constructor(
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+ private val systemExitRestarter: SystemExitRestarter,
+) : Restarter {
+
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change")
+ restartNow()
+ }
+ }
+
+ override fun restart() {
+ Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting on next screen off.")
+ if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
+ restartNow()
+ } else {
+ wakefulnessLifecycle.addObserver(observer)
+ }
+ }
+
+ private fun restartNow() {
+ systemExitRestarter.restart()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
new file mode 100644
index 000000000000..a3f0f665c056
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+/** Restarts SystemUI when the device appears idle. */
+class FeatureFlagsReleaseRestarter
+@Inject
+constructor(
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+ private val batteryController: BatteryController,
+ @Background private val bgExecutor: DelayableExecutor,
+ private val systemExitRestarter: SystemExitRestarter
+) : Restarter {
+ var shouldRestart = false
+ var pendingRestart: Runnable? = null
+
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ maybeScheduleRestart()
+ }
+ }
+
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+ maybeScheduleRestart()
+ }
+ }
+
+ override fun restart() {
+ Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting when plugged in and idle.")
+ if (!shouldRestart) {
+ // Don't bother scheduling twice.
+ shouldRestart = true
+ wakefulnessLifecycle.addObserver(observer)
+ batteryController.addCallback(batteryCallback)
+ maybeScheduleRestart()
+ }
+ }
+
+ private fun maybeScheduleRestart() {
+ if (
+ wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
+ ) {
+ if (pendingRestart == null) {
+ pendingRestart = bgExecutor.executeDelayed(this::restartNow, 30L, TimeUnit.SECONDS)
+ }
+ } else if (pendingRestart != null) {
+ pendingRestart?.run()
+ pendingRestart = null
+ }
+ }
+
+ private fun restartNow() {
+ Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
+ systemExitRestarter.restart()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 784e92d37e28..51691c27f537 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -61,6 +61,9 @@ object Flags {
// TODO(b/254512517): Tracking Bug
val FSI_REQUIRES_KEYGUARD = unreleasedFlag(110, "fsi_requires_keyguard", teamfood = true)
+ // TODO(b/259130119): Tracking Bug
+ val FSI_ON_DND_UPDATE = unreleasedFlag(259130119, "fsi_on_dnd_update", teamfood = true)
+
// TODO(b/254512538): Tracking Bug
val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply", teamfood = true)
@@ -90,7 +93,8 @@ object Flags {
// TODO(b/257315550): Tracking Bug
val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when")
- // next id: 119
+ val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
+ unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
// 200 - keyguard/lockscreen
// ** Flag retired **
@@ -141,9 +145,9 @@ object Flags {
/**
* Whether to enable the code powering customizable lock screen quick affordances.
*
- * Note that this flag does not enable individual implementations of quick affordances like the
- * new camera quick affordance. Look for individual flags for those.
+ * This flag enables any new prebuilt quick affordances as well.
*/
+ // TODO(b/255618149): Tracking Bug
@JvmField
val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false)
@@ -363,9 +367,7 @@ object Flags {
// 1300 - screenshots
// TODO(b/254512719): Tracking Bug
- @JvmField
- val SCREENSHOT_REQUEST_PROCESSOR =
- unreleasedFlag(1300, "screenshot_request_processor", teamfood = true)
+ @JvmField val SCREENSHOT_REQUEST_PROCESSOR = releasedFlag(1300, "screenshot_request_processor")
// TODO(b/254513155): Tracking Bug
@JvmField
@@ -386,9 +388,7 @@ object Flags {
unreleasedFlag(1600, "a11y_floating_menu_fling_spring_animations")
// 1700 - clipboard
- @JvmField
- val CLIPBOARD_OVERLAY_REFACTOR =
- unreleasedFlag(1700, "clipboard_overlay_refactor", teamfood = true)
+ @JvmField val CLIPBOARD_OVERLAY_REFACTOR = releasedFlag(1700, "clipboard_overlay_refactor")
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = unreleasedFlag(1701, "clipboard_remote_behavior")
// 1800 - shade container
@@ -404,4 +404,10 @@ object Flags {
// 2100 - Falsing Manager
@JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
+
+ // 2200 - udfps
+ // TODO(b/259264861): Tracking Bug
+ @JvmField val UDFPS_NEW_TOUCH_DETECTION = unreleasedFlag(2200, "udfps_new_touch_detection")
+ @JvmField val UDFPS_ELLIPSE_DEBUG_UI = unreleasedFlag(2201, "udfps_ellipse_debug")
+ @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection")
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 18d7bcf9b3fc..8442230fc5b1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -15,7 +15,6 @@
*/
package com.android.systemui.flags
-import com.android.internal.statusbar.IStatusBarService
import dagger.Module
import dagger.Provides
import javax.inject.Named
@@ -32,15 +31,5 @@ interface FlagsCommonModule {
fun providesAllFlags(): Map<Int, Flag<*>> {
return FlagsFactory.knownFlags.map { it.value.id to it.value }.toMap()
}
-
- @JvmStatic
- @Provides
- fun providesRestarter(barService: IStatusBarService): Restarter {
- return object : Restarter {
- override fun restart() {
- barService.restart()
- }
- }
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
new file mode 100644
index 000000000000..f1b1be47a84f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import javax.inject.Inject
+
+class SystemExitRestarter @Inject constructor() : Restarter {
+ override fun restart() {
+ System.exit(0)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 1f52fc6e022c..9b2e6b8ee544 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -375,7 +375,7 @@ public class KeyguardIndicationRotateTextViewController extends
public static final int INDICATION_TYPE_ALIGNMENT = 4;
public static final int INDICATION_TYPE_TRANSIENT = 5;
public static final int INDICATION_TYPE_TRUST = 6;
- public static final int INDICATION_TYPE_RESTING = 7;
+ public static final int INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE = 7;
public static final int INDICATION_TYPE_USER_LOCKED = 8;
public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11;
@@ -390,7 +390,7 @@ public class KeyguardIndicationRotateTextViewController extends
INDICATION_TYPE_ALIGNMENT,
INDICATION_TYPE_TRANSIENT,
INDICATION_TYPE_TRUST,
- INDICATION_TYPE_RESTING,
+ INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
INDICATION_TYPE_USER_LOCKED,
INDICATION_TYPE_REVERSE_CHARGING,
INDICATION_TYPE_BIOMETRIC_MESSAGE,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
index 0f4581ce3e61..1f1ed007fca0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
@@ -31,7 +31,6 @@ import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCall
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
import javax.inject.Inject
-import kotlinx.coroutines.runBlocking
class KeyguardQuickAffordanceProvider :
ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
@@ -57,6 +56,11 @@ class KeyguardQuickAffordanceProvider :
Contract.SelectionTable.TABLE_NAME,
MATCH_CODE_ALL_SELECTIONS,
)
+ addURI(
+ Contract.AUTHORITY,
+ Contract.FlagsTable.TABLE_NAME,
+ MATCH_CODE_ALL_FLAGS,
+ )
}
override fun onCreate(): Boolean {
@@ -77,6 +81,7 @@ class KeyguardQuickAffordanceProvider :
when (uriMatcher.match(uri)) {
MATCH_CODE_ALL_SLOTS,
MATCH_CODE_ALL_AFFORDANCES,
+ MATCH_CODE_ALL_FLAGS,
MATCH_CODE_ALL_SELECTIONS -> "vnd.android.cursor.dir/vnd."
else -> null
}
@@ -86,6 +91,7 @@ class KeyguardQuickAffordanceProvider :
MATCH_CODE_ALL_SLOTS -> Contract.SlotTable.TABLE_NAME
MATCH_CODE_ALL_AFFORDANCES -> Contract.AffordanceTable.TABLE_NAME
MATCH_CODE_ALL_SELECTIONS -> Contract.SelectionTable.TABLE_NAME
+ MATCH_CODE_ALL_FLAGS -> Contract.FlagsTable.TABLE_NAME
else -> null
}
@@ -115,6 +121,7 @@ class KeyguardQuickAffordanceProvider :
MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
MATCH_CODE_ALL_SLOTS -> querySlots()
MATCH_CODE_ALL_SELECTIONS -> querySelections()
+ MATCH_CODE_ALL_FLAGS -> queryFlags()
else -> null
}
}
@@ -171,12 +178,11 @@ class KeyguardQuickAffordanceProvider :
throw IllegalArgumentException("Cannot insert selection, affordance ID was empty!")
}
- val success = runBlocking {
+ val success =
interactor.select(
slotId = slotId,
affordanceId = affordanceId,
)
- }
return if (success) {
Log.d(TAG, "Successfully selected $affordanceId for slot $slotId")
@@ -196,7 +202,7 @@ class KeyguardQuickAffordanceProvider :
)
)
.apply {
- val affordanceIdsBySlotId = runBlocking { interactor.getSelections() }
+ val affordanceIdsBySlotId = interactor.getSelections()
affordanceIdsBySlotId.entries.forEach { (slotId, affordanceIds) ->
affordanceIds.forEach { affordanceId ->
addRow(
@@ -250,6 +256,29 @@ class KeyguardQuickAffordanceProvider :
}
}
+ private fun queryFlags(): Cursor {
+ return MatrixCursor(
+ arrayOf(
+ Contract.FlagsTable.Columns.NAME,
+ Contract.FlagsTable.Columns.VALUE,
+ )
+ )
+ .apply {
+ interactor.getPickerFlags().forEach { flag ->
+ addRow(
+ arrayOf(
+ flag.name,
+ if (flag.value) {
+ 1
+ } else {
+ 0
+ },
+ )
+ )
+ }
+ }
+ }
+
private fun deleteSelection(
uri: Uri,
selectionArgs: Array<out String>?,
@@ -271,12 +300,11 @@ class KeyguardQuickAffordanceProvider :
)
}
- val deleted = runBlocking {
+ val deleted =
interactor.unselect(
slotId = slotId,
affordanceId = affordanceId,
)
- }
return if (deleted) {
Log.d(TAG, "Successfully unselected $affordanceId for slot $slotId")
@@ -293,5 +321,6 @@ class KeyguardQuickAffordanceProvider :
private const val MATCH_CODE_ALL_SLOTS = 1
private const val MATCH_CODE_ALL_AFFORDANCES = 2
private const val MATCH_CODE_ALL_SELECTIONS = 3
+ private const val MATCH_CODE_ALL_FLAGS = 4
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index a069582f6692..f5220b8fae92 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -24,6 +24,7 @@ package com.android.systemui.keyguard.data.quickaffordance
*/
object BuiltInKeyguardQuickAffordanceKeys {
// Please keep alphabetical order of const names to simplify future maintenance.
+ const val CAMERA = "camera"
const val HOME_CONTROLS = "home"
const val QR_CODE_SCANNER = "qr_code_scanner"
const val QUICK_ACCESS_WALLET = "wallet"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
new file mode 100644
index 000000000000..3c09aab60443
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.camera.CameraGestureHelper
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import javax.inject.Inject
+
+@SysUISingleton
+class CameraQuickAffordanceConfig @Inject constructor(
+ @Application private val context: Context,
+ private val cameraGestureHelper: CameraGestureHelper,
+) : KeyguardQuickAffordanceConfig {
+
+ override val key: String
+ get() = BuiltInKeyguardQuickAffordanceKeys.CAMERA
+
+ override val pickerName: String
+ get() = context.getString(R.string.accessibility_camera_button)
+
+ override val pickerIconResourceId: Int
+ get() = com.android.internal.R.drawable.perm_group_camera
+
+ override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
+ get() = flowOf(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = Icon.Resource(
+ com.android.internal.R.drawable.perm_group_camera,
+ ContentDescription.Resource(R.string.accessibility_camera_button)
+ )
+ )
+ )
+
+ override fun onTriggered(expandable: Expandable?): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ cameraGestureHelper.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index bea9363efc81..f7225a249eda 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -29,8 +29,10 @@ object KeyguardDataQuickAffordanceModule {
home: HomeControlsKeyguardQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+ camera: CameraQuickAffordanceConfig,
): Set<KeyguardQuickAffordanceConfig> {
return setOf(
+ camera,
home,
quickAccessWallet,
qrCodeScanner,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
new file mode 100644
index 000000000000..766096f1fa2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncer.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer.Companion.BINDINGS
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Keeps quick affordance selections and legacy user settings in sync.
+ *
+ * "Legacy user settings" are user settings like: Settings > Display > Lock screen > "Show device
+ * controls" Settings > Display > Lock screen > "Show wallet"
+ *
+ * Quick affordance selections are the ones available through the new custom lock screen experience
+ * from Settings > Wallpaper & Style.
+ *
+ * This class keeps these in sync, mostly for backwards compatibility purposes and in order to not
+ * "forget" an existing legacy user setting when the device gets updated with a version of System UI
+ * that has the new customizable lock screen feature.
+ *
+ * The way it works is that, when [startSyncing] is called, the syncer starts coroutines to listen
+ * for changes in both legacy user settings and their respective affordance selections. Whenever one
+ * of each pair is changed, the other member of that pair is also updated to match. For example, if
+ * the user turns on "Show device controls", we automatically select the home controls affordance
+ * for the preferred slot. Conversely, when the home controls affordance is unselected by the user,
+ * we set the "Show device controls" setting to "off".
+ *
+ * The class can be configured by updating its list of triplets in the code under [BINDINGS].
+ */
+@SysUISingleton
+class KeyguardQuickAffordanceLegacySettingSyncer
+@Inject
+constructor(
+ @Application private val scope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val secureSettings: SecureSettings,
+ private val selectionsManager: KeyguardQuickAffordanceSelectionManager,
+) {
+ companion object {
+ private val BINDINGS =
+ listOf(
+ Binding(
+ settingsKey = Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+ ),
+ Binding(
+ settingsKey = Settings.Secure.LOCKSCREEN_SHOW_WALLET,
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET,
+ ),
+ Binding(
+ settingsKey = Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER,
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER,
+ ),
+ )
+ }
+
+ fun startSyncing(
+ bindings: List<Binding> = BINDINGS,
+ ): Job {
+ return scope.launch { bindings.forEach { binding -> startSyncing(this, binding) } }
+ }
+
+ private fun startSyncing(
+ scope: CoroutineScope,
+ binding: Binding,
+ ) {
+ secureSettings
+ .observerFlow(
+ names = arrayOf(binding.settingsKey),
+ userId = UserHandle.USER_ALL,
+ )
+ .map {
+ isSet(
+ settingsKey = binding.settingsKey,
+ )
+ }
+ .distinctUntilChanged()
+ .onEach { isSet ->
+ if (isSelected(binding.affordanceId) != isSet) {
+ if (isSet) {
+ select(
+ slotId = binding.slotId,
+ affordanceId = binding.affordanceId,
+ )
+ } else {
+ unselect(
+ affordanceId = binding.affordanceId,
+ )
+ }
+ }
+ }
+ .flowOn(backgroundDispatcher)
+ .launchIn(scope)
+
+ selectionsManager.selections
+ .map { it.values.flatten().toSet() }
+ .map { it.contains(binding.affordanceId) }
+ .distinctUntilChanged()
+ .onEach { isSelected ->
+ if (isSet(binding.settingsKey) != isSelected) {
+ set(binding.settingsKey, isSelected)
+ }
+ }
+ .flowOn(backgroundDispatcher)
+ .launchIn(scope)
+ }
+
+ private fun isSelected(
+ affordanceId: String,
+ ): Boolean {
+ return selectionsManager
+ .getSelections() // Map<String, List<String>>
+ .values // Collection<List<String>>
+ .flatten() // List<String>
+ .toSet() // Set<String>
+ .contains(affordanceId)
+ }
+
+ private fun select(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ val affordanceIdsAtSlotId = selectionsManager.getSelections()[slotId] ?: emptyList()
+ selectionsManager.setSelections(
+ slotId = slotId,
+ affordanceIds = affordanceIdsAtSlotId + listOf(affordanceId),
+ )
+ }
+
+ private fun unselect(
+ affordanceId: String,
+ ) {
+ val currentSelections = selectionsManager.getSelections()
+ val slotIdsContainingAffordanceId =
+ currentSelections
+ .filter { (_, affordanceIds) -> affordanceIds.contains(affordanceId) }
+ .map { (slotId, _) -> slotId }
+
+ slotIdsContainingAffordanceId.forEach { slotId ->
+ val currentAffordanceIds = currentSelections[slotId] ?: emptyList()
+ val affordanceIdsAfterUnselecting =
+ currentAffordanceIds.toMutableList().apply { remove(affordanceId) }
+
+ selectionsManager.setSelections(
+ slotId = slotId,
+ affordanceIds = affordanceIdsAfterUnselecting,
+ )
+ }
+ }
+
+ private fun isSet(
+ settingsKey: String,
+ ): Boolean {
+ return secureSettings.getIntForUser(
+ settingsKey,
+ 0,
+ UserHandle.USER_CURRENT,
+ ) != 0
+ }
+
+ private suspend fun set(
+ settingsKey: String,
+ isSet: Boolean,
+ ) {
+ withContext(backgroundDispatcher) {
+ secureSettings.putInt(
+ settingsKey,
+ if (isSet) 1 else 0,
+ )
+ }
+ }
+
+ data class Binding(
+ val settingsKey: String,
+ val slotId: String,
+ val affordanceId: String,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
index 9c9354fec695..b29cf45cc709 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -17,46 +17,138 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
/**
* Manages and provides access to the current "selections" of keyguard quick affordances, answering
* the question "which affordances should the keyguard show?".
*/
@SysUISingleton
-class KeyguardQuickAffordanceSelectionManager @Inject constructor() {
+class KeyguardQuickAffordanceSelectionManager
+@Inject
+constructor(
+ @Application context: Context,
+ private val userFileManager: UserFileManager,
+ private val userTracker: UserTracker,
+) {
- // TODO(b/254858695): implement a persistence layer (database).
- private val _selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+ private val sharedPrefs: SharedPreferences
+ get() =
+ userFileManager.getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId,
+ )
+
+ private val userId: Flow<Int> = conflatedCallbackFlow {
+ val callback =
+ object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ trySendWithFailureLogging(newUser, TAG)
+ }
+ }
+
+ userTracker.addCallback(callback) { it.run() }
+ trySendWithFailureLogging(userTracker.userId, TAG)
+
+ awaitClose { userTracker.removeCallback(callback) }
+ }
+ private val defaults: Map<String, List<String>> by lazy {
+ context.resources
+ .getStringArray(R.array.config_keyguardQuickAffordanceDefaults)
+ .associate { item ->
+ val splitUp = item.split(SLOT_AFFORDANCES_DELIMITER)
+ check(splitUp.size == 2)
+ val slotId = splitUp[0]
+ val affordanceIds = splitUp[1].split(AFFORDANCE_DELIMITER)
+ slotId to affordanceIds
+ }
+ }
/** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
- val selections: Flow<Map<String, List<String>>> = _selections.asStateFlow()
+ val selections: Flow<Map<String, List<String>>> =
+ userId.flatMapLatest {
+ conflatedCallbackFlow {
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
+ trySend(getSelections())
+ }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+ send(getSelections())
+
+ awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+ }
/**
* Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
* descending priority order.
*/
- suspend fun getSelections(): Map<String, List<String>> {
- return _selections.value
+ fun getSelections(): Map<String, List<String>> {
+ val slotKeys = sharedPrefs.all.keys.filter { it.startsWith(KEY_PREFIX_SLOT) }
+ val result =
+ slotKeys
+ .associate { key ->
+ val slotId = key.substring(KEY_PREFIX_SLOT.length)
+ val value = sharedPrefs.getString(key, null)
+ val affordanceIds =
+ if (!value.isNullOrEmpty()) {
+ value.split(AFFORDANCE_DELIMITER)
+ } else {
+ emptyList()
+ }
+ slotId to affordanceIds
+ }
+ .toMutableMap()
+
+ // If the result map is missing keys, it means that the system has never set anything for
+ // those slots. This is where we need examine our defaults and see if there should be a
+ // default value for the affordances in the slot IDs that are missing from the result.
+ //
+ // Once the user makes any selection for a slot, even when they select "None", this class
+ // will persist a key for that slot ID. In the case of "None", it will have a value of the
+ // empty string. This is why this system works.
+ defaults.forEach { (slotId, affordanceIds) ->
+ if (!result.containsKey(slotId)) {
+ result[slotId] = affordanceIds
+ }
+ }
+
+ return result
}
/**
* Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
* IDs should be descending priority order.
*/
- suspend fun setSelections(
+ fun setSelections(
slotId: String,
affordanceIds: List<String>,
) {
- // Must make a copy of the map and update it, otherwise, the MutableStateFlow won't emit
- // when we set its value to the same instance of the original map, even if we change the
- // map by updating the value of one of its keys.
- val copy = _selections.value.toMutableMap()
- copy[slotId] = affordanceIds
- _selections.value = copy
+ val key = "$KEY_PREFIX_SLOT$slotId"
+ val value = affordanceIds.joinToString(AFFORDANCE_DELIMITER)
+ sharedPrefs.edit().putString(key, value).apply()
+ }
+
+ companion object {
+ private const val TAG = "KeyguardQuickAffordanceSelectionManager"
+ @VisibleForTesting const val FILE_NAME = "quick_affordance_selections"
+ private const val KEY_PREFIX_SLOT = "slot_"
+ private const val SLOT_AFFORDANCES_DELIMITER = ":"
+ private const val AFFORDANCE_DELIMITER = ","
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 95f614fbf7b1..533b3abf4fb6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -17,31 +17,31 @@
package com.android.systemui.keyguard.data.repository
+import android.content.Context
+import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
/** Abstracts access to application state related to keyguard quick affordances. */
@SysUISingleton
class KeyguardQuickAffordanceRepository
@Inject
constructor(
+ @Application private val appContext: Context,
@Application private val scope: CoroutineScope,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
private val selectionManager: KeyguardQuickAffordanceSelectionManager,
+ legacySettingSyncer: KeyguardQuickAffordanceLegacySettingSyncer,
private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
) {
/**
@@ -61,11 +61,39 @@ constructor(
initialValue = emptyMap(),
)
+ private val _slotPickerRepresentations: List<KeyguardSlotPickerRepresentation> by lazy {
+ fun parseSlot(unparsedSlot: String): Pair<String, Int> {
+ val split = unparsedSlot.split(SLOT_CONFIG_DELIMITER)
+ check(split.size == 2)
+ val slotId = split[0]
+ val slotCapacity = split[1].toInt()
+ return slotId to slotCapacity
+ }
+
+ val unparsedSlots =
+ appContext.resources.getStringArray(R.array.config_keyguardQuickAffordanceSlots)
+
+ val seenSlotIds = mutableSetOf<String>()
+ unparsedSlots.mapNotNull { unparsedSlot ->
+ val (slotId, slotCapacity) = parseSlot(unparsedSlot)
+ check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" }
+ seenSlotIds.add(slotId)
+ KeyguardSlotPickerRepresentation(
+ id = slotId,
+ maxSelectedAffordances = slotCapacity,
+ )
+ }
+ }
+
+ init {
+ legacySettingSyncer.startSyncing()
+ }
+
/**
* Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
* slot with the given ID. The configs are sorted in descending priority order.
*/
- suspend fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
+ fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList())
return configs.filter { selections.contains(it.key) }
}
@@ -74,7 +102,7 @@ constructor(
* Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs
* are sorted in descending priority order.
*/
- suspend fun getSelections(): Map<String, List<String>> {
+ fun getSelections(): Map<String, List<String>> {
return selectionManager.getSelections()
}
@@ -86,12 +114,10 @@ constructor(
slotId: String,
affordanceIds: List<String>,
) {
- scope.launch(backgroundDispatcher) {
- selectionManager.setSelections(
- slotId = slotId,
- affordanceIds = affordanceIds,
- )
- }
+ selectionManager.setSelections(
+ slotId = slotId,
+ affordanceIds = affordanceIds,
+ )
}
/**
@@ -115,14 +141,10 @@ constructor(
* each slot and select which affordance(s) is/are installed in each slot on the keyguard.
*/
fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
- // TODO(b/256195304): source these from a config XML file.
- return listOf(
- KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- ),
- KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- ),
- )
+ return _slotPickerRepresentations
+ }
+
+ companion object {
+ private const val SLOT_CONFIG_DELIMITER = ":"
}
}
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 92caa89bb0e8..45eb6f501287 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
@@ -28,11 +28,13 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardPickerFlag
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
@@ -117,7 +119,7 @@ constructor(
*
* @return `true` if the affordance was selected successfully; `false` otherwise.
*/
- suspend fun select(slotId: String, affordanceId: String): Boolean {
+ fun select(slotId: String, affordanceId: String): Boolean {
check(isUsingRepository)
val slots = repository.get().getSlotPickerRepresentations()
@@ -152,7 +154,7 @@ constructor(
* @return `true` if the affordance was successfully removed; `false` otherwise (for example, if
* the affordance was not on the slot to begin with).
*/
- suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
+ fun unselect(slotId: String, affordanceId: String?): Boolean {
check(isUsingRepository)
val slots = repository.get().getSlotPickerRepresentations()
@@ -187,7 +189,7 @@ constructor(
}
/** Returns affordance IDs indexed by slot ID, for all known slots. */
- suspend fun getSelections(): Map<String, List<String>> {
+ fun getSelections(): Map<String, List<String>> {
check(isUsingRepository)
val selections = repository.get().getSelections()
@@ -314,6 +316,15 @@ constructor(
return repository.get().getSlotPickerRepresentations()
}
+ fun getPickerFlags(): List<KeyguardPickerFlag> {
+ return listOf(
+ KeyguardPickerFlag(
+ name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED,
+ value = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES),
+ )
+ )
+ }
+
companion object {
private const val TAG = "KeyguardQuickAffordanceInteractor"
private const val DELIMITER = "::"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt
new file mode 100644
index 000000000000..a7a5957cd527
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardPickerFlag.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/** Represents a flag that's consumed by the settings or wallpaper picker app. */
+data class KeyguardPickerFlag(
+ val name: String,
+ val value: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 14dd99023b92..3012bb41445e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -82,7 +82,6 @@ import com.android.systemui.util.traceSection
import java.io.IOException
import java.io.PrintWriter
import java.util.concurrent.Executor
-import java.util.concurrent.Executors
import javax.inject.Inject
// URI fields to try loading album art from
@@ -154,6 +153,7 @@ private fun allowMediaRecommendations(context: Context): Boolean {
class MediaDataManager(
private val context: Context,
@Background private val backgroundExecutor: Executor,
+ @Main private val uiExecutor: Executor,
@Main private val foregroundExecutor: DelayableExecutor,
private val mediaControllerFactory: MediaControllerFactory,
private val broadcastDispatcher: BroadcastDispatcher,
@@ -171,7 +171,8 @@ class MediaDataManager(
private val systemClock: SystemClock,
private val tunerService: TunerService,
private val mediaFlags: MediaFlags,
- private val logger: MediaUiEventLogger
+ private val logger: MediaUiEventLogger,
+ private val smartspaceManager: SmartspaceManager,
) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener {
companion object {
@@ -218,6 +219,7 @@ class MediaDataManager(
constructor(
context: Context,
@Background backgroundExecutor: Executor,
+ @Main uiExecutor: Executor,
@Main foregroundExecutor: DelayableExecutor,
mediaControllerFactory: MediaControllerFactory,
dumpManager: DumpManager,
@@ -233,10 +235,12 @@ class MediaDataManager(
clock: SystemClock,
tunerService: TunerService,
mediaFlags: MediaFlags,
- logger: MediaUiEventLogger
+ logger: MediaUiEventLogger,
+ smartspaceManager: SmartspaceManager,
) : this(
context,
backgroundExecutor,
+ uiExecutor,
foregroundExecutor,
mediaControllerFactory,
broadcastDispatcher,
@@ -254,7 +258,8 @@ class MediaDataManager(
clock,
tunerService,
mediaFlags,
- logger
+ logger,
+ smartspaceManager,
)
private val appChangeReceiver =
@@ -314,21 +319,18 @@ class MediaDataManager(
// Register for Smartspace data updates.
smartspaceMediaDataProvider.registerListener(this)
- val smartspaceManager: SmartspaceManager =
- context.getSystemService(SmartspaceManager::class.java)
smartspaceSession =
smartspaceManager.createSmartspaceSession(
SmartspaceConfig.Builder(context, SMARTSPACE_UI_SURFACE_LABEL).build()
)
smartspaceSession?.let {
it.addOnTargetsAvailableListener(
- // Use a new thread listening to Smartspace updates instead of using the existing
- // backgroundExecutor. SmartspaceSession has scheduled routine updates which can be
- // unpredictable on test simulators, using the backgroundExecutor makes it's hard to
- // test the threads numbers.
- // Switch to use backgroundExecutor when SmartspaceSession has a good way to be
- // mocked.
- Executors.newCachedThreadPool(),
+ // Use a main uiExecutor thread listening to Smartspace updates instead of using
+ // the existing background executor.
+ // SmartspaceSession has scheduled routine updates which can be unpredictable on
+ // test simulators, using the backgroundExecutor makes it's hard to test the threads
+ // numbers.
+ uiExecutor,
SmartspaceSession.OnTargetsAvailableListener { targets ->
smartspaceMediaDataProvider.onTargetsAvailable(targets)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index 9ba3501c3434..03bb7a0f45da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -32,8 +32,6 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.FgsManagerController
@@ -42,10 +40,9 @@ import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
import com.android.systemui.qs.footer.data.repository.ForegroundServicesRepository
import com.android.systemui.qs.footer.data.repository.UserSwitcherRepository
import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.security.data.repository.SecurityRepository
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.user.domain.interactor.UserInteractor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@@ -100,13 +97,12 @@ class FooterActionsInteractorImpl
@Inject
constructor(
private val activityStarter: ActivityStarter,
- private val featureFlags: FeatureFlags,
private val metricsLogger: MetricsLogger,
private val uiEventLogger: UiEventLogger,
private val deviceProvisionedController: DeviceProvisionedController,
private val qsSecurityFooterUtils: QSSecurityFooterUtils,
private val fgsManagerController: FgsManagerController,
- private val userSwitchDialogController: UserSwitchDialogController,
+ private val userInteractor: UserInteractor,
securityRepository: SecurityRepository,
foregroundServicesRepository: ForegroundServicesRepository,
userSwitcherRepository: UserSwitcherRepository,
@@ -182,22 +178,6 @@ constructor(
}
override fun showUserSwitcher(context: Context, expandable: Expandable) {
- if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- userSwitchDialogController.showDialog(context, expandable)
- return
- }
-
- val intent =
- Intent(context, UserSwitcherActivity::class.java).apply {
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- }
-
- activityStarter.startActivity(
- intent,
- true /* dismissShade */,
- expandable.activityLaunchController(),
- true /* showOverlockscreenwhenlocked */,
- UserHandle.SYSTEM,
- )
+ userInteractor.showUserSwitcher(context, expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 7143ba263570..b4934cf7b804 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -38,6 +38,7 @@ import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -81,7 +82,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
private String mScreenshotId;
- private final boolean mSmartActionsEnabled;
private final Random mRandom = new Random();
private final Supplier<ActionTransition> mSharedElementTransition;
private final ImageExporter mImageExporter;
@@ -109,8 +109,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mParams = data;
// Initialize screenshot notification smart actions provider.
- mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true);
mSmartActionsProvider = screenshotNotificationSmartActionsProvider;
}
@@ -131,8 +129,16 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Bitmap image = mParams.image;
mScreenshotId = String.format(SCREENSHOT_ID_TEMPLATE, requestId);
+
+ boolean savingToOtherUser = mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
+ && (user != Process.myUserHandle());
+ // Smart actions don't yet work for cross-user saves.
+ boolean smartActionsEnabled = !savingToOtherUser
+ && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
+ true);
try {
- if (mSmartActionsEnabled && mParams.mQuickShareActionsReadyListener != null) {
+ if (smartActionsEnabled && mParams.mQuickShareActionsReadyListener != null) {
// Since Quick Share target recommendation does not rely on image URL, it is
// queried and surfaced before image compress/export. Action intent would not be
// used, because it does not contain image URL.
@@ -150,10 +156,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
CompletableFuture<List<Notification.Action>> smartActionsFuture =
mScreenshotSmartActions.getSmartActionsFuture(
mScreenshotId, uri, image, mSmartActionsProvider, REGULAR_SMART_ACTIONS,
- mSmartActionsEnabled, user);
-
+ smartActionsEnabled, user);
List<Notification.Action> smartActions = new ArrayList<>();
- if (mSmartActionsEnabled) {
+ if (smartActionsEnabled) {
int timeoutMs = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
@@ -168,9 +173,12 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mImageData.uri = uri;
mImageData.owner = user;
mImageData.smartActions = smartActions;
- mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri);
- mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri);
- mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
+ mImageData.shareTransition = createShareAction(mContext, mContext.getResources(), uri,
+ smartActionsEnabled);
+ mImageData.editTransition = createEditAction(mContext, mContext.getResources(), uri,
+ smartActionsEnabled);
+ mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri,
+ smartActionsEnabled);
mImageData.quickShareAction = createQuickShareAction(mContext,
mQuickShareData.quickShareAction, uri);
mImageData.subject = getSubjectString();
@@ -228,7 +236,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
* Assumes that the action intent is sent immediately after being supplied.
*/
@VisibleForTesting
- Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri) {
+ Supplier<ActionTransition> createShareAction(Context context, Resources r, Uri uri,
+ boolean smartActionsEnabled) {
return () -> {
ActionTransition transition = mSharedElementTransition.get();
@@ -274,7 +283,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
.putExtra(ScreenshotController.EXTRA_DISALLOW_ENTER_PIP, true)
.putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
+ smartActionsEnabled)
.setAction(Intent.ACTION_SEND)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
@@ -290,7 +299,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
}
@VisibleForTesting
- Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri) {
+ Supplier<ActionTransition> createEditAction(Context context, Resources r, Uri uri,
+ boolean smartActionsEnabled) {
return () -> {
ActionTransition transition = mSharedElementTransition.get();
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
@@ -323,7 +333,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
.putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
+ smartActionsEnabled)
.putExtra(ScreenshotController.EXTRA_OVERRIDE_TRANSITION, true)
.setAction(Intent.ACTION_EDIT)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
@@ -339,7 +349,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
}
@VisibleForTesting
- Notification.Action createDeleteAction(Context context, Resources r, Uri uri) {
+ Notification.Action createDeleteAction(Context context, Resources r, Uri uri,
+ boolean smartActionsEnabled) {
// Make sure pending intents for the system user are still unique across users
// by setting the (otherwise unused) request code to the current user id.
int requestCode = mContext.getUserId();
@@ -350,7 +361,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
.putExtra(ScreenshotController.SCREENSHOT_URI_ID, uri.toString())
.putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
.putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
+ smartActionsEnabled)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
PendingIntent.FLAG_CANCEL_CURRENT
| PendingIntent.FLAG_ONE_SHOT
@@ -391,7 +402,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Intent intent = new Intent(context, SmartActionsReceiver.class)
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, action.actionIntent)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
+ addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
mRandom.nextInt(),
intent,
@@ -445,7 +456,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Intent intent = new Intent(context, SmartActionsReceiver.class)
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, updatedPendingIntent)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
+ // We only query for quick share actions when smart actions are enabled, so we can assert
+ // that it's true here.
+ addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
mRandom.nextInt(),
intent,
@@ -464,7 +477,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mScreenshotSmartActions.getSmartActionsFuture(
mScreenshotId, null, image, mSmartActionsProvider,
QUICK_SHARE_ACTION,
- mSmartActionsEnabled, user);
+ true /* smartActionsEnabled */, user);
int timeoutMs = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_QUICK_SHARE_ACTIONS_TIMEOUT_MS,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ceef8c8ff31c..b92cf5a0a49d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -177,6 +177,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -253,6 +254,8 @@ public final class NotificationPanelViewController implements Dumpable {
private static final int FLING_COLLAPSE = 1;
/** Fling until QS is completely hidden. */
private static final int FLING_HIDE = 2;
+ /** The delay to reset the hint text when the hint animation is finished running. */
+ private static final int HINT_RESET_DELAY_MS = 1200;
private static final long ANIMATION_DELAY_ICON_FADE_IN =
ActivityLaunchAnimator.TIMINGS.getTotalDuration()
- CollapsedStatusBarFragment.FADE_IN_DURATION
@@ -343,6 +346,7 @@ public final class NotificationPanelViewController implements Dumpable {
private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired;
private final FragmentListener mQsFragmentListener = new QsFragmentListener();
private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
+ private final NotificationGutsManager mGutsManager;
private long mDownTime;
private boolean mTouchSlopExceededBeforeDown;
@@ -625,7 +629,6 @@ public final class NotificationPanelViewController implements Dumpable {
private float mLastGesturedOverExpansion = -1;
/** Whether the current animator is the spring back animation. */
private boolean mIsSpringBackAnimation;
- private boolean mInSplitShade;
private float mHintDistance;
private float mInitialOffsetOnTouch;
private boolean mCollapsedAndHeadsUpOnDown;
@@ -702,6 +705,7 @@ public final class NotificationPanelViewController implements Dumpable {
ConversationNotificationManager conversationNotificationManager,
MediaHierarchyManager mediaHierarchyManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ NotificationGutsManager gutsManager,
NotificationsQSContainerController notificationsQSContainerController,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
@@ -755,6 +759,7 @@ public final class NotificationPanelViewController implements Dumpable {
mLockscreenGestureLogger = lockscreenGestureLogger;
mShadeExpansionStateManager = shadeExpansionStateManager;
mShadeLog = shadeLogger;
+ mGutsManager = gutsManager;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -1061,7 +1066,6 @@ public final class NotificationPanelViewController implements Dumpable {
mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier();
mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
mPanelFlingOvershootAmount = mResources.getDimension(R.dimen.panel_overshoot_amount);
- mInSplitShade = mResources.getBoolean(R.bool.config_use_split_notification_shade);
mFlingAnimationUtils = mFlingAnimationUtilsBuilder.get()
.setMaxLengthSeconds(0.4f).build();
mStatusBarMinHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
@@ -1569,23 +1573,31 @@ public final class NotificationPanelViewController implements Dumpable {
// Find the clock, so we can exclude it from this transition.
FrameLayout clockContainerView =
mView.findViewById(R.id.lockscreen_clock_view_large);
- View clockView = clockContainerView.getChildAt(0);
- transition.excludeTarget(clockView, /* exclude= */ true);
+ // The clock container can sometimes be null. If it is, just fall back to the
+ // old animation rather than setting up the custom animations.
+ if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
+ TransitionManager.beginDelayedTransition(
+ mNotificationContainerParent, transition);
+ } else {
+ View clockView = clockContainerView.getChildAt(0);
+
+ transition.excludeTarget(clockView, /* exclude= */ true);
- TransitionSet set = new TransitionSet();
- set.addTransition(transition);
+ TransitionSet set = new TransitionSet();
+ set.addTransition(transition);
- SplitShadeTransitionAdapter adapter =
- new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
+ SplitShadeTransitionAdapter adapter =
+ new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
- // Use linear here, so the actual clock can pick its own interpolator.
- adapter.setInterpolator(Interpolators.LINEAR);
- adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- adapter.addTarget(clockView);
- set.addTransition(adapter);
+ // Use linear here, so the actual clock can pick its own interpolator.
+ adapter.setInterpolator(Interpolators.LINEAR);
+ adapter.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ adapter.addTarget(clockView);
+ set.addTransition(adapter);
- TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+ TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
+ }
} else {
TransitionManager.beginDelayedTransition(
mNotificationContainerParent, transition);
@@ -1760,7 +1772,7 @@ public final class NotificationPanelViewController implements Dumpable {
}
public void resetViews(boolean animate) {
- mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
+ mGutsManager.closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
if (animate && !isFullyCollapsed()) {
animateCloseQs(true /* animateAway */);
@@ -1836,6 +1848,10 @@ public final class NotificationPanelViewController implements Dumpable {
public void closeQs() {
cancelQsAnimation();
setQsExpansionHeight(mQsMinExpansionHeight);
+ // qsExpandImmediate is a safety latch in case we're calling closeQS while we're in the
+ // middle of animation - we need to make sure that value is always false when shade if
+ // fully collapsed or expanded
+ setQsExpandImmediate(false);
}
@VisibleForTesting
@@ -1938,7 +1954,7 @@ public final class NotificationPanelViewController implements Dumpable {
// we want to perform an overshoot animation when flinging open
final boolean addOverscroll =
expand
- && !mInSplitShade // Split shade has its own overscroll logic
+ && !mSplitShadeEnabled // Split shade has its own overscroll logic
&& mStatusBarStateController.getState() != KEYGUARD
&& mOverExpansion == 0.0f
&& vel >= 0;
@@ -2727,8 +2743,10 @@ public final class NotificationPanelViewController implements Dumpable {
* as well based on the bounds of the shade and QS state.
*/
private void setQSClippingBounds() {
- final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction());
- final boolean qsVisible = (computeQsExpansionFraction() > 0 || qsPanelBottomY > 0);
+ float qsExpansionFraction = computeQsExpansionFraction();
+ final int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
+ final boolean qsVisible = (qsExpansionFraction > 0 || qsPanelBottomY > 0);
+ checkCorrectScrimVisibility(qsExpansionFraction);
int top = calculateTopQsClippingBound(qsPanelBottomY);
int bottom = calculateBottomQsClippingBound(top);
@@ -2739,6 +2757,19 @@ public final class NotificationPanelViewController implements Dumpable {
applyQSClippingBounds(left, top, right, bottom, qsVisible);
}
+ private void checkCorrectScrimVisibility(float expansionFraction) {
+ // issues with scrims visible on keyguard occur only in split shade
+ if (mSplitShadeEnabled) {
+ boolean keyguardViewsVisible = mBarState == KEYGUARD && mKeyguardOnlyContentAlpha == 1;
+ // expansionFraction == 1 means scrims are fully visible as their size/visibility depend
+ // on QS expansion
+ if (expansionFraction == 1 && keyguardViewsVisible) {
+ Log.wtf(TAG,
+ "Incorrect state, scrim is visible at the same time when clock is visible");
+ }
+ }
+ }
+
private int calculateTopQsClippingBound(int qsPanelBottomY) {
int top;
if (mSplitShadeEnabled) {
@@ -3686,7 +3717,6 @@ public final class NotificationPanelViewController implements Dumpable {
private void onTrackingStopped(boolean expand) {
mFalsingCollector.onTrackingStopped();
mTracking = false;
- mCentralSurfaces.onTrackingStopped(expand);
updatePanelExpansionAndVisibility();
if (expand) {
mNotificationStackScrollLayoutController.setOverScrollAmount(0.0f, true /* onTop */,
@@ -3729,14 +3759,16 @@ public final class NotificationPanelViewController implements Dumpable {
@VisibleForTesting
void onUnlockHintFinished() {
- mCentralSurfaces.onHintFinished();
+ // Delay the reset a bit so the user can read the text.
+ mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
mScrimController.setExpansionAffectsAlpha(true);
mNotificationStackScrollLayoutController.setUnlockHintRunning(false);
}
@VisibleForTesting
void onUnlockHintStarted() {
- mCentralSurfaces.onUnlockHintStarted();
+ mFalsingCollector.onUnlockHintStarted();
+ mKeyguardIndicationController.showActionToUnlock();
mScrimController.setExpansionAffectsAlpha(false);
mNotificationStackScrollLayoutController.setUnlockHintRunning(true);
}
@@ -4393,7 +4425,7 @@ public final class NotificationPanelViewController implements Dumpable {
ipw.print("mPanelFlingOvershootAmount="); ipw.println(mPanelFlingOvershootAmount);
ipw.print("mLastGesturedOverExpansion="); ipw.println(mLastGesturedOverExpansion);
ipw.print("mIsSpringBackAnimation="); ipw.println(mIsSpringBackAnimation);
- ipw.print("mInSplitShade="); ipw.println(mInSplitShade);
+ ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled);
ipw.print("mHintDistance="); ipw.println(mHintDistance);
ipw.print("mInitialOffsetOnTouch="); ipw.println(mInitialOffsetOnTouch);
ipw.print("mCollapsedAndHeadsUpOnDown="); ipw.println(mCollapsedAndHeadsUpOnDown);
@@ -4762,7 +4794,6 @@ public final class NotificationPanelViewController implements Dumpable {
}
mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
- mCentralSurfaces.isFalsingThresholdNeeded(),
mCentralSurfaces.isWakeUpComingFromTouch());
// Log collapse gesture if on lock screen.
if (!expand && onKeyguard) {
@@ -4811,9 +4842,6 @@ public final class NotificationPanelViewController implements Dumpable {
*/
private boolean isFalseTouch(float x, float y,
@Classifier.InteractionType int interactionType) {
- if (!mCentralSurfaces.isFalsingThresholdNeeded()) {
- return false;
- }
if (mFalsingManager.isClassifierEnabled()) {
return mFalsingManager.isFalseTouch(interactionType);
}
@@ -4911,7 +4939,7 @@ public final class NotificationPanelViewController implements Dumpable {
float maxPanelHeight = getMaxPanelTransitionDistance();
if (mHeightAnimator == null) {
// Split shade has its own overscroll logic
- if (mTracking && !mInSplitShade) {
+ if (mTracking && !mSplitShadeEnabled) {
float overExpansionPixels = Math.max(0, h - maxPanelHeight);
setOverExpansionInternal(overExpansionPixels, true /* isFromGesture */);
}
@@ -5459,6 +5487,12 @@ public final class NotificationPanelViewController implements Dumpable {
// - from SHADE to KEYGUARD
// - from SHADE_LOCKED to SHADE
// - getting notified again about the current SHADE or KEYGUARD state
+ if (mSplitShadeEnabled && oldState == SHADE && statusBarState == KEYGUARD) {
+ // user can go to keyguard from different shade states and closing animation
+ // may not fully run - we always want to make sure we close QS when that happens
+ // as we never need QS open in fresh keyguard state
+ closeQs();
+ }
final boolean animatingUnlockedShadeToKeyguard = oldState == SHADE
&& statusBarState == KEYGUARD
&& mScreenOffAnimationController.isKeyguardShowDelayed();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 400b0baea01b..6acf417f0ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -24,6 +24,7 @@ import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.LayoutRes;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -36,6 +37,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
import android.util.AttributeSet;
+import android.util.Pair;
import android.view.ActionMode;
import android.view.DisplayCutout;
import android.view.InputQueue;
@@ -74,6 +76,7 @@ public class NotificationShadeWindowView extends FrameLayout {
private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
private InteractionEventHandler mInteractionEventHandler;
+ private LayoutInsetsController mLayoutInsetProvider;
public NotificationShadeWindowView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -108,12 +111,10 @@ public class NotificationShadeWindowView extends FrameLayout {
mLeftInset = 0;
mRightInset = 0;
DisplayCutout displayCutout = getRootWindowInsets().getDisplayCutout();
- if (displayCutout != null) {
- mLeftInset = displayCutout.getSafeInsetLeft();
- mRightInset = displayCutout.getSafeInsetRight();
- }
- mLeftInset = Math.max(insets.left, mLeftInset);
- mRightInset = Math.max(insets.right, mRightInset);
+ Pair<Integer, Integer> pairInsets = mLayoutInsetProvider
+ .getinsets(windowInsets, displayCutout);
+ mLeftInset = pairInsets.first;
+ mRightInset = pairInsets.second;
applyMargins();
return windowInsets;
}
@@ -172,6 +173,10 @@ public class NotificationShadeWindowView extends FrameLayout {
mInteractionEventHandler = listener;
}
+ protected void setLayoutInsetsController(LayoutInsetsController provider) {
+ mLayoutInsetProvider = provider;
+ }
+
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Boolean result = mInteractionEventHandler.handleDispatchTouchEvent(ev);
@@ -353,6 +358,18 @@ public class NotificationShadeWindowView extends FrameLayout {
}
}
+ /**
+ * Controller responsible for calculating insets for the shade window.
+ */
+ public interface LayoutInsetsController {
+
+ /**
+ * Update the insets and calculate them accordingly.
+ */
+ Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
+ @Nullable DisplayCutout displayCutout);
+ }
+
interface InteractionEventHandler {
/**
* Returns a result for {@link ViewGroup#dispatchTouchEvent(MotionEvent)} or null to defer
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index bb67280c07b8..8379e51f7449 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -42,6 +42,7 @@ import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationInsetsController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -76,6 +77,7 @@ public class NotificationShadeWindowViewController {
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final AmbientState mAmbientState;
private final PulsingGestureListener mPulsingGestureListener;
+ private final NotificationInsetsController mNotificationInsetsController;
private GestureDetector mPulsingWakeupGestureHandler;
private View mBrightnessMirror;
@@ -111,6 +113,7 @@ public class NotificationShadeWindowViewController {
CentralSurfaces centralSurfaces,
NotificationShadeWindowController controller,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+ NotificationInsetsController notificationInsetsController,
AmbientState ambientState,
PulsingGestureListener pulsingGestureListener,
FeatureFlags featureFlags,
@@ -134,6 +137,7 @@ public class NotificationShadeWindowViewController {
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mAmbientState = ambientState;
mPulsingGestureListener = pulsingGestureListener;
+ mNotificationInsetsController = notificationInsetsController;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -165,6 +169,7 @@ public class NotificationShadeWindowViewController {
mPulsingWakeupGestureHandler = new GestureDetector(mView.getContext(),
mPulsingGestureListener);
+ mView.setLayoutInsetsController(mNotificationInsetsController);
mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
@Override
public Boolean handleDispatchTouchEvent(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 2101efb61443..0f27420e22b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -36,7 +36,7 @@ import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewCont
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
@@ -67,6 +67,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -74,6 +75,7 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TrustGrantFlags;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
@@ -154,11 +156,12 @@ public class KeyguardIndicationController {
private final AccessibilityManager mAccessibilityManager;
private final Handler mHandler;
- protected KeyguardIndicationRotateTextViewController mRotateTextViewController;
+ @VisibleForTesting
+ public KeyguardIndicationRotateTextViewController mRotateTextViewController;
private BroadcastReceiver mBroadcastReceiver;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private String mRestingIndication;
+ private String mPersistentUnlockMessage;
private String mAlignmentIndication;
private CharSequence mTrustGrantedIndication;
private CharSequence mTransientIndication;
@@ -195,7 +198,9 @@ public class KeyguardIndicationController {
public void onScreenTurnedOn() {
mHandler.removeMessages(MSG_RESET_ERROR_MESSAGE_ON_SCREEN_ON);
if (mBiometricErrorMessageToShowOnScreenOn != null) {
- showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn);
+ String followUpMessage = mFaceLockedOutThisAuthSession
+ ? faceLockedOutFollowupMessage() : null;
+ showBiometricMessage(mBiometricErrorMessageToShowOnScreenOn, followUpMessage);
// We want to keep this message around in case the screen was off
hideBiometricMessageDelayed(DEFAULT_HIDE_DELAY_MS);
mBiometricErrorMessageToShowOnScreenOn = null;
@@ -374,7 +379,7 @@ public class KeyguardIndicationController {
updateLockScreenTrustMsg(userId, getTrustGrantedIndication(), getTrustManagedIndication());
updateLockScreenAlignmentMsg();
updateLockScreenLogoutView();
- updateLockScreenRestingMsg();
+ updateLockScreenPersistentUnlockMsg();
}
private void updateOrganizedOwnedDevice() {
@@ -480,7 +485,8 @@ public class KeyguardIndicationController {
}
private void updateLockScreenUserLockedMsg(int userId) {
- if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
+ if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)
+ || mKeyguardUpdateMonitor.isEncryptedOrLockdown(userId)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_USER_LOCKED,
new KeyguardIndication.Builder()
@@ -585,18 +591,17 @@ public class KeyguardIndicationController {
}
}
- private void updateLockScreenRestingMsg() {
- if (!TextUtils.isEmpty(mRestingIndication)
- && !mRotateTextViewController.hasIndications()) {
+ private void updateLockScreenPersistentUnlockMsg() {
+ if (!TextUtils.isEmpty(mPersistentUnlockMessage)) {
mRotateTextViewController.updateIndication(
- INDICATION_TYPE_RESTING,
+ INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
new KeyguardIndication.Builder()
- .setMessage(mRestingIndication)
+ .setMessage(mPersistentUnlockMessage)
.setTextColor(mInitialTextColorState)
.build(),
- false);
+ true);
} else {
- mRotateTextViewController.hideIndication(INDICATION_TYPE_RESTING);
+ mRotateTextViewController.hideIndication(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE);
}
}
@@ -679,11 +684,8 @@ public class KeyguardIndicationController {
}
}
- /**
- * Sets the indication that is shown if nothing else is showing.
- */
- public void setRestingIndication(String restingIndication) {
- mRestingIndication = restingIndication;
+ private void setPersistentUnlockMessage(String persistentUnlockMessage) {
+ mPersistentUnlockMessage = persistentUnlockMessage;
updateDeviceEntryIndication(false);
}
@@ -1117,6 +1119,9 @@ public class KeyguardIndicationController {
public void onLockedOutStateChanged(BiometricSourceType biometricSourceType) {
if (biometricSourceType == FACE && !mKeyguardUpdateMonitor.isFaceLockedOut()) {
mFaceLockedOutThisAuthSession = false;
+ } else if (biometricSourceType == FINGERPRINT) {
+ setPersistentUnlockMessage(mKeyguardUpdateMonitor.isFingerprintLockedOut()
+ ? mContext.getString(R.string.keyguard_unlock) : "");
}
}
@@ -1188,9 +1193,9 @@ public class KeyguardIndicationController {
}
@Override
- public void onTrustGrantedWithFlags(int flags, int userId, @Nullable String message) {
- if (!isCurrentUser(userId)) return;
- showTrustGrantedMessage(flags, message);
+ public void onTrustGrantedForCurrentUser(boolean dismissKeyguard,
+ @NonNull TrustGrantFlags flags, @Nullable String message) {
+ showTrustGrantedMessage(dismissKeyguard, message);
}
@Override
@@ -1254,15 +1259,13 @@ public class KeyguardIndicationController {
return getCurrentUser() == userId;
}
- void showTrustGrantedMessage(int flags, @Nullable CharSequence message) {
+ protected void showTrustGrantedMessage(boolean dismissKeyguard, @Nullable String message) {
mTrustGrantedIndication = message;
updateDeviceEntryIndication(false);
}
private void handleFaceLockoutError(String errString) {
- int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
- : R.string.keyguard_unlock;
- String followupMessage = mContext.getString(followupMsgId);
+ String followupMessage = faceLockedOutFollowupMessage();
// Lockout error can happen multiple times in a session because we trigger face auth
// even when it is locked out so that the user is aware that face unlock would have
// triggered but didn't because it is locked out.
@@ -1280,6 +1283,12 @@ public class KeyguardIndicationController {
}
}
+ private String faceLockedOutFollowupMessage() {
+ int followupMsgId = canUnlockWithFingerprint() ? R.string.keyguard_suggest_fingerprint
+ : R.string.keyguard_unlock;
+ return mContext.getString(followupMsgId);
+ }
+
private static boolean isLockoutError(int msgId) {
return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
|| msgId == FaceManager.FACE_ERROR_LOCKOUT;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
new file mode 100644
index 000000000000..39d7d6675ed4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsController.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import com.android.systemui.shade.NotificationShadeWindowView;
+
+/**
+ * Calculates insets for the notification shade window view.
+ */
+public abstract class NotificationInsetsController
+ implements NotificationShadeWindowView.LayoutInsetsController {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java
new file mode 100644
index 000000000000..1ed704e66c76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsImpl.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import static android.view.WindowInsets.Type.systemBars;
+
+import android.annotation.Nullable;
+import android.graphics.Insets;
+import android.util.Pair;
+import android.view.DisplayCutout;
+import android.view.WindowInsets;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Default implementation of NotificationsInsetsController.
+ */
+@SysUISingleton
+public class NotificationInsetsImpl extends NotificationInsetsController {
+
+ @Inject
+ public NotificationInsetsImpl() {
+
+ }
+
+ @Override
+ public Pair<Integer, Integer> getinsets(@Nullable WindowInsets windowInsets,
+ @Nullable DisplayCutout displayCutout) {
+ final Insets insets = windowInsets.getInsetsIgnoringVisibility(systemBars());
+ int leftInset = 0;
+ int rightInset = 0;
+
+ if (displayCutout != null) {
+ leftInset = displayCutout.getSafeInsetLeft();
+ rightInset = displayCutout.getSafeInsetRight();
+ }
+ leftInset = Math.max(insets.left, leftInset);
+ rightInset = Math.max(insets.right, rightInset);
+
+ return new Pair(leftInset, rightInset);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java
new file mode 100644
index 000000000000..614bc0f1a6f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInsetsModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar;
+
+import com.android.systemui.dagger.SysUISingleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+@Module
+public interface NotificationInsetsModule {
+
+ @Binds
+ @SysUISingleton
+ NotificationInsetsController bindNotificationInsetsController(NotificationInsetsImpl impl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index cd130854ff3f..d7eddf53dea5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -71,6 +71,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
private int[] mTmp = new int[2];
private boolean mHideBackground;
private int mStatusBarHeight;
+ private boolean mEnableNotificationClipping;
private AmbientState mAmbientState;
private NotificationStackScrollLayoutController mHostLayoutController;
private int mPaddingBetweenElements;
@@ -117,7 +118,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
// Setting this to first in section to get the clipping to the top roundness correct. This
// value determines the way we are clipping to the top roundness of the overall shade
setFirstInSection(true);
- initDimens();
+ updateResources();
}
public void bind(AmbientState ambientState,
@@ -126,7 +127,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
mHostLayoutController = hostLayoutController;
}
- private void initDimens() {
+ private void updateResources() {
Resources res = getResources();
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
@@ -144,6 +145,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
mCornerAnimationDistance = res.getDimensionPixelSize(
R.dimen.notification_corner_animation_distance);
+ mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
mShelfIcons.setInNotificationIconShelf(true);
if (!mShowNotificationShelf) {
@@ -154,7 +156,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- initDimens();
+ updateResources();
}
@Override
@@ -639,7 +641,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
if (!isPinned) {
if (viewEnd > notificationClipEnd && !shouldClipOwnTop) {
- int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
+ int clipBottomAmount =
+ mEnableNotificationClipping ? (int) (viewEnd - notificationClipEnd) : 0;
view.setClipBottomAmount(clipBottomAmount);
} else {
view.setClipBottomAmount(0);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
index c070fccf9808..324e97294f4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java
@@ -24,17 +24,21 @@ import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import javax.inject.Inject;
/**
- *
+ * A Helper class that offloads {@link Vibrator} calls to a different thread.
+ * {@link Vibrator} makes blocking calls that may cause SysUI to ANR.
+ * TODO(b/245528624): Use regular Vibrator instance once new APIs are available.
*/
@SysUISingleton
public class VibratorHelper {
@@ -53,10 +57,18 @@ public class VibratorHelper {
private final Executor mExecutor;
/**
- *
+ * Creates a vibrator helper on a new single threaded {@link Executor}.
*/
@Inject
- public VibratorHelper(@Nullable Vibrator vibrator, @Background Executor executor) {
+ public VibratorHelper(@Nullable Vibrator vibrator) {
+ this(vibrator, Executors.newSingleThreadExecutor());
+ }
+
+ /**
+ * Creates new vibrator helper on a specific {@link Executor}.
+ */
+ @VisibleForTesting
+ public VibratorHelper(@Nullable Vibrator vibrator, Executor executor) {
mExecutor = executor;
mVibrator = vibrator;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 2734511de78c..7eb890677206 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -40,4 +40,8 @@ class NotifPipelineFlags @Inject constructor(
val isSemiStableSortEnabled: Boolean by lazy {
featureFlags.isEnabled(Flags.SEMI_STABLE_SORT)
}
+
+ val shouldFilterUnseenNotifsOnKeyguard: Boolean by lazy {
+ featureFlags.isEnabled(Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
deleted file mode 100644
index e3d71c8b29d9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-
-import javax.inject.Inject;
-
-/**
- * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen.
- */
-@CoordinatorScope
-public class KeyguardCoordinator implements Coordinator {
- private static final String TAG = "KeyguardCoordinator";
- private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
- private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
- private final StatusBarStateController mStatusBarStateController;
-
- @Inject
- public KeyguardCoordinator(
- KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
- SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider,
- StatusBarStateController statusBarStateController) {
- mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
- mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider;
- mStatusBarStateController = statusBarStateController;
- }
-
- @Override
- public void attach(NotifPipeline pipeline) {
-
- setupInvalidateNotifListCallbacks();
- // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
- pipeline.addFinalizeFilter(mNotifFilter);
- mKeyguardNotificationVisibilityProvider
- .addOnStateChangedListener(this::invalidateListFromFilter);
- updateSectionHeadersVisibility();
- }
-
- private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
- @Override
- public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) {
- return mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry);
- }
- };
-
- // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
- // these same updates
- private void setupInvalidateNotifListCallbacks() {
-
- }
-
- private void invalidateListFromFilter(String reason) {
- updateSectionHeadersVisibility();
- mNotifFilter.invalidateList(reason);
- }
-
- private void updateSectionHeadersVisibility() {
- boolean onKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- boolean neverShowSections = mSectionHeaderVisibilityProvider.getNeverShowSectionHeaders();
- boolean showSections = !onKeyguard && !neverShowSections;
- mSectionHeaderVisibilityProvider.setSectionHeadersVisible(showSections);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
new file mode 100644
index 000000000000..6e5fcebf780f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+/**
+ * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
+ * headers on the lockscreen.
+ */
+@CoordinatorScope
+class KeyguardCoordinator
+@Inject
+constructor(
+ private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
+ private val keyguardRepository: KeyguardRepository,
+ private val notifPipelineFlags: NotifPipelineFlags,
+ @Application private val scope: CoroutineScope,
+ private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
+ private val statusBarStateController: StatusBarStateController,
+) : Coordinator {
+
+ private val unseenNotifications = mutableSetOf<NotificationEntry>()
+
+ override fun attach(pipeline: NotifPipeline) {
+ setupInvalidateNotifListCallbacks()
+ // Filter at the "finalize" stage so that views remain bound by PreparationCoordinator
+ pipeline.addFinalizeFilter(notifFilter)
+ keyguardNotificationVisibilityProvider.addOnStateChangedListener(::invalidateListFromFilter)
+ updateSectionHeadersVisibility()
+ if (notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard) {
+ attachUnseenFilter(pipeline)
+ }
+ }
+
+ private fun attachUnseenFilter(pipeline: NotifPipeline) {
+ pipeline.addFinalizeFilter(unseenNotifFilter)
+ pipeline.addCollectionListener(collectionListener)
+ scope.launch { clearUnseenWhenKeyguardIsDismissed() }
+ }
+
+ private suspend fun clearUnseenWhenKeyguardIsDismissed() {
+ // Use collectLatest so that the suspending block is cancelled if isKeyguardShowing changes
+ // during the timeout period
+ keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing ->
+ if (!isKeyguardShowing) {
+ unseenNotifFilter.invalidateList("keyguard no longer showing")
+ delay(SEEN_TIMEOUT)
+ unseenNotifications.clear()
+ }
+ }
+ }
+
+ private val collectionListener =
+ object : NotifCollectionListener {
+ override fun onEntryAdded(entry: NotificationEntry) {
+ if (keyguardRepository.isKeyguardShowing()) {
+ unseenNotifications.add(entry)
+ }
+ }
+
+ override fun onEntryUpdated(entry: NotificationEntry) {
+ if (keyguardRepository.isKeyguardShowing()) {
+ unseenNotifications.add(entry)
+ }
+ }
+
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ unseenNotifications.remove(entry)
+ }
+ }
+
+ @VisibleForTesting
+ internal val unseenNotifFilter =
+ object : NotifFilter("$TAG-unseen") {
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+ when {
+ // Don't apply filter if the keyguard isn't currently showing
+ !keyguardRepository.isKeyguardShowing() -> false
+ // Don't apply the filter if the notification is unseen
+ unseenNotifications.contains(entry) -> false
+ // Don't apply the filter to (non-promoted) group summaries
+ // - summary will be pruned if necessary, depending on if children are filtered
+ entry.parent?.summary == entry -> false
+ else -> true
+ }
+ }
+
+ private val notifFilter: NotifFilter =
+ object : NotifFilter(TAG) {
+ override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean =
+ keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
+ }
+
+ // TODO(b/206118999): merge this class with SensitiveContentCoordinator which also depends on
+ // these same updates
+ private fun setupInvalidateNotifListCallbacks() {}
+
+ private fun invalidateListFromFilter(reason: String) {
+ updateSectionHeadersVisibility()
+ notifFilter.invalidateList(reason)
+ }
+
+ private fun updateSectionHeadersVisibility() {
+ val onKeyguard = statusBarStateController.state == StatusBarState.KEYGUARD
+ val neverShowSections = sectionHeaderVisibilityProvider.neverShowSectionHeaders
+ val showSections = !onKeyguard && !neverShowSections
+ sectionHeaderVisibilityProvider.sectionHeadersVisible = showSections
+ }
+
+ companion object {
+ private const val TAG = "KeyguardCoordinator"
+ private val SEEN_TIMEOUT = 5.seconds
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 3002a6820990..a2379b270f49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -29,6 +29,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeStateEvents;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -62,6 +63,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
private final HeadsUpManager mHeadsUpManager;
private final ShadeStateEvents mShadeStateEvents;
private final StatusBarStateController mStatusBarStateController;
+ private final VisibilityLocationProvider mVisibilityLocationProvider;
private final VisualStabilityProvider mVisualStabilityProvider;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -94,9 +96,11 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
HeadsUpManager headsUpManager,
ShadeStateEvents shadeStateEvents,
StatusBarStateController statusBarStateController,
+ VisibilityLocationProvider visibilityLocationProvider,
VisualStabilityProvider visualStabilityProvider,
WakefulnessLifecycle wakefulnessLifecycle) {
mHeadsUpManager = headsUpManager;
+ mVisibilityLocationProvider = visibilityLocationProvider;
mVisualStabilityProvider = visualStabilityProvider;
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
@@ -123,6 +127,11 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
// HUNs to the top of the shade
private final NotifStabilityManager mNotifStabilityManager =
new NotifStabilityManager("VisualStabilityCoordinator") {
+ private boolean canMoveForHeadsUp(NotificationEntry entry) {
+ return entry != null && mHeadsUpManager.isAlerting(entry.getKey())
+ && !mVisibilityLocationProvider.isInVisibleLocation(entry);
+ }
+
@Override
public void onBeginRun() {
mIsSuppressingPipelineRun = false;
@@ -140,7 +149,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
@Override
public boolean isGroupChangeAllowed(@NonNull NotificationEntry entry) {
final boolean isGroupChangeAllowedForEntry =
- mReorderingAllowed || mHeadsUpManager.isAlerting(entry.getKey());
+ mReorderingAllowed || canMoveForHeadsUp(entry);
mIsSuppressingGroupChange |= !isGroupChangeAllowedForEntry;
return isGroupChangeAllowedForEntry;
}
@@ -156,7 +165,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
public boolean isSectionChangeAllowed(@NonNull NotificationEntry entry) {
final boolean isSectionChangeAllowedForEntry =
mReorderingAllowed
- || mHeadsUpManager.isAlerting(entry.getKey())
+ || canMoveForHeadsUp(entry)
|| mEntriesThatCanChangeSection.containsKey(entry.getKey());
if (!isSectionChangeAllowedForEntry) {
mEntriesWithSuppressedSectionChange.add(entry.getKey());
@@ -165,8 +174,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
}
@Override
- public boolean isEntryReorderingAllowed(@NonNull ListEntry section) {
- return mReorderingAllowed;
+ public boolean isEntryReorderingAllowed(@NonNull ListEntry entry) {
+ return mReorderingAllowed || canMoveForHeadsUp(entry.getRepresentativeEntry());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt
new file mode 100644
index 000000000000..4bc4ecf5dae9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/VisibilityLocationProviderDelegator.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/**
+ * An injectable component which delegates the visibility location computation to a delegate which
+ * can be initialized after the initial injection, generally because it's provided by a view.
+ */
+@SysUISingleton
+class VisibilityLocationProviderDelegator @Inject constructor() : VisibilityLocationProvider {
+ private var delegate: VisibilityLocationProvider? = null
+
+ fun setDelegate(provider: VisibilityLocationProvider) {
+ delegate = provider
+ }
+
+ override fun isInVisibleLocation(entry: NotificationEntry): Boolean =
+ requireNotNull(this.delegate) { "delegate not initialized" }.isInVisibleLocation(entry)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index df2de560b5b9..a7b7a239f4c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -37,6 +37,7 @@ import com.android.systemui.shade.ShadeEventsModule;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
@@ -51,6 +52,7 @@ import com.android.systemui.statusbar.notification.collection.inflation.OnUserIn
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.collection.provider.NotificationVisibilityProviderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -151,6 +153,11 @@ public interface NotificationsModule {
@Binds
NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
+ /** Provides an instance of {@link VisibilityLocationProvider} */
+ @Binds
+ VisibilityLocationProvider bindVisibilityLocationProvider(
+ VisibilityLocationProviderDelegator visibilityLocationProviderDelegator);
+
/** Provides an instance of {@link NotificationLogger} */
@SysUISingleton
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index e13378269ba6..0240bbcf1e19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -89,6 +89,7 @@ import com.android.systemui.statusbar.notification.collection.PipelineDumpable;
import com.android.systemui.statusbar.notification.collection.PipelineDumper;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
@@ -157,6 +158,7 @@ public class NotificationStackScrollLayoutController {
private final NotifCollection mNotifCollection;
private final UiEventLogger mUiEventLogger;
private final NotificationRemoteInputManager mRemoteInputManager;
+ private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
private final ShadeController mShadeController;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
@@ -638,6 +640,7 @@ public class NotificationStackScrollLayoutController {
ShadeTransitionController shadeTransitionController,
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
+ VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
ShadeController shadeController,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
@@ -679,6 +682,7 @@ public class NotificationStackScrollLayoutController {
mNotifCollection = notifCollection;
mUiEventLogger = uiEventLogger;
mRemoteInputManager = remoteInputManager;
+ mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
mShadeController = shadeController;
mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
@@ -750,6 +754,8 @@ public class NotificationStackScrollLayoutController {
mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+ mVisibilityLocationProviderDelegator.setDelegate(this::isInVisibleLocation);
+
mTunerService.addTunable(
(key, newValue) -> {
switch (key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index eea1d9118fb7..62f57b8d4068 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -57,6 +57,7 @@ public class StackScrollAlgorithm {
private float mGapHeight;
private float mGapHeightOnLockscreen;
private int mCollapsedSize;
+ private boolean mEnableNotificationClipping;
private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
private boolean mIsExpanded;
@@ -85,6 +86,7 @@ public class StackScrollAlgorithm {
mPaddingBetweenElements = res.getDimensionPixelSize(
R.dimen.notification_divider_height);
mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height);
+ mEnableNotificationClipping = res.getBoolean(R.bool.notification_enable_clipping);
mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop);
int statusBarHeight = SystemBarUtils.getStatusBarHeight(context);
mHeadsUpInset = statusBarHeight + res.getDimensionPixelSize(
@@ -289,7 +291,7 @@ public class StackScrollAlgorithm {
// The bottom of this view is peeking out from under the previous view.
// Clip the part that is peeking out.
float overlapAmount = newNotificationEnd - firstHeadsUpEnd;
- state.clipBottomAmount = (int) overlapAmount;
+ state.clipBottomAmount = mEnableNotificationClipping ? (int) overlapAmount : 0;
} else {
state.clipBottomAmount = 0;
}
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 1ab9be79d3e2..be0818339b74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -54,7 +54,6 @@ import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.LightRevealScrim;
import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import java.io.PrintWriter;
@@ -254,8 +253,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
boolean isWakeUpComingFromTouch();
- boolean isFalsingThresholdNeeded();
-
void onKeyguardViewManagerStatesUpdated();
ViewGroup getNotificationScrollLayout();
@@ -413,12 +410,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
void onClosingFinished();
- void onUnlockHintStarted();
-
- void onHintFinished();
-
- void onTrackingStopped(boolean expand);
-
// TODO: Figure out way to remove these.
NavigationBarView getNavigationBarView();
@@ -500,8 +491,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
boolean isKeyguardSecure();
- NotificationGutsManager getGutsManager();
-
void updateNotificationPanelTouchState();
void makeExpandedVisible(boolean force);
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 18a08f73e596..4562e6930f91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -279,6 +279,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// 1020-1040 reserved for BaseStatusBar
/**
+ * TODO(b/249277686) delete this
* The delay to reset the hint text when the hint animation is finished running.
*/
private static final int HINT_RESET_DELAY_MS = 1200;
@@ -1784,11 +1785,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return mWakeUpComingFromTouch;
}
- @Override
- public boolean isFalsingThresholdNeeded() {
- return true;
- }
-
/**
* To be called when there's a state change in StatusBarKeyguardViewManager.
*/
@@ -3392,22 +3388,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
}
- @Override
- public void onUnlockHintStarted() {
- mFalsingCollector.onUnlockHintStarted();
- mKeyguardIndicationController.showActionToUnlock();
- }
-
- @Override
- public void onHintFinished() {
- // Delay the reset a bit so the user can read the text.
- mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
- }
-
- @Override
- public void onTrackingStopped(boolean expand) {
- }
-
// TODO: Figure out way to remove these.
@Override
public NavigationBarView getNavigationBarView() {
@@ -4138,11 +4118,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// End Extra BaseStatusBarMethods.
- @Override
- public NotificationGutsManager getGutsManager() {
- return mGutsManager;
- }
-
boolean isTransientShown() {
return mTransientShown;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 34cd1ceeffc7..7dcdc0bdb383 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -33,7 +33,7 @@ class ConfigurationControllerImpl @Inject constructor(context: Context) : Config
private val lastConfig = Configuration()
private var density: Int = 0
private var smallestScreenWidth: Int = 0
- private var maxBounds: Rect? = null
+ private var maxBounds = Rect()
private var fontScale: Float = 0.toFloat()
private val inCarMode: Boolean
private var uiMode: Int = 0
@@ -47,6 +47,7 @@ class ConfigurationControllerImpl @Inject constructor(context: Context) : Config
fontScale = currentConfig.fontScale
density = currentConfig.densityDpi
smallestScreenWidth = currentConfig.smallestScreenWidthDp
+ maxBounds.set(currentConfig.windowConfiguration.maxBounds)
inCarMode = currentConfig.uiMode and Configuration.UI_MODE_TYPE_MASK ==
Configuration.UI_MODE_TYPE_CAR
uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
@@ -92,7 +93,11 @@ class ConfigurationControllerImpl @Inject constructor(context: Context) : Config
val maxBounds = newConfig.windowConfiguration.maxBounds
if (maxBounds != this.maxBounds) {
- this.maxBounds = maxBounds
+ // Update our internal rect to have the same bounds, instead of using
+ // `this.maxBounds = maxBounds` directly. Setting it directly means that `maxBounds`
+ // would be a direct reference to windowConfiguration.maxBounds, so the if statement
+ // above would always fail. See b/245799099 for more information.
+ this.maxBounds.set(maxBounds)
listeners.filterForEach({ this.listeners.contains(it) }) {
it.onMaxBoundsChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 7a49a495155b..13566ef8c630 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -48,6 +48,9 @@ import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -70,7 +73,7 @@ public class KeyguardStatusBarView extends RelativeLayout {
private ImageView mMultiUserAvatar;
private BatteryMeterView mBatteryView;
private StatusIconContainer mStatusIconContainer;
- private ViewGroup mUserSwitcherContainer;
+ private StatusBarUserSwitcherContainer mUserSwitcherContainer;
private boolean mKeyguardUserSwitcherEnabled;
private boolean mKeyguardUserAvatarEnabled;
@@ -121,8 +124,12 @@ public class KeyguardStatusBarView extends RelativeLayout {
loadDimens();
}
- public ViewGroup getUserSwitcherContainer() {
- return mUserSwitcherContainer;
+ /**
+ * Should only be called from {@link KeyguardStatusBarViewController}
+ * @param viewModel view model for the status bar user chip
+ */
+ void init(StatusBarUserChipViewModel viewModel) {
+ StatusBarUserChipViewBinder.bind(mUserSwitcherContainer, viewModel);
}
@Override
@@ -304,10 +311,7 @@ public class KeyguardStatusBarView extends RelativeLayout {
lp = (LayoutParams) mStatusIconArea.getLayoutParams();
lp.removeRule(RelativeLayout.RIGHT_OF);
lp.width = LayoutParams.WRAP_CONTENT;
-
- LinearLayout.LayoutParams llp =
- (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
- llp.setMarginStart(getResources().getDimensionPixelSize(
+ lp.setMarginStart(getResources().getDimensionPixelSize(
R.dimen.system_icons_super_container_margin_start));
return true;
}
@@ -339,10 +343,7 @@ public class KeyguardStatusBarView extends RelativeLayout {
lp = (LayoutParams) mStatusIconArea.getLayoutParams();
lp.addRule(RelativeLayout.RIGHT_OF, R.id.cutout_space_view);
lp.width = LayoutParams.MATCH_PARENT;
-
- LinearLayout.LayoutParams llp =
- (LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
- llp.setMarginStart(0);
+ lp.setMarginStart(0);
return true;
}
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 14cebf4b9f4b..d4dc1dc197a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -59,13 +59,11 @@ import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.fragment.StatusBarIconBlocklistKt;
import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventAnimator;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
@@ -110,9 +108,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private final SysuiStatusBarStateController mStatusBarStateController;
private final StatusBarContentInsetsProvider mInsetsProvider;
private final UserManager mUserManager;
- private final StatusBarUserSwitcherFeatureController mFeatureController;
- private final StatusBarUserSwitcherController mUserSwitcherController;
- private final StatusBarUserInfoTracker mStatusBarUserInfoTracker;
+ private final StatusBarUserChipViewModel mStatusBarUserChipViewModel;
private final SecureSettings mSecureSettings;
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
@@ -276,9 +272,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
SysuiStatusBarStateController statusBarStateController,
StatusBarContentInsetsProvider statusBarContentInsetsProvider,
UserManager userManager,
- StatusBarUserSwitcherFeatureController featureController,
- StatusBarUserSwitcherController userSwitcherController,
- StatusBarUserInfoTracker statusBarUserInfoTracker,
+ StatusBarUserChipViewModel userChipViewModel,
SecureSettings secureSettings,
CommandQueue commandQueue,
@Main Executor mainExecutor,
@@ -301,9 +295,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mStatusBarStateController = statusBarStateController;
mInsetsProvider = statusBarContentInsetsProvider;
mUserManager = userManager;
- mFeatureController = featureController;
- mUserSwitcherController = userSwitcherController;
- mStatusBarUserInfoTracker = statusBarUserInfoTracker;
+ mStatusBarUserChipViewModel = userChipViewModel;
mSecureSettings = secureSettings;
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
@@ -328,8 +320,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
R.dimen.header_notifications_collide_distance);
mView.setKeyguardUserAvatarEnabled(
- !mFeatureController.isStatusBarUserSwitcherFeatureEnabled());
- mFeatureController.addCallback(enabled -> mView.setKeyguardUserAvatarEnabled(!enabled));
+ !mStatusBarUserChipViewModel.getChipEnabled());
mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, r);
mDisableStateTracker = new DisableStateTracker(
@@ -344,11 +335,11 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
super.onInit();
mCarrierTextController.init();
mBatteryMeterViewController.init();
- mUserSwitcherController.init();
}
@Override
protected void onViewAttached() {
+ mView.init(mStatusBarUserChipViewModel);
mConfigurationController.addCallback(mConfigurationListener);
mAnimationScheduler.addCallback(mAnimationCallback);
mUserInfoController.addCallback(mOnUserInfoChangedListener);
@@ -394,9 +385,6 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
/** Sets whether user switcher is enabled. */
public void setKeyguardUserSwitcherEnabled(boolean enabled) {
mView.setKeyguardUserSwitcherEnabled(enabled);
- // We don't have a listener for when the user switcher setting changes, so this is
- // where we re-check the state
- mStatusBarUserInfoTracker.checkEnabled();
}
/** Sets whether this controller should listen to battery updates. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 7aeb08dd5ddb..28bc64de2cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -38,6 +38,9 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
+import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.leak.RotationUtils;
import java.util.Objects;
@@ -73,6 +76,11 @@ public class PhoneStatusBarView extends FrameLayout {
mTouchEventHandler = handler;
}
+ void init(StatusBarUserChipViewModel viewModel) {
+ StatusBarUserSwitcherContainer container = findViewById(R.id.user_switcher_container);
+ StatusBarUserChipViewBinder.bind(container, viewModel);
+ }
+
@Override
public void onFinishInflate() {
super.onFinishInflate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index f9c4c8f3b4fe..a6c2b2c2771c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -23,11 +23,11 @@ import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.UNFOLD_STATUS_BAR
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.ViewController
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.view.ViewUtil
@@ -40,7 +40,7 @@ class PhoneStatusBarViewController private constructor(
view: PhoneStatusBarView,
@Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
- private val userSwitcherController: StatusBarUserSwitcherController,
+ private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
touchEventHandler: PhoneStatusBarView.TouchEventHandler,
private val configurationController: ConfigurationController
@@ -91,10 +91,10 @@ class PhoneStatusBarViewController private constructor(
init {
mView.setTouchEventHandler(touchEventHandler)
+ mView.init(userChipViewModel)
}
override fun onInit() {
- userSwitcherController.init()
}
fun setImportantForAccessibility(mode: Int) {
@@ -156,9 +156,9 @@ class PhoneStatusBarViewController private constructor(
private val unfoldComponent: Optional<SysUIUnfoldComponent>,
@Named(UNFOLD_STATUS_BAR)
private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
- private val userSwitcherController: StatusBarUserSwitcherController,
+ private val userChipViewModel: StatusBarUserChipViewModel,
private val viewUtil: ViewUtil,
- private val configurationController: ConfigurationController
+ private val configurationController: ConfigurationController,
) {
fun create(
view: PhoneStatusBarView,
@@ -168,7 +168,7 @@ class PhoneStatusBarViewController private constructor(
view,
progressProvider.getOrNull(),
unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
- userSwitcherController,
+ userChipViewModel,
viewUtil,
touchEventHandler,
configurationController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index 41f1f9589ce4..efec27099dcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -29,8 +29,6 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherControllerImpl;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -39,7 +37,6 @@ import java.util.Set;
import javax.inject.Named;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.Multibinds;
@@ -126,12 +123,6 @@ public interface StatusBarFragmentModule {
}
/** */
- @Binds
- @StatusBarFragmentScope
- StatusBarUserSwitcherController bindStatusBarUserSwitcherController(
- StatusBarUserSwitcherControllerImpl controller);
-
- /** */
@Provides
@StatusBarFragmentScope
static PhoneStatusBarViewController providePhoneStatusBarViewController(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
deleted file mode 100644
index f6b8cb0782cd..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserInfoTracker.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.userswitcher
-
-import android.graphics.drawable.Drawable
-import android.os.UserManager
-import com.android.systemui.Dumpable
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.statusbar.policy.CallbackController
-import com.android.systemui.statusbar.policy.UserInfoController
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
-import java.io.PrintWriter
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-/**
- * Since every user switcher chip will user the exact same information and logic on whether or not
- * to show, and what data to show, it makes sense to create a single tracker here
- */
-@SysUISingleton
-class StatusBarUserInfoTracker @Inject constructor(
- private val userInfoController: UserInfoController,
- private val userManager: UserManager,
- private val dumpManager: DumpManager,
- @Main private val mainExecutor: Executor,
- @Background private val backgroundExecutor: Executor
-) : CallbackController<CurrentUserChipInfoUpdatedListener>, Dumpable {
- var currentUserName: String? = null
- private set
- var currentUserAvatar: Drawable? = null
- private set
- var userSwitcherEnabled = false
- private set
- private var listening = false
-
- private val listeners = mutableListOf<CurrentUserChipInfoUpdatedListener>()
-
- private val userInfoChangedListener = OnUserInfoChangedListener { name, picture, _ ->
- currentUserAvatar = picture
- currentUserName = name
- notifyListenersUserInfoChanged()
- }
-
- init {
- dumpManager.registerDumpable(TAG, this)
- }
-
- override fun addCallback(listener: CurrentUserChipInfoUpdatedListener) {
- if (listeners.isEmpty()) {
- startListening()
- }
-
- if (!listeners.contains(listener)) {
- listeners.add(listener)
- }
- }
-
- override fun removeCallback(listener: CurrentUserChipInfoUpdatedListener) {
- listeners.remove(listener)
-
- if (listeners.isEmpty()) {
- stopListening()
- }
- }
-
- private fun notifyListenersUserInfoChanged() {
- listeners.forEach {
- it.onCurrentUserChipInfoUpdated()
- }
- }
-
- private fun notifyListenersSettingChanged() {
- listeners.forEach {
- it.onStatusBarUserSwitcherSettingChanged(userSwitcherEnabled)
- }
- }
-
- private fun startListening() {
- listening = true
- userInfoController.addCallback(userInfoChangedListener)
- }
-
- private fun stopListening() {
- listening = false
- userInfoController.removeCallback(userInfoChangedListener)
- }
-
- /**
- * Force a check to [UserManager.isUserSwitcherEnabled], and update listeners if the value has
- * changed
- */
- fun checkEnabled() {
- backgroundExecutor.execute {
- // Check on a background thread to avoid main thread Binder calls
- val wasEnabled = userSwitcherEnabled
- userSwitcherEnabled = userManager.isUserSwitcherEnabled
-
- if (wasEnabled != userSwitcherEnabled) {
- mainExecutor.execute {
- notifyListenersSettingChanged()
- }
- }
- }
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println(" userSwitcherEnabled=$userSwitcherEnabled")
- pw.println(" listening=$listening")
- }
-}
-
-interface CurrentUserChipInfoUpdatedListener {
- fun onCurrentUserChipInfoUpdated()
- fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {}
-}
-
-private const val TAG = "StatusBarUserInfoTracker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
deleted file mode 100644
index e498ae451400..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherController.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.userswitcher
-
-import android.content.Intent
-import android.os.UserHandle
-import android.view.View
-import com.android.systemui.animation.Expandable
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.user.UserSwitcherActivity
-import com.android.systemui.util.ViewController
-
-import javax.inject.Inject
-
-/**
- * ViewController for [StatusBarUserSwitcherContainer]
- */
-class StatusBarUserSwitcherControllerImpl @Inject constructor(
- view: StatusBarUserSwitcherContainer,
- private val tracker: StatusBarUserInfoTracker,
- private val featureController: StatusBarUserSwitcherFeatureController,
- private val userSwitcherDialogController: UserSwitchDialogController,
- private val featureFlags: FeatureFlags,
- private val activityStarter: ActivityStarter,
- private val falsingManager: FalsingManager
-) : ViewController<StatusBarUserSwitcherContainer>(view),
- StatusBarUserSwitcherController {
- private val listener = object : CurrentUserChipInfoUpdatedListener {
- override fun onCurrentUserChipInfoUpdated() {
- updateChip()
- }
-
- override fun onStatusBarUserSwitcherSettingChanged(enabled: Boolean) {
- updateEnabled()
- }
- }
-
- private val featureFlagListener = object : OnUserSwitcherPreferenceChangeListener {
- override fun onUserSwitcherPreferenceChange(enabled: Boolean) {
- updateEnabled()
- }
- }
-
- public override fun onViewAttached() {
- tracker.addCallback(listener)
- featureController.addCallback(featureFlagListener)
- mView.setOnClickListener { view: View ->
- if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
- return@setOnClickListener
- }
-
- if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- val intent = Intent(context, UserSwitcherActivity::class.java)
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
-
- activityStarter.startActivity(intent, true /* dismissShade */,
- null /* ActivityLaunchAnimator.Controller */,
- true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM)
- } else {
- userSwitcherDialogController.showDialog(view.context, Expandable.fromView(view))
- }
- }
-
- updateEnabled()
- }
-
- override fun onViewDetached() {
- tracker.removeCallback(listener)
- featureController.removeCallback(featureFlagListener)
- mView.setOnClickListener(null)
- }
-
- private fun updateChip() {
- mView.text.text = tracker.currentUserName
- mView.avatar.setImageDrawable(tracker.currentUserAvatar)
- }
-
- private fun updateEnabled() {
- if (featureController.isStatusBarUserSwitcherFeatureEnabled() &&
- tracker.userSwitcherEnabled) {
- mView.visibility = View.VISIBLE
- updateChip()
- } else {
- mView.visibility = View.GONE
- }
- }
-}
-
-interface StatusBarUserSwitcherController {
- fun init()
-}
-
-private const val TAG = "SbUserSwitcherController"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
deleted file mode 100644
index 7bae9ff72760..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherFeatureController.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.userswitcher
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.statusbar.policy.CallbackController
-
-import javax.inject.Inject
-
-@SysUISingleton
-class StatusBarUserSwitcherFeatureController @Inject constructor(
- private val flags: FeatureFlags
-) : CallbackController<OnUserSwitcherPreferenceChangeListener> {
- private val listeners = mutableListOf<OnUserSwitcherPreferenceChangeListener>()
-
- init {
- flags.addListener(Flags.STATUS_BAR_USER_SWITCHER) {
- it.requestNoRestart()
- notifyListeners()
- }
- }
-
- fun isStatusBarUserSwitcherFeatureEnabled(): Boolean {
- return flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER)
- }
-
- override fun addCallback(listener: OnUserSwitcherPreferenceChangeListener) {
- if (!listeners.contains(listener)) {
- listeners.add(listener)
- }
- }
-
- override fun removeCallback(listener: OnUserSwitcherPreferenceChangeListener) {
- listeners.remove(listener)
- }
-
- private fun notifyListeners() {
- val enabled = flags.isEnabled(Flags.STATUS_BAR_USER_SWITCHER)
- listeners.forEach {
- it.onUserSwitcherPreferenceChange(enabled)
- }
- }
-}
-
-interface OnUserSwitcherPreferenceChangeListener {
- fun onUserSwitcherPreferenceChange(enabled: Boolean)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 3c2ac7b7a124..2ee52325ca4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -156,6 +156,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
pw.print(" mPluggedIn="); pw.println(mPluggedIn);
pw.print(" mCharging="); pw.println(mCharging);
pw.print(" mCharged="); pw.println(mCharged);
+ pw.print(" mIsOverheated="); pw.println(mIsOverheated);
pw.print(" mPowerSave="); pw.println(mPowerSave);
pw.print(" mStateUnknown="); pw.println(mStateUnknown);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 38b3769c1071..acdf0d2bc32b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -146,7 +146,13 @@ public class BluetoothControllerImpl implements BluetoothController, BluetoothCa
}
private String getDeviceString(CachedBluetoothDevice device) {
- return device.getName() + " " + device.getBondState() + " " + device.isConnected();
+ return device.getName()
+ + " bondState=" + device.getBondState()
+ + " connected=" + device.isConnected()
+ + " active[A2DP]=" + device.isActiveDevice(BluetoothProfile.A2DP)
+ + " active[HEADSET]=" + device.isActiveDevice(BluetoothProfile.HEADSET)
+ + " active[HEARING_AID]=" + device.isActiveDevice(BluetoothProfile.HEARING_AID)
+ + " active[LE_AUDIO]=" + device.isActiveDevice(BluetoothProfile.LE_AUDIO);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
index ae73df201f8d..773ac55130d4 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleAnimationConfig.kt
@@ -27,6 +27,6 @@ data class RippleAnimationConfig(
companion object {
const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt()
- const val RIPPLE_DEFAULT_ALPHA: Int = 45 // full opacity is 255.
+ const val RIPPLE_DEFAULT_ALPHA: Int = 115 // full opacity is 255.
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index 299469494295..2ad824348794 100644
--- a/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -111,7 +111,7 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a
/**
* Set the color to be used for the ripple.
*
- * The alpha value of the color will be applied to the ripple. The alpha range is [0-100].
+ * The alpha value of the color will be applied to the ripple. The alpha range is [0-255].
*/
fun setColor(color: Int, alpha: Int = RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA) {
rippleShader.color = ColorUtils.setAlphaComponent(color, alpha)
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 44e5ce865241..fb17b693e17e 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -174,7 +174,7 @@ open class ChipbarCoordinator @Inject constructor(
chipInnerView,
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_DECELERATE,
- duration = ANIMATION_DURATION,
+ duration = ANIMATION_IN_DURATION,
includeMargins = true,
includeFadeIn = true,
// We can only request focus once the animation finishes.
@@ -187,7 +187,7 @@ open class ChipbarCoordinator @Inject constructor(
view.requireViewById<ViewGroup>(R.id.chipbar_inner),
ViewHierarchyAnimator.Hotspot.TOP,
Interpolators.EMPHASIZED_ACCELERATE,
- ANIMATION_DURATION,
+ ANIMATION_OUT_DURATION,
includeMargins = true,
onAnimationEnd,
)
@@ -208,4 +208,5 @@ open class ChipbarCoordinator @Inject constructor(
}
}
-private const val ANIMATION_DURATION = 500L
+private const val ANIMATION_IN_DURATION = 500L
+private const val ANIMATION_OUT_DURATION = 250L
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index ed53de7dbee7..4c9b8e4639ca 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -23,6 +23,7 @@ import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -79,6 +80,9 @@ interface UserRepository {
/** Whether we've scheduled the creation of a guest user. */
val isGuestUserCreationScheduled: AtomicBoolean
+ /** Whether to enable the status bar user chip (which launches the user switcher) */
+ val isStatusBarUserChipEnabled: Boolean
+
/** The user of the secondary service. */
var secondaryUserId: Int
@@ -127,6 +131,9 @@ constructor(
override val isGuestUserCreationScheduled = AtomicBoolean()
+ override val isStatusBarUserChipEnabled: Boolean =
+ appContext.resources.getBoolean(R.bool.flag_user_switcher_chip)
+
override var secondaryUserId: Int = UserHandle.USER_NULL
override var isRefreshUsersPaused: Boolean = false
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 6b81bf2cfb08..83f0711caa38 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -34,15 +34,20 @@ import android.util.Log
import com.android.internal.util.UserIcons
import com.android.systemui.R
import com.android.systemui.SystemUISecondaryUserService
+import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.UserSwitcherActivity
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.data.source.UserRecord
import com.android.systemui.user.domain.model.ShowDialogRequestModel
@@ -61,6 +66,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -79,6 +85,7 @@ constructor(
private val repository: UserRepository,
private val activityStarter: ActivityStarter,
private val keyguardInteractor: KeyguardInteractor,
+ private val featureFlags: FeatureFlags,
private val manager: UserManager,
@Application private val applicationScope: CoroutineScope,
telephonyInteractor: TelephonyInteractor,
@@ -108,12 +115,16 @@ constructor(
private val callbackMutex = Mutex()
private val callbacks = mutableSetOf<UserCallback>()
+ private val userInfos =
+ combine(repository.userSwitcherSettings, repository.userInfos) { settings, userInfos ->
+ userInfos.filter { !it.isGuest || canCreateGuestUser(settings) }
+ }
/** List of current on-device users to select from. */
val users: Flow<List<UserModel>>
get() =
combine(
- repository.userInfos,
+ userInfos,
repository.selectedUserInfo,
repository.userSwitcherSettings,
) { userInfos, selectedUserInfo, settings ->
@@ -147,22 +158,13 @@ constructor(
get() =
combine(
repository.selectedUserInfo,
- repository.userInfos,
+ userInfos,
repository.userSwitcherSettings,
keyguardInteractor.isKeyguardShowing,
) { _, userInfos, settings, isDeviceLocked ->
buildList {
val hasGuestUser = userInfos.any { it.isGuest }
- if (
- !hasGuestUser &&
- (guestUserInteractor.isGuestUserAutoCreated ||
- UserActionsUtil.canCreateGuest(
- manager,
- repository,
- settings.isUserSwitcherEnabled,
- settings.isAddUsersFromLockscreen,
- ))
- ) {
+ if (!hasGuestUser && canCreateGuestUser(settings)) {
add(UserActionModel.ENTER_GUEST_MODE)
}
@@ -211,7 +213,7 @@ constructor(
val userRecords: StateFlow<ArrayList<UserRecord>> =
combine(
- repository.userInfos,
+ userInfos,
repository.selectedUserInfo,
actions,
repository.userSwitcherSettings,
@@ -259,6 +261,9 @@ constructor(
/** Whether the guest user is currently being reset. */
val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
+ /** Whether to enable the user chip in the status bar */
+ val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled
+
private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
@@ -471,6 +476,26 @@ constructor(
}
}
+ fun showUserSwitcher(context: Context, expandable: Expandable) {
+ if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog)
+ return
+ }
+
+ val intent =
+ Intent(context, UserSwitcherActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ activityStarter.startActivity(
+ intent,
+ true /* dismissShade */,
+ expandable.activityLaunchController(),
+ true /* showOverlockscreenwhenlocked */,
+ UserHandle.SYSTEM,
+ )
+ }
+
private fun showDialog(request: ShowDialogRequestModel) {
_dialogShowRequests.value = request
}
@@ -687,6 +712,16 @@ constructor(
)
}
+ private fun canCreateGuestUser(settings: UserSwitcherSettingsModel): Boolean {
+ return guestUserInteractor.isGuestUserAutoCreated ||
+ UserActionsUtil.canCreateGuest(
+ manager,
+ repository,
+ settings.isUserSwitcherEnabled,
+ settings.isAddUsersFromLockscreen,
+ )
+ }
+
companion object {
private const val TAG = "UserInteractor"
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 177356e6b573..85c29647719b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -43,4 +43,7 @@ sealed class ShowDialogRequestModel(
val onExitGuestUser: (guestId: Int, targetId: Int, forceRemoveGuest: Boolean) -> Unit,
override val dialogShower: UserSwitchDialogController.DialogShower?,
) : ShowDialogRequestModel(dialogShower)
+
+ /** Show the user switcher dialog */
+ object ShowUserSwitcherDialog : ShowDialogRequestModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt
new file mode 100644
index 000000000000..8e40f68e27e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/StatusBarUserChipViewBinder.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.ui.binder
+
+import androidx.core.view.isVisible
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
+import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@OptIn(InternalCoroutinesApi::class)
+object StatusBarUserChipViewBinder {
+ /** Binds the status bar user chip view model to the given view */
+ @JvmStatic
+ fun bind(
+ view: StatusBarUserSwitcherContainer,
+ viewModel: StatusBarUserChipViewModel,
+ ) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.isChipVisible.collect { isVisible -> view.isVisible = isVisible }
+ }
+
+ launch {
+ viewModel.userName.collect { name -> TextViewBinder.bind(view.text, name) }
+ }
+
+ launch {
+ viewModel.userAvatar.collect { avatar -> view.avatar.setImageDrawable(avatar) }
+ }
+
+ bindButton(view, viewModel)
+ }
+ }
+ }
+
+ private fun bindButton(
+ view: StatusBarUserSwitcherContainer,
+ viewModel: StatusBarUserChipViewModel,
+ ) {
+ view.setOnClickListener { viewModel.onClick(Expandable.fromView(view)) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
new file mode 100644
index 000000000000..ed2589889435
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
@@ -0,0 +1,68 @@
+package com.android.systemui.user.ui.dialog
+
+import android.content.Context
+import android.content.Intent
+import android.provider.Settings
+import android.view.LayoutInflater
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+/**
+ * Extracted from the old UserSwitchDialogController. This is the dialog version of the full-screen
+ * user switcher. See config_enableFullscreenUserSwitcher
+ */
+class UserSwitchDialog(
+ context: Context,
+ adapter: UserDetailView.Adapter,
+ uiEventLogger: UiEventLogger,
+ falsingManager: FalsingManager,
+ activityStarter: ActivityStarter,
+ dialogLaunchAnimator: DialogLaunchAnimator,
+) : SystemUIDialog(context) {
+ init {
+ setShowForAllUsers(true)
+ setCanceledOnTouchOutside(true)
+ setTitle(R.string.qs_user_switch_dialog_title)
+ setPositiveButton(R.string.quick_settings_done) { _, _ ->
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE)
+ }
+ setNeutralButton(
+ R.string.quick_settings_more_user_settings,
+ { _, _ ->
+ if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+ uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
+ val controller =
+ dialogLaunchAnimator.createActivityLaunchController(
+ getButton(BUTTON_NEUTRAL)
+ )
+
+ if (controller == null) {
+ dismiss()
+ }
+
+ activityStarter.postStartActivityDismissingKeyguard(
+ USER_SETTINGS_INTENT,
+ 0,
+ controller
+ )
+ }
+ },
+ false /* dismissOnClick */
+ )
+ val gridFrame =
+ LayoutInflater.from(this.context).inflate(R.layout.qs_user_dialog_content, null)
+ setView(gridFrame)
+
+ adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
+ }
+
+ companion object {
+ private val USER_SETTINGS_INTENT = Intent(Settings.ACTION_USER_SETTINGS)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 58a4473186b3..41410542204c 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -20,6 +20,7 @@ package com.android.systemui.user.ui.dialog
import android.app.Dialog
import android.content.Context
import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.users.UserCreatingDialog
import com.android.systemui.CoreStartable
import com.android.systemui.animation.DialogCuj
@@ -27,11 +28,14 @@ import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.tiles.UserDetailView
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
import dagger.Lazy
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
@@ -47,6 +51,9 @@ constructor(
private val broadcastSender: Lazy<BroadcastSender>,
private val dialogLaunchAnimator: Lazy<DialogLaunchAnimator>,
private val interactor: Lazy<UserInteractor>,
+ private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>,
+ private val eventLogger: Lazy<UiEventLogger>,
+ private val activityStarter: Lazy<ActivityStarter>,
) : CoreStartable {
private var currentDialog: Dialog? = null
@@ -108,6 +115,21 @@ constructor(
INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
),
)
+ is ShowDialogRequestModel.ShowUserSwitcherDialog ->
+ Pair(
+ UserSwitchDialog(
+ context = context.get(),
+ adapter = userDetailAdapterProvider.get(),
+ uiEventLogger = eventLogger.get(),
+ falsingManager = falsingManager.get(),
+ activityStarter = activityStarter.get(),
+ dialogLaunchAnimator = dialogLaunchAnimator.get(),
+ ),
+ DialogCuj(
+ InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+ INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
+ ),
+ )
}
currentDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
new file mode 100644
index 000000000000..3300e8e5b2a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.ui.viewmodel
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.domain.interactor.UserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class StatusBarUserChipViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ interactor: UserInteractor,
+) {
+ /** Whether the status bar chip ui should be available */
+ val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
+
+ /** Whether or not the chip should be showing, based on the number of users */
+ val isChipVisible: Flow<Boolean> =
+ if (!chipEnabled) {
+ flowOf(false)
+ } else {
+ interactor.users.mapLatest { users -> users.size > 1 }
+ }
+
+ /** The display name of the current user */
+ val userName: Flow<Text> = interactor.selectedUser.mapLatest { userModel -> userModel.name }
+
+ /** Avatar for the current user */
+ val userAvatar: Flow<Drawable> =
+ interactor.selectedUser.mapLatest { userModel -> userModel.image }
+
+ /** Action to execute on click. Should launch the user switcher */
+ val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(context, it) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
index 6b5556b3ea91..0f3eddf2eb7c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
@@ -19,7 +19,6 @@ package com.android.systemui.util;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.Context;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -53,8 +52,8 @@ public class DeviceConfigProxy {
/**
* Wrapped version of {@link DeviceConfig#enforceReadPermission}.
*/
- public void enforceReadPermission(Context context, String namespace) {
- DeviceConfig.enforceReadPermission(context, namespace);
+ public void enforceReadPermission(String namespace) {
+ DeviceConfig.enforceReadPermission(namespace);
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index 131cf7d33e3a..88396628017b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -84,10 +84,9 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel(
userId = user,
listening = false,
authInterruptActive = false,
- becauseCannotSkipBouncer = false,
biometricSettingEnabledForUser = false,
bouncerFullyShown = false,
- faceAuthenticated = false,
+ faceAndFpNotAuthenticated = false,
faceDisabled = false,
faceLockedOut = false,
fpLockedOut = false,
@@ -101,4 +100,6 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel(
secureCameraLaunched = false,
switchingUser = false,
udfpsBouncerShowing = false,
+ udfpsFingerDown = false,
+ userNotTrustedOrDetectionIsNeeded = false
)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 5514fd0f9311..7231b3427a71 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -29,6 +29,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOM
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
+import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
import static com.google.common.truth.Truth.assertThat;
@@ -36,6 +37,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
@@ -89,6 +91,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.dreams.IDreamManager;
+import android.service.trust.TrustAgentService;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -353,7 +356,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@After
public void tearDown() {
- mMockitoSession.finishMocking();
+ if (mMockitoSession != null) {
+ mMockitoSession.finishMocking();
+ }
cleanupKeyguardUpdateMonitor();
}
@@ -1316,9 +1321,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
Arrays.asList("Unlocked by wearable"));
// THEN the showTrustGrantedMessage should be called with the first message
- verify(mTestCallback).onTrustGrantedWithFlags(
- eq(0),
- eq(KeyguardUpdateMonitor.getCurrentUser()),
+ verify(mTestCallback).onTrustGrantedForCurrentUser(
+ anyBoolean(),
+ eq(new TrustGrantFlags(0)),
eq("Unlocked by wearable"));
}
@@ -1376,6 +1381,29 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void testShouldListenForFace_whenFpIsAlreadyAuthenticated_returnsFalse()
+ throws RemoteException {
+ // Face auth should run when the following is true.
+ bouncerFullyVisibleAndNotGoingToSleep();
+ keyguardNotGoingAway();
+ currentUserIsPrimary();
+ strongAuthNotRequired();
+ biometricsEnabledForCurrentUser();
+ currentUserDoesNotHaveTrust();
+ biometricsNotDisabledThroughDevicePolicyManager();
+ userNotCurrentlySwitching();
+
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
+
+ successfulFingerprintAuth();
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
+ }
+
+ @Test
public void testShouldListenForFace_whenUserIsNotPrimary_returnsFalse() throws RemoteException {
cleanupKeyguardUpdateMonitor();
// This disables face auth
@@ -1729,6 +1757,155 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
}
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_deviceInteractive() {
+ // GIVEN device is interactive
+ deviceIsInteractive();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+ eq(null) /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_doesNotDismiss() {
+ // GIVEN device is NOT interactive
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD flag
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback).onTrustGrantedForCurrentUser(
+ eq(false) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD)),
+ eq(null) /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_dismissKeyguardRequested_temporaryAndRenewable() {
+ // GIVEN device is interactive
+ deviceIsInteractive();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged for a different user
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ 546 /* userId, not the current userId */,
+ 0 /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback, never()).onTrustGrantedForCurrentUser(
+ anyBoolean() /* dismissKeyguard */,
+ anyObject() /* flags */,
+ anyString() /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGranted_differentUser_noCallback() {
+ // GIVEN device is interactive
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with TRUST_DISMISS_KEYGUARD AND TRUST_TEMPORARY_AND_RENEWABLE
+ // flags (temporary & rewable is active unlock)
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)),
+ eq(null) /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_bouncerShowing_initiatedByUser() {
+ // GIVEN device is interactive & bouncer is showing
+ deviceIsInteractive();
+ bouncerFullyVisible();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with INITIATED_BY_USER flag
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId, not the current userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback, never()).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER)),
+ anyString() /* message */
+ );
+ }
+
+ @Test
+ public void testOnTrustGrantedForCurrentUser_bouncerShowing_temporaryRenewable() {
+ // GIVEN device is NOT interactive & bouncer is showing
+ bouncerFullyVisible();
+
+ // GIVEN callback is registered
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN onTrustChanged with INITIATED_BY_USER flag
+ mKeyguardUpdateMonitor.onTrustChanged(
+ true /* enabled */,
+ getCurrentUser() /* userId, not the current userId */,
+ TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE /* flags */,
+ null /* trustGrantedMessages */);
+
+ // THEN onTrustGrantedForCurrentUser callback called
+ verify(callback, never()).onTrustGrantedForCurrentUser(
+ eq(true) /* dismissKeyguard */,
+ eq(new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER
+ | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE)),
+ anyString() /* message */
+ );
+ }
+
private void cleanupKeyguardUpdateMonitor() {
if (mKeyguardUpdateMonitor != null) {
mKeyguardUpdateMonitor.removeCallback(mTestCallback);
@@ -1780,6 +1957,15 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
.onAuthenticationAcquired(FINGERPRINT_ACQUIRED_START);
}
+ private void successfulFingerprintAuth() {
+ mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
+ .onAuthenticationSucceeded(
+ new FingerprintManager.AuthenticationResult(null,
+ null,
+ mCurrentUserId,
+ true));
+ }
+
private void triggerSuccessfulFaceAuth() {
mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
verify(mFaceManager).authenticate(any(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index 75629f451526..3c61382d9446 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -94,6 +94,11 @@ public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase {
mKeyguardStateControllerCallbackCaptor;
protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
+ private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.KeyguardViewManagerCallback>
+ mKeyguardViewManagerCallbackArgumentCaptor;
+ protected StatusBarKeyguardViewManager.KeyguardViewManagerCallback mKeyguardViewManagerCallback;
+
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -143,15 +148,22 @@ public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase {
}
public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
- return createUdfpsKeyguardViewController(false);
+ return createUdfpsKeyguardViewController(false, false);
+ }
+
+ public void captureKeyGuardViewManagerCallback() {
+ verify(mStatusBarKeyguardViewManager).addCallback(
+ mKeyguardViewManagerCallbackArgumentCaptor.capture());
+ mKeyguardViewManagerCallback = mKeyguardViewManagerCallbackArgumentCaptor.getValue();
}
protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
- boolean useModernBouncer) {
+ boolean useModernBouncer, boolean useExpandedOverlay) {
mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
+ mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
when(mStatusBarKeyguardViewManager.getPrimaryBouncer()).thenReturn(
useModernBouncer ? null : mBouncer);
- return new UdfpsKeyguardViewController(
+ UdfpsKeyguardViewController controller = new UdfpsKeyguardViewController(
mView,
mStatusBarStateController,
mShadeExpansionStateManager,
@@ -168,5 +180,6 @@ public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase {
mActivityLaunchAnimator,
mFeatureFlags,
mPrimaryBouncerInteractor);
+ return controller;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 16728b6f2ab9..babe5334e3eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.biometrics;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
@@ -30,6 +31,7 @@ import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
@@ -52,7 +54,8 @@ public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewController
@Override
public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
- return createUdfpsKeyguardViewController(/* useModernBouncer */ false);
+ return createUdfpsKeyguardViewController(/* useModernBouncer */ false,
+ /* useExpandedOverlay */ false);
}
@Test
@@ -422,4 +425,37 @@ public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewController
verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
}
+
+ @Test
+ // TODO(b/259264861): Tracking Bug
+ public void testUdfpsExpandedOverlayOn() {
+ // GIVEN view is attached and useExpandedOverlay is true
+ mController = createUdfpsKeyguardViewController(false, true);
+ mController.onViewAttached();
+ captureKeyGuardViewManagerCallback();
+
+ // WHEN a touch is received
+ mKeyguardViewManagerCallback.onTouch(
+ MotionEvent.obtain(0, 0, 0, 0, 0, 0));
+
+ // THEN udfpsController onTouch is not called
+ assertTrue(mView.mUseExpandedOverlay);
+ verify(mUdfpsController, never()).onTouch(any());
+ }
+
+ @Test
+ // TODO(b/259264861): Tracking Bug
+ public void testUdfpsExpandedOverlayOff() {
+ // GIVEN view is attached and useExpandedOverlay is false
+ mController.onViewAttached();
+ captureKeyGuardViewManagerCallback();
+
+ // WHEN a touch is received
+ mKeyguardViewManagerCallback.onTouch(
+ MotionEvent.obtain(0, 0, 0, 0, 0, 0));
+
+ // THEN udfpsController onTouch is called
+ assertFalse(mView.mUseExpandedOverlay);
+ verify(mUdfpsController).onTouch(any());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index 68e744e53843..517e27a3ce2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -72,7 +72,10 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle
mock(KeyguardBypassController::class.java),
mKeyguardUpdateMonitor
)
- return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
+ return createUdfpsKeyguardViewController(
+ /* useModernBouncer */ true, /* useExpandedOverlay */
+ false
+ )
}
/** After migration, replaces LockIconViewControllerTest version */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index dedc7239bf85..98ff8d1d8845 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -46,6 +46,7 @@ import com.android.systemui.util.time.FakeSystemClock
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -480,6 +481,19 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
assertNull(controller.getCurrentServices()[0].panelActivity)
}
+ @Test
+ fun testListingsNotModifiedByCallback() {
+ // This test checks that if the list passed to the callback is modified, it has no effect
+ // in the resulting services
+ val list = mutableListOf<ServiceInfo>()
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ list.add(ServiceInfo(ComponentName("a", "b")))
+ executor.runAllReady()
+
+ assertTrue(controller.getCurrentServices().isEmpty())
+ }
+
private fun ServiceInfo(
componentName: ComponentName,
panelActivityComponentName: ComponentName? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
new file mode 100644
index 000000000000..1e7b1f26ba8c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class FeatureFlagsDebugRestarterTest : SysuiTestCase() {
+ private lateinit var restarter: FeatureFlagsDebugRestarter
+
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter)
+ }
+
+ @Test
+ fun testRestart_ImmediateWhenAsleep() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ restarter.restart()
+ verify(systemExitRestarter).restart()
+ }
+
+ @Test
+ fun testRestart_WaitsForSceenOff() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+
+ restarter.restart()
+ verify(systemExitRestarter, never()).restart()
+
+ val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
+ verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+ captor.value.onFinishedGoingToSleep()
+
+ verify(systemExitRestarter).restart()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
new file mode 100644
index 000000000000..68ca48dd327d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
+ private lateinit var restarter: FeatureFlagsReleaseRestarter
+
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var batteryController: BatteryController
+ @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ restarter =
+ FeatureFlagsReleaseRestarter(
+ wakefulnessLifecycle,
+ batteryController,
+ executor,
+ systemExitRestarter
+ )
+ }
+
+ @Test
+ fun testRestart_ScheduledWhenReady() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testRestart_RestartsWhenIdle() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ restarter.restart()
+ verify(systemExitRestarter, never()).restart()
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ verify(systemExitRestarter).restart()
+ }
+
+ @Test
+ fun testRestart_NotScheduledWhenAwake() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
+ @Test
+ fun testRestart_NotScheduledWhenNotPluggedIn() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
+
+ @Test
+ fun testRestart_NotDoubleSheduled() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+ restarter.restart()
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testWakefulnessLifecycle_CanRestart() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+
+ val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
+ verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+
+ captor.value.onFinishedGoingToSleep()
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testBatteryController_CanRestart() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+ assertThat(executor.numPending()).isEqualTo(0)
+ restarter.restart()
+
+ val captor =
+ ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
+ verify(batteryController).addCallback(captor.capture())
+
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ captor.value.onBatteryLevelChanged(0, true, true)
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
new file mode 100644
index 000000000000..8395f02cbc41
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard
+
+import android.content.ContentValues
+import android.content.pm.PackageManager
+import android.content.pm.ProviderInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.SystemUIAppComponentFactoryBase
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
+
+ @Mock private lateinit var lockPatternUtils: LockPatternUtils
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+
+ private lateinit var underTest: KeyguardQuickAffordanceProvider
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ underTest = KeyguardQuickAffordanceProvider()
+ val scope = CoroutineScope(IMMEDIATE)
+ val selectionManager =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ )
+ val quickAffordanceRepository =
+ KeyguardQuickAffordanceRepository(
+ appContext = context,
+ scope = scope,
+ selectionManager = selectionManager,
+ configs =
+ setOf(
+ FakeKeyguardQuickAffordanceConfig(
+ key = AFFORDANCE_1,
+ pickerIconResourceId = 1,
+ ),
+ FakeKeyguardQuickAffordanceConfig(
+ key = AFFORDANCE_2,
+ pickerIconResourceId = 2,
+ ),
+ ),
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = selectionManager,
+ ),
+ )
+ underTest.interactor =
+ KeyguardQuickAffordanceInteractor(
+ keyguardInteractor =
+ KeyguardInteractor(
+ repository = FakeKeyguardRepository(),
+ ),
+ registry = mock(),
+ lockPatternUtils = lockPatternUtils,
+ keyguardStateController = keyguardStateController,
+ userTracker = userTracker,
+ activityStarter = activityStarter,
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+ },
+ repository = { quickAffordanceRepository },
+ )
+
+ underTest.attachInfoForTesting(
+ context,
+ ProviderInfo().apply { authority = Contract.AUTHORITY },
+ )
+ context.contentResolver.addProvider(Contract.AUTHORITY, underTest)
+ context.testablePermissions.setPermission(
+ Contract.PERMISSION,
+ PackageManager.PERMISSION_GRANTED,
+ )
+ }
+
+ @Test
+ fun `onAttachInfo - reportsContext`() {
+ val callback: SystemUIAppComponentFactoryBase.ContextAvailableCallback = mock()
+ underTest.setContextAvailableCallback(callback)
+
+ underTest.attachInfo(context, null)
+
+ verify(callback).onContextAvailable(context)
+ }
+
+ @Test
+ fun getType() {
+ assertThat(underTest.getType(Contract.AffordanceTable.URI))
+ .isEqualTo(
+ "vnd.android.cursor.dir/vnd." +
+ "${Contract.AUTHORITY}.${Contract.AffordanceTable.TABLE_NAME}"
+ )
+ assertThat(underTest.getType(Contract.SlotTable.URI))
+ .isEqualTo(
+ "vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}.${Contract.SlotTable.TABLE_NAME}"
+ )
+ assertThat(underTest.getType(Contract.SelectionTable.URI))
+ .isEqualTo(
+ "vnd.android.cursor.dir/vnd." +
+ "${Contract.AUTHORITY}.${Contract.SelectionTable.TABLE_NAME}"
+ )
+ }
+
+ @Test
+ fun `insert and query selection`() =
+ runBlocking(IMMEDIATE) {
+ val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+ val affordanceId = AFFORDANCE_2
+
+ insertSelection(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ )
+
+ assertThat(querySelections())
+ .isEqualTo(
+ listOf(
+ Selection(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ )
+ )
+ )
+ }
+
+ @Test
+ fun `query slots`() =
+ runBlocking(IMMEDIATE) {
+ assertThat(querySlots())
+ .isEqualTo(
+ listOf(
+ Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ capacity = 1,
+ ),
+ Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ capacity = 1,
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun `query affordances`() =
+ runBlocking(IMMEDIATE) {
+ assertThat(queryAffordances())
+ .isEqualTo(
+ listOf(
+ Affordance(
+ id = AFFORDANCE_1,
+ name = AFFORDANCE_1,
+ iconResourceId = 1,
+ ),
+ Affordance(
+ id = AFFORDANCE_2,
+ name = AFFORDANCE_2,
+ iconResourceId = 2,
+ ),
+ )
+ )
+ }
+
+ @Test
+ fun `delete and query selection`() =
+ runBlocking(IMMEDIATE) {
+ insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = AFFORDANCE_1,
+ )
+ insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = AFFORDANCE_2,
+ )
+
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
+ " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
+ arrayOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ AFFORDANCE_2,
+ ),
+ )
+
+ assertThat(querySelections())
+ .isEqualTo(
+ listOf(
+ Selection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = AFFORDANCE_1,
+ )
+ )
+ )
+ }
+
+ @Test
+ fun `delete all selections in a slot`() =
+ runBlocking(IMMEDIATE) {
+ insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = AFFORDANCE_1,
+ )
+ insertSelection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ affordanceId = AFFORDANCE_2,
+ )
+
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ Contract.SelectionTable.Columns.SLOT_ID,
+ arrayOf(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ ),
+ )
+
+ assertThat(querySelections())
+ .isEqualTo(
+ listOf(
+ Selection(
+ slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ affordanceId = AFFORDANCE_1,
+ )
+ )
+ )
+ }
+
+ private fun insertSelection(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ context.contentResolver.insert(
+ Contract.SelectionTable.URI,
+ ContentValues().apply {
+ put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
+ put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
+ }
+ )
+ }
+
+ private fun querySelections(): List<Selection> {
+ return context.contentResolver
+ .query(
+ Contract.SelectionTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val slotIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
+ val affordanceIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+ if (slotIdColumnIndex == -1 || affordanceIdColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ Selection(
+ slotId = cursor.getString(slotIdColumnIndex),
+ affordanceId = cursor.getString(affordanceIdColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ private fun querySlots(): List<Slot> {
+ return context.contentResolver
+ .query(
+ Contract.SlotTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
+ val capacityColumnIndex =
+ cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
+ if (idColumnIndex == -1 || capacityColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ Slot(
+ id = cursor.getString(idColumnIndex),
+ capacity = cursor.getInt(capacityColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ private fun queryAffordances(): List<Affordance> {
+ return context.contentResolver
+ .query(
+ Contract.AffordanceTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex = cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
+ val nameColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
+ val iconColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
+ if (idColumnIndex == -1 || nameColumnIndex == -1 || iconColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ Affordance(
+ id = cursor.getString(idColumnIndex),
+ name = cursor.getString(nameColumnIndex),
+ iconResourceId = cursor.getInt(iconColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ data class Slot(
+ val id: String,
+ val capacity: Int,
+ )
+
+ data class Affordance(
+ val id: String,
+ val name: String,
+ val iconResourceId: Int,
+ )
+
+ data class Selection(
+ val slotId: String,
+ val affordanceId: String,
+ )
+
+ companion object {
+ private val IMMEDIATE = Dispatchers.Main.immediate
+ private const val AFFORDANCE_1 = "affordance_1"
+ private const val AFFORDANCE_2 = "affordance_2"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
new file mode 100644
index 000000000000..623becf166d3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.app.StatusBarManager
+import android.content.Context
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.CameraGestureHelper
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class CameraQuickAffordanceConfigTest : SysuiTestCase() {
+
+ @Mock private lateinit var cameraGestureHelper: CameraGestureHelper
+ @Mock private lateinit var context: Context
+ private lateinit var underTest: CameraQuickAffordanceConfig
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ underTest = CameraQuickAffordanceConfig(
+ context,
+ cameraGestureHelper,
+ )
+ }
+
+ @Test
+ fun `affordance triggered -- camera launch called`() {
+ //when
+ val result = underTest.onTriggered(null)
+
+ //then
+ verify(cameraGestureHelper)
+ .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
new file mode 100644
index 000000000000..8ef921eaa50a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.data.quickaffordance
+
+import android.content.Context
+import android.content.res.Resources
+import android.provider.Settings
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() {
+
+ @Mock private lateinit var sharedPrefs: FakeSharedPreferences
+
+ private lateinit var underTest: KeyguardQuickAffordanceLegacySettingSyncer
+
+ private lateinit var testScope: TestScope
+ private lateinit var testDispatcher: TestDispatcher
+ private lateinit var selectionManager: KeyguardQuickAffordanceSelectionManager
+ private lateinit var settings: FakeSettings
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ val context: Context = mock()
+ sharedPrefs = FakeSharedPreferences()
+ whenever(context.getSharedPreferences(anyString(), any())).thenReturn(sharedPrefs)
+ val resources: Resources = mock()
+ whenever(resources.getStringArray(R.array.config_keyguardQuickAffordanceDefaults))
+ .thenReturn(emptyArray())
+ whenever(context.resources).thenReturn(resources)
+
+ testDispatcher = UnconfinedTestDispatcher()
+ testScope = TestScope(testDispatcher)
+ selectionManager =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager =
+ mock {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = FakeUserTracker(),
+ )
+ settings = FakeSettings()
+ settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0)
+ settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET, 0)
+ settings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER, 0)
+
+ underTest =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = testScope,
+ backgroundDispatcher = testDispatcher,
+ secureSettings = settings,
+ selectionsManager = selectionManager,
+ )
+ }
+
+ @Test
+ fun `Setting a setting selects the affordance`() =
+ testScope.runTest {
+ val job = underTest.startSyncing()
+
+ settings.putInt(
+ Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ 1,
+ )
+
+ assertThat(
+ selectionManager
+ .getSelections()
+ .getOrDefault(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ emptyList()
+ )
+ )
+ .contains(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `Clearing a setting selects the affordance`() =
+ testScope.runTest {
+ val job = underTest.startSyncing()
+
+ settings.putInt(
+ Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ 1,
+ )
+ settings.putInt(
+ Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
+ 0,
+ )
+
+ assertThat(
+ selectionManager
+ .getSelections()
+ .getOrDefault(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ emptyList()
+ )
+ )
+ .doesNotContain(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `Selecting an affordance sets its setting`() =
+ testScope.runTest {
+ val job = underTest.startSyncing()
+
+ selectionManager.setSelections(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ listOf(BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET)
+ )
+
+ advanceUntilIdle()
+ assertThat(settings.getInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET)).isEqualTo(1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `Unselecting an affordance clears its setting`() =
+ testScope.runTest {
+ val job = underTest.startSyncing()
+
+ selectionManager.setSelections(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ listOf(BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET)
+ )
+ selectionManager.setSelections(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ emptyList()
+ )
+
+ assertThat(settings.getInt(Settings.Secure.LOCKSCREEN_SHOW_WALLET)).isEqualTo(0)
+
+ job.cancel()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
index d2422ad7b53b..d8ee9f113d33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
@@ -17,111 +17,312 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.SharedPreferences
+import android.content.pm.UserInfo
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(JUnit4::class)
class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
+ @Mock private lateinit var userFileManager: UserFileManager
+
private lateinit var underTest: KeyguardQuickAffordanceSelectionManager
+ private lateinit var userTracker: FakeUserTracker
+ private lateinit var sharedPrefs: MutableMap<Int, SharedPreferences>
+
@Before
fun setUp() {
- underTest = KeyguardQuickAffordanceSelectionManager()
+ MockitoAnnotations.initMocks(this)
+ sharedPrefs = mutableMapOf()
+ whenever(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt())).thenAnswer {
+ val userId = it.arguments[2] as Int
+ sharedPrefs.getOrPut(userId) { FakeSharedPreferences() }
+ }
+ userTracker = FakeUserTracker()
+
+ underTest =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager = userFileManager,
+ userTracker = userTracker,
+ )
}
@Test
- fun setSelections() =
- runBlocking(IMMEDIATE) {
- var affordanceIdsBySlotId: Map<String, List<String>>? = null
- val job = underTest.selections.onEach { affordanceIdsBySlotId = it }.launchIn(this)
- val slotId1 = "slot1"
- val slotId2 = "slot2"
- val affordanceId1 = "affordance1"
- val affordanceId2 = "affordance2"
- val affordanceId3 = "affordance3"
-
- underTest.setSelections(
- slotId = slotId1,
- affordanceIds = listOf(affordanceId1),
- )
- assertSelections(
- affordanceIdsBySlotId,
- mapOf(
- slotId1 to listOf(affordanceId1),
- ),
- )
+ fun setSelections() = runTest {
+ overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
- underTest.setSelections(
- slotId = slotId2,
- affordanceIds = listOf(affordanceId2),
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId1),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId1),
+ ),
+ )
+
+ underTest.setSelections(
+ slotId = slotId2,
+ affordanceIds = listOf(affordanceId2),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId1),
+ slotId2 to listOf(affordanceId2),
)
- assertSelections(
- affordanceIdsBySlotId,
- mapOf(
- slotId1 to listOf(affordanceId1),
- slotId2 to listOf(affordanceId2),
- )
+ )
+
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId1, affordanceId3),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId1, affordanceId3),
+ slotId2 to listOf(affordanceId2),
)
+ )
- underTest.setSelections(
- slotId = slotId1,
- affordanceIds = listOf(affordanceId1, affordanceId3),
+ underTest.setSelections(
+ slotId = slotId1,
+ affordanceIds = listOf(affordanceId3),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId3),
+ slotId2 to listOf(affordanceId2),
)
- assertSelections(
- affordanceIdsBySlotId,
- mapOf(
- slotId1 to listOf(affordanceId1, affordanceId3),
- slotId2 to listOf(affordanceId2),
- )
+ )
+
+ underTest.setSelections(
+ slotId = slotId2,
+ affordanceIds = listOf(),
+ )
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId3),
+ slotId2 to listOf(),
)
+ )
- underTest.setSelections(
- slotId = slotId1,
- affordanceIds = listOf(affordanceId3),
+ job.cancel()
+ }
+
+ @Test
+ fun `remembers selections by user`() = runTest {
+ val slot1 = "slot_1"
+ val slot2 = "slot_2"
+ val affordance1 = "affordance_1"
+ val affordance2 = "affordance_2"
+ val affordance3 = "affordance_3"
+
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+
+ val userInfos =
+ listOf(
+ UserInfo(/* id= */ 0, "zero", /* flags= */ 0),
+ UserInfo(/* id= */ 1, "one", /* flags= */ 0),
)
- assertSelections(
- affordanceIdsBySlotId,
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ underTest.setSelections(
+ slotId = slot1,
+ affordanceIds = listOf(affordance1),
+ )
+ underTest.setSelections(
+ slotId = slot2,
+ affordanceIds = listOf(affordance2),
+ )
+
+ // Switch to user 1
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 1,
+ )
+ // We never set selections on user 1, so it should be empty.
+ assertSelections(
+ observed = affordanceIdsBySlotId.last(),
+ expected = emptyMap(),
+ )
+ // Now, let's set selections on user 1.
+ underTest.setSelections(
+ slotId = slot1,
+ affordanceIds = listOf(affordance2),
+ )
+ underTest.setSelections(
+ slotId = slot2,
+ affordanceIds = listOf(affordance3),
+ )
+ assertSelections(
+ observed = affordanceIdsBySlotId.last(),
+ expected =
mapOf(
- slotId1 to listOf(affordanceId3),
- slotId2 to listOf(affordanceId2),
- )
- )
+ slot1 to listOf(affordance2),
+ slot2 to listOf(affordance3),
+ ),
+ )
- underTest.setSelections(
- slotId = slotId2,
- affordanceIds = listOf(),
- )
- assertSelections(
- affordanceIdsBySlotId,
+ // Switch back to user 0.
+ userTracker.set(
+ userInfos = userInfos,
+ selectedUserIndex = 0,
+ )
+ // Assert that we still remember the old selections for user 0.
+ assertSelections(
+ observed = affordanceIdsBySlotId.last(),
+ expected =
mapOf(
- slotId1 to listOf(affordanceId3),
- slotId2 to listOf(),
- )
- )
+ slot1 to listOf(affordance1),
+ slot2 to listOf(affordance2),
+ ),
+ )
- job.cancel()
- }
+ job.cancel()
+ }
+
+ @Test
+ fun `selections respects defaults`() = runTest {
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf(
+ "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}",
+ "$slotId2:${listOf(affordanceId2).joinToString(",")}",
+ ),
+ )
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId1, affordanceId3),
+ slotId2 to listOf(affordanceId2),
+ ),
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `selections ignores defaults after selecting an affordance`() = runTest {
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf(
+ "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}",
+ "$slotId2:${listOf(affordanceId2).joinToString(",")}",
+ ),
+ )
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+
+ underTest.setSelections(slotId1, listOf(affordanceId2))
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(affordanceId2),
+ slotId2 to listOf(affordanceId2),
+ ),
+ )
+
+ job.cancel()
+ }
+
+ @Test
+ fun `selections ignores defaults after clearing a slot`() = runTest {
+ val slotId1 = "slot1"
+ val slotId2 = "slot2"
+ val affordanceId1 = "affordance1"
+ val affordanceId2 = "affordance2"
+ val affordanceId3 = "affordance3"
+ overrideResource(
+ R.array.config_keyguardQuickAffordanceDefaults,
+ arrayOf(
+ "$slotId1:${listOf(affordanceId1, affordanceId3).joinToString(",")}",
+ "$slotId2:${listOf(affordanceId2).joinToString(",")}",
+ ),
+ )
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
- private suspend fun assertSelections(
+ underTest.setSelections(slotId1, listOf())
+ assertSelections(
+ affordanceIdsBySlotId.last(),
+ mapOf(
+ slotId1 to listOf(),
+ slotId2 to listOf(affordanceId2),
+ ),
+ )
+
+ job.cancel()
+ }
+
+ private fun assertSelections(
observed: Map<String, List<String>>?,
expected: Map<String, List<String>>,
) {
assertThat(underTest.getSelections()).isEqualTo(expected)
assertThat(observed).isEqualTo(expected)
}
-
- companion object {
- private val IMMEDIATE = Dispatchers.Main.immediate
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 5a7f2bb5cb37..d8a360567a07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -18,13 +18,20 @@
package com.android.systemui.keyguard.data.repository
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -36,6 +43,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -51,11 +60,36 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
fun setUp() {
config1 = FakeKeyguardQuickAffordanceConfig("built_in:1")
config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
+ val scope = CoroutineScope(IMMEDIATE)
+ val selectionManager =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = FakeUserTracker(),
+ )
+
underTest =
KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ appContext = context,
+ scope = scope,
+ selectionManager = selectionManager,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = selectionManager,
+ ),
configs = setOf(config1, config2),
)
}
@@ -119,16 +153,32 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
@Test
fun getSlotPickerRepresentations() {
+ val slot1 = "slot1"
+ val slot2 = "slot2"
+ val slot3 = "slot3"
+ context.orCreateTestableResources.addOverride(
+ R.array.config_keyguardQuickAffordanceSlots,
+ arrayOf(
+ "$slot1:2",
+ "$slot2:4",
+ "$slot3:5",
+ ),
+ )
+
assertThat(underTest.getSlotPickerRepresentations())
.isEqualTo(
listOf(
KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
- maxSelectedAffordances = 1,
+ id = slot1,
+ maxSelectedAffordances = 2,
+ ),
+ KeyguardSlotPickerRepresentation(
+ id = slot2,
+ maxSelectedAffordances = 4,
),
KeyguardSlotPickerRepresentation(
- id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
- maxSelectedAffordances = 1,
+ id = slot3,
+ maxSelectedAffordances = 5,
),
)
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 8b6603d79244..1e1d3f19d83c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -30,17 +30,22 @@ import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runBlockingTest
@@ -50,6 +55,8 @@ import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameter
import org.junit.runners.Parameterized.Parameters
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.same
import org.mockito.Mock
@@ -201,7 +208,6 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
- @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
@Mock private lateinit var expandable: Expandable
@@ -214,12 +220,14 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
@JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
@JvmField @Parameter(4) var startActivity: Boolean = false
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
+ private lateinit var userTracker: UserTracker
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(expandable.activityLaunchController()).thenReturn(animationController)
+ userTracker = FakeUserTracker()
homeControls =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
val quickAccessWallet =
@@ -228,11 +236,35 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
)
val qrCodeScanner =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+ val scope = CoroutineScope(IMMEDIATE)
+ val selectionManager =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ appContext = context,
+ scope = scope,
+ selectionManager = selectionManager,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = selectionManager,
+ ),
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
)
underTest =
@@ -318,7 +350,6 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
needStrongAuthAfterBoot: Boolean = true,
keyguardIsUnlocked: Boolean = false,
) {
- whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(any()))
.thenReturn(
if (needStrongAuthAfterBoot) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 33645354d9f3..c47e6f52c596 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -27,6 +27,7 @@ import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
@@ -35,11 +36,14 @@ import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAff
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -53,6 +57,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@@ -89,12 +95,36 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
)
qrCodeScanner =
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
-
+ val scope = CoroutineScope(IMMEDIATE)
+
+ val selectionManager =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ appContext = context,
+ scope = scope,
+ selectionManager = selectionManager,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = selectionManager,
+ ),
configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
)
featureFlags =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 78148c4d3d1b..ecc63ecc879d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -28,6 +28,7 @@ import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
@@ -38,10 +39,13 @@ import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAff
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlin.math.max
import kotlin.math.min
@@ -56,6 +60,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verifyZeroInteractions
@@ -115,11 +120,35 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
+ val scope = CoroutineScope(IMMEDIATE)
+ val selectionManager =
+ KeyguardQuickAffordanceSelectionManager(
+ context = context,
+ userFileManager =
+ mock<UserFileManager>().apply {
+ whenever(
+ getSharedPreferences(
+ anyString(),
+ anyInt(),
+ anyInt(),
+ )
+ )
+ .thenReturn(FakeSharedPreferences())
+ },
+ userTracker = userTracker,
+ )
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
- scope = CoroutineScope(IMMEDIATE),
- backgroundDispatcher = IMMEDIATE,
- selectionManager = KeyguardQuickAffordanceSelectionManager(),
+ appContext = context,
+ scope = scope,
+ selectionManager = selectionManager,
+ legacySettingSyncer =
+ KeyguardQuickAffordanceLegacySettingSyncer(
+ scope = scope,
+ backgroundDispatcher = IMMEDIATE,
+ secureSettings = FakeSettings(),
+ selectionsManager = selectionManager,
+ ),
configs =
setOf(
homeControlsQuickAffordanceConfig,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index 11eb26b1da02..52b694fac07c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -20,6 +20,8 @@ import android.app.Notification
import android.app.Notification.MediaStyle
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
+import android.app.smartspace.SmartspaceConfig
+import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceTarget
import android.content.Intent
import android.graphics.Bitmap
@@ -60,7 +62,6 @@ import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -106,6 +107,7 @@ class MediaDataManagerTest : SysuiTestCase() {
lateinit var metadataBuilder: MediaMetadata.Builder
lateinit var backgroundExecutor: FakeExecutor
lateinit var foregroundExecutor: FakeExecutor
+ lateinit var uiExecutor: FakeExecutor
@Mock lateinit var dumpManager: DumpManager
@Mock lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock lateinit var mediaTimeoutListener: MediaTimeoutListener
@@ -117,6 +119,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Mock lateinit var listener: MediaDataManager.Listener
@Mock lateinit var pendingIntent: PendingIntent
@Mock lateinit var activityStarter: ActivityStarter
+ @Mock lateinit var smartspaceManager: SmartspaceManager
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
@@ -131,6 +134,7 @@ class MediaDataManagerTest : SysuiTestCase() {
@Mock private lateinit var tunerService: TunerService
@Captor lateinit var tunableCaptor: ArgumentCaptor<TunerService.Tunable>
@Captor lateinit var callbackCaptor: ArgumentCaptor<(String, PlaybackState) -> Unit>
+ @Captor lateinit var smartSpaceConfigBuilderCaptor: ArgumentCaptor<SmartspaceConfig>
private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20)
@@ -145,6 +149,7 @@ class MediaDataManagerTest : SysuiTestCase() {
fun setup() {
foregroundExecutor = FakeExecutor(clock)
backgroundExecutor = FakeExecutor(clock)
+ uiExecutor = FakeExecutor(clock)
smartspaceMediaDataProvider = SmartspaceMediaDataProvider()
Settings.Secure.putInt(
context.contentResolver,
@@ -155,6 +160,7 @@ class MediaDataManagerTest : SysuiTestCase() {
MediaDataManager(
context = context,
backgroundExecutor = backgroundExecutor,
+ uiExecutor = uiExecutor,
foregroundExecutor = foregroundExecutor,
mediaControllerFactory = mediaControllerFactory,
broadcastDispatcher = broadcastDispatcher,
@@ -172,7 +178,8 @@ class MediaDataManagerTest : SysuiTestCase() {
systemClock = clock,
tunerService = tunerService,
mediaFlags = mediaFlags,
- logger = logger
+ logger = logger,
+ smartspaceManager = smartspaceManager,
)
verify(tunerService)
.addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION))
@@ -191,6 +198,7 @@ class MediaDataManagerTest : SysuiTestCase() {
putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
+ verify(smartspaceManager).createSmartspaceSession(capture(smartSpaceConfigBuilderCaptor))
whenever(mediaControllerFactory.create(eq(session.sessionToken))).thenReturn(controller)
whenever(controller.transportControls).thenReturn(transportControls)
whenever(controller.playbackInfo).thenReturn(playbackInfo)
@@ -767,15 +775,14 @@ class MediaDataManagerTest : SysuiTestCase() {
.onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
}
- @Ignore("b/233283726")
@Test
fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() {
smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget))
verify(logger).getNewInstanceId()
smartspaceMediaDataProvider.onTargetsAvailable(listOf())
- foregroundExecutor.advanceClockToLast()
- foregroundExecutor.runAllReady()
+ uiExecutor.advanceClockToLast()
+ uiExecutor.runAllReady()
verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(false))
verifyNoMoreInteractions(logger)
@@ -798,7 +805,6 @@ class MediaDataManagerTest : SysuiTestCase() {
.onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean())
}
- @Ignore("b/229838140")
@Test
fun testMediaRecommendationDisabled_removesSmartspaceData() {
// GIVEN a media recommendation card is present
@@ -815,7 +821,9 @@ class MediaDataManagerTest : SysuiTestCase() {
tunableCaptor.value.onTuningChanged(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION, "0")
// THEN listeners are notified
+ uiExecutor.advanceClockToLast()
foregroundExecutor.advanceClockToLast()
+ uiExecutor.runAllReady()
foregroundExecutor.runAllReady()
verify(listener).onSmartspaceMediaDataRemoved(eq(KEY_MEDIA_SMARTSPACE), eq(true))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
index 2c2ddbb9b8c5..645b1cde632f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -16,10 +16,8 @@
package com.android.systemui.qs.footer.domain.interactor
-import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.os.UserHandle
import android.provider.Settings
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -30,17 +28,13 @@ import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.QSSecurityFooterUtils
import com.android.systemui.qs.footer.FooterActionsTestUtils
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.truth.correspondence.FakeUiEvent
import com.android.systemui.truth.correspondence.LogMaker
-import com.android.systemui.user.UserSwitcherActivity
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
@@ -156,54 +150,4 @@ class FooterActionsInteractorTest : SysuiTestCase() {
// We only unlock the device.
verify(activityStarter).postQSRunnableDismissingKeyguard(any())
}
-
- @Test
- fun showUserSwitcher_fullScreenDisabled() {
- val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
- val userSwitchDialogController = mock<UserSwitchDialogController>()
- val underTest =
- utils.footerActionsInteractor(
- featureFlags = featureFlags,
- userSwitchDialogController = userSwitchDialogController,
- )
-
- val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
-
- // Dialog is shown.
- verify(userSwitchDialogController).showDialog(context, expandable)
- }
-
- @Test
- fun showUserSwitcher_fullScreenEnabled() {
- val featureFlags = FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }
- val activityStarter = mock<ActivityStarter>()
- val underTest =
- utils.footerActionsInteractor(
- featureFlags = featureFlags,
- activityStarter = activityStarter,
- )
-
- // The clicked expandable.
- val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
-
- // Dialog is shown.
- val intentCaptor = argumentCaptor<Intent>()
- verify(activityStarter)
- .startActivity(
- intentCaptor.capture(),
- /* dismissShade= */ eq(true),
- /* ActivityLaunchAnimator.Controller= */ nullable(),
- /* showOverLockscreenWhenLocked= */ eq(true),
- eq(UserHandle.SYSTEM),
- )
- assertThat(intentCaptor.value.component)
- .isEqualTo(
- ComponentName(
- context,
- UserSwitcherActivity::class.java,
- )
- )
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 8c9404e336ca..85c8ba7e77b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -184,7 +184,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
ActionTransition::new, mSmartActionsProvider);
Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
- Uri.parse("Screenshot_123.png")).get().action;
+ Uri.parse("Screenshot_123.png"), true).get().action;
Intent intent = shareAction.actionIntent.getIntent();
assertNotNull(intent);
@@ -212,7 +212,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
ActionTransition::new, mSmartActionsProvider);
Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
- Uri.parse("Screenshot_123.png")).get().action;
+ Uri.parse("Screenshot_123.png"), true).get().action;
Intent intent = editAction.actionIntent.getIntent();
assertNotNull(intent);
@@ -241,7 +241,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
Notification.Action deleteAction = task.createDeleteAction(mContext,
mContext.getResources(),
- Uri.parse("Screenshot_123.png"));
+ Uri.parse("Screenshot_123.png"), true);
Intent intent = deleteAction.actionIntent.getIntent();
assertNotNull(intent);
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 7d2251e20021..69a45599668b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -133,6 +133,7 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
@@ -198,6 +199,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock private KeyguardBottomAreaView mQsFrame;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationShelfController mNotificationShelfController;
+ @Mock private NotificationGutsManager mGutsManager;
@Mock private KeyguardStatusBarView mKeyguardStatusBar;
@Mock private KeyguardUserSwitcherView mUserSwitcherView;
@Mock private ViewStub mUserSwitcherStubView;
@@ -453,6 +455,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
() -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
mConversationNotificationManager, mMediaHierarchyManager,
mStatusBarKeyguardViewManager,
+ mGutsManager,
mNotificationsQSContainerController,
mNotificationStackScrollLayoutController,
mKeyguardStatusViewComponentFactory,
@@ -754,6 +757,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Test
public void testOnTouchEvent_expansionResumesAfterBriefTouch() {
+ mFalsingManager.setIsClassifierEnabled(true);
+ mFalsingManager.setIsFalseTouch(false);
// Start shade collapse with swipe up
onTouchEvent(MotionEvent.obtain(0L /* downTime */,
0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
@@ -1119,6 +1124,19 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
+ public void testUnlockedSplitShadeTransitioningToKeyguard_closesQS() {
+ enableSplitShade(true);
+ mStatusBarStateController.setState(SHADE);
+ mNotificationPanelViewController.setQsExpanded(true);
+
+ mStatusBarStateController.setState(KEYGUARD);
+
+
+ assertThat(mNotificationPanelViewController.isQsExpanded()).isEqualTo(false);
+ assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isEqualTo(false);
+ }
+
+ @Test
public void testSwitchesToCorrectClockInSinglePaneShade() {
mStatusBarStateController.setState(KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index db7e017e379e..c3207c2f58a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.NotificationInsetsController
import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -94,6 +95,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock
private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock
+ private lateinit var notificationInsetsController: NotificationInsetsController
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerContainer: ViewGroup
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@@ -124,6 +127,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
centralSurfaces,
notificationShadeWindowController,
keyguardUnlockAnimationController,
+ notificationInsetsController,
ambientState,
pulsingGestureListener,
featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index a6c80ab649e5..4bf00c4ccb51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -43,6 +43,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.NotificationInsetsController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -91,6 +92,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
@Mock private FeatureFlags mFeatureFlags;
@Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
@Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
+ @Mock private NotificationInsetsController mNotificationInsetsController;
@Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
mInteractionEventHandlerCaptor;
@@ -125,6 +127,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
mCentralSurfaces,
mNotificationShadeWindowController,
mKeyguardUnlockAnimationController,
+ mNotificationInsetsController,
mAmbientState,
mPulsingGestureListener,
mFeatureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index f5bed79b5e6f..a7588ddcaeef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -43,6 +43,7 @@ import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.notNull
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
@@ -178,4 +179,12 @@ class DefaultClockProviderTest : SysuiTestCase() {
verify(mockSmallClockView, times(2)).refreshFormat()
verify(mockLargeClockView, times(2)).refreshFormat()
}
+
+ @Test
+ fun test_aodClock_always_whiteColor() {
+ val clock = provider.createClock(DEFAULT_CLOCK_ID)
+ clock.animations.doze(0.9f) // set AOD mode to active
+ clock.smallClock.events.onRegionDarknessChanged(true)
+ verify((clock.smallClock.view as AnimatableClockView), never()).animateAppearOnLockscreen()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index e8a7ec82aab1..c8a392b11363 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -32,12 +32,12 @@ import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewCont
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
-import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_OFF;
import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_TURNING_ON;
import static com.google.common.truth.Truth.assertThat;
@@ -87,6 +87,7 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.TrustGrantFlags;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
@@ -829,31 +830,6 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
}
@Test
- public void updateMonitor_listenerUpdatesIndication() {
- createController();
- String restingIndication = "Resting indication";
- reset(mKeyguardUpdateMonitor);
-
- mController.setVisible(true);
- verifyIndicationMessage(INDICATION_TYPE_USER_LOCKED,
- mContext.getString(com.android.internal.R.string.lockscreen_storage_locked));
-
- reset(mRotateTextViewController);
- when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
- when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
- mController.setRestingIndication(restingIndication);
- verifyHideIndication(INDICATION_TYPE_USER_LOCKED);
- verifyIndicationMessage(INDICATION_TYPE_RESTING, restingIndication);
-
- reset(mRotateTextViewController);
- reset(mKeyguardUpdateMonitor);
- when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
- when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false);
- mKeyguardStateControllerCallback.onUnlockedChanged();
- verifyIndicationMessage(INDICATION_TYPE_RESTING, restingIndication);
- }
-
- @Test
public void onRefreshBatteryInfo_computesChargingTime() throws RemoteException {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
@@ -1068,7 +1044,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
// GIVEN a trust granted message but trust isn't granted
final String trustGrantedMsg = "testing trust granted message";
- mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, trustGrantedMsg);
+ mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+ false, new TrustGrantFlags(0), trustGrantedMsg);
verifyHideIndication(INDICATION_TYPE_TRUST);
@@ -1092,7 +1069,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
// WHEN the showTrustGranted method is called
final String trustGrantedMsg = "testing trust granted message";
- mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, trustGrantedMsg);
+ mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+ false, new TrustGrantFlags(0), trustGrantedMsg);
// THEN verify the trust granted message shows
verifyIndicationMessage(
@@ -1109,7 +1087,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
// WHEN the showTrustGranted method is called with a null message
- mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, null);
+ mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+ false, new TrustGrantFlags(0), null);
// THEN verify the default trust granted message shows
verifyIndicationMessage(
@@ -1126,7 +1105,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
// WHEN the showTrustGranted method is called with an EMPTY string
- mController.getKeyguardCallback().onTrustGrantedWithFlags(0, 0, "");
+ mController.getKeyguardCallback().onTrustGrantedForCurrentUser(
+ false, new TrustGrantFlags(0), "");
// THEN verify NO trust message is shown
verifyNoMessage(INDICATION_TYPE_TRUST);
@@ -1488,6 +1468,44 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
}
@Test
+ public void onFpLockoutStateChanged_whenFpIsLockedOut_showsPersistentMessage() {
+ createController();
+ mController.setVisible(true);
+ when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(true);
+
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+
+ verifyIndicationShown(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onFpLockoutStateChanged_whenFpIsNotLockedOut_showsPersistentMessage() {
+ createController();
+ mController.setVisible(true);
+ clearInvocations(mRotateTextViewController);
+ when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(false);
+
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+
+ verifyHideIndication(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE);
+ }
+
+ @Test
+ public void onVisibilityChange_showsPersistentMessage_ifFpIsLockedOut() {
+ createController();
+ mController.setVisible(false);
+ when(mKeyguardUpdateMonitor.isFingerprintLockedOut()).thenReturn(true);
+ mKeyguardUpdateMonitorCallback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT);
+ clearInvocations(mRotateTextViewController);
+
+ mController.setVisible(true);
+
+ verifyIndicationShown(INDICATION_TYPE_PERSISTENT_UNLOCK_MESSAGE,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
public void onBiometricError_whenFaceIsLocked_onMultipleLockOutErrors_showUnavailableMessage() {
createController();
onFaceLockoutError("first lockout");
@@ -1501,6 +1519,44 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mContext.getString(R.string.keyguard_face_unlock_unavailable));
}
+ @Test
+ public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsNotAvailable_showsMessage() {
+ createController();
+ screenIsTurningOn();
+ fingerprintUnlockIsNotPossible();
+
+ onFaceLockoutError("lockout error");
+ verifyNoMoreInteractions(mRotateTextViewController);
+
+ mScreenObserver.onScreenTurnedOn();
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ "lockout error");
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
+ @Test
+ public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
+ createController();
+ screenIsTurningOn();
+ fingerprintUnlockIsPossible();
+
+ onFaceLockoutError("lockout error");
+ verifyNoMoreInteractions(mRotateTextViewController);
+
+ mScreenObserver.onScreenTurnedOn();
+
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ "lockout error");
+ verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_suggest_fingerprint));
+ }
+
+ private void screenIsTurningOn() {
+ when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_TURNING_ON);
+ }
+
private void sendUpdateDisclosureBroadcast() {
mBroadcastReceiver.onReceive(mContext, new Intent());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 7e2e6f619946..bdedd244abfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -13,57 +13,55 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.statusbar.notification.collection.coordinator
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
-import java.util.function.Consumer
-import org.junit.Before
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
class KeyguardCoordinatorTest : SysuiTestCase() {
- private val notifPipeline: NotifPipeline = mock()
+
private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val notifPipelineFlags: NotifPipelineFlags = mock()
+ private val notifPipeline: NotifPipeline = mock()
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
private val statusBarStateController: StatusBarStateController = mock()
- private lateinit var onStateChangeListener: Consumer<String>
- private lateinit var keyguardFilter: NotifFilter
-
- @Before
- fun setup() {
- val keyguardCoordinator = KeyguardCoordinator(
- keyguardNotifVisibilityProvider,
- sectionHeaderVisibilityProvider,
- statusBarStateController
- )
- keyguardCoordinator.attach(notifPipeline)
- onStateChangeListener = withArgCaptor {
- verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
- }
- keyguardFilter = withArgCaptor {
- verify(notifPipeline).addFinalizeFilter(capture())
- }
- }
-
@Test
- fun testSetSectionHeadersVisibleInShade() {
+ fun testSetSectionHeadersVisibleInShade() = runKeyguardCoordinatorTest {
clearInvocations(sectionHeaderVisibilityProvider)
whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
onStateChangeListener.accept("state change")
@@ -71,10 +69,176 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
}
@Test
- fun testSetSectionHeadersNotVisibleOnKeyguard() {
+ fun testSetSectionHeadersNotVisibleOnKeyguard() = runKeyguardCoordinatorTest {
clearInvocations(sectionHeaderVisibilityProvider)
whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
onStateChangeListener.accept("state change")
verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false)
}
+
+ @Test
+ fun unseenFilterSuppressesSeenNotifWhileKeyguardShowing() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is not showing, and a notification is present
+ keyguardRepository.setKeyguardShowing(false)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: The keyguard is now showing
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+
+ // WHEN: The keyguard goes away
+ keyguardRepository.setKeyguardShowing(false)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is shown regardless
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenFilterAllowsNewNotif() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is showing, no notifications present
+ keyguardRepository.setKeyguardShowing(true)
+ runKeyguardCoordinatorTest {
+ // WHEN: A new notification is posted
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // THEN: The notification is recognized as "unseen" and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenFilterSeenGroupSummaryWithUnseenChild() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is not showing, and a notification is present
+ keyguardRepository.setKeyguardShowing(false)
+ runKeyguardCoordinatorTest {
+ // WHEN: A new notification is posted
+ val fakeSummary = NotificationEntryBuilder().build()
+ val fakeChild = NotificationEntryBuilder()
+ .setGroup(context, "group")
+ .setGroupSummary(context, false)
+ .build()
+ GroupEntryBuilder()
+ .setSummary(fakeSummary)
+ .addChild(fakeChild)
+ .build()
+
+ collectionListener.onEntryAdded(fakeSummary)
+ collectionListener.onEntryAdded(fakeChild)
+
+ // WHEN: Keyguard is now showing, both notifications are marked as seen
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // WHEN: The child notification is now unseen
+ collectionListener.onEntryUpdated(fakeChild)
+
+ // THEN: The summary is not filtered out, because the child is unseen
+ assertThat(unseenFilter.shouldFilterOut(fakeSummary, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationIsMarkedAsSeenWhenKeyguardGoesAway() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is showing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: Keyguard is no longer showing for 5 seconds
+ keyguardRepository.setKeyguardShowing(false)
+ testScheduler.runCurrent()
+ testScheduler.advanceTimeBy(5.seconds.inWholeMilliseconds)
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is now recognized as "seen" and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isTrue()
+ }
+ }
+
+ @Test
+ fun unseenNotificationIsNotMarkedAsSeenIfTimeThresholdNotMet() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is showing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: Keyguard is no longer showing for <5 seconds
+ keyguardRepository.setKeyguardShowing(false)
+ testScheduler.runCurrent()
+ testScheduler.advanceTimeBy(1.seconds.inWholeMilliseconds)
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is not recognized as "seen" and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ private fun runKeyguardCoordinatorTest(
+ testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
+ ) {
+ val testScope = TestScope(UnconfinedTestDispatcher())
+ val keyguardCoordinator =
+ KeyguardCoordinator(
+ keyguardNotifVisibilityProvider,
+ keyguardRepository,
+ notifPipelineFlags,
+ testScope.backgroundScope,
+ sectionHeaderVisibilityProvider,
+ statusBarStateController,
+ )
+ keyguardCoordinator.attach(notifPipeline)
+ KeyguardCoordinatorTestScope(keyguardCoordinator, testScope).run {
+ testScheduler.advanceUntilIdle()
+ testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) { testBlock() }
+ }
+ }
+
+ private inner class KeyguardCoordinatorTestScope(
+ private val keyguardCoordinator: KeyguardCoordinator,
+ private val scope: TestScope,
+ ) : CoroutineScope by scope {
+ val testScheduler: TestCoroutineScheduler
+ get() = scope.testScheduler
+
+ val onStateChangeListener: Consumer<String> =
+ withArgCaptor {
+ verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+ }
+
+ val unseenFilter: NotifFilter
+ get() = keyguardCoordinator.unseenNotifFilter
+
+ // TODO(254647461): Remove lazy once Flags.FILTER_UNSEEN_NOTIFS_ON_KEYGUARD is enabled and
+ // removed
+ val collectionListener: NotifCollectionListener by lazy {
+ withArgCaptor { verify(notifPipeline).addCollectionListener(capture()) }
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index b4a5f5ce205f..e488f39bf27d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -38,6 +40,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeStateEvents;
import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener;
+import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -73,6 +76,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
@Mock private HeadsUpManager mHeadsUpManager;
@Mock private ShadeStateEvents mShadeStateEvents;
+ @Mock private VisibilityLocationProvider mVisibilityLocationProvider;
@Mock private VisualStabilityProvider mVisualStabilityProvider;
@Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
@@ -100,6 +104,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
mHeadsUpManager,
mShadeStateEvents,
mStatusBarStateController,
+ mVisibilityLocationProvider,
mVisualStabilityProvider,
mWakefulnessLifecycle);
@@ -355,6 +360,38 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
}
@Test
+ public void testMovingVisibleHeadsUpNotAllowed() {
+ // GIVEN stability enforcing conditions
+ setPanelExpanded(true);
+ setSleepy(false);
+
+ // WHEN a notification is alerting and visible
+ when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
+ .thenReturn(true);
+
+ // VERIFY the notification cannot be reordered
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isFalse();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isFalse();
+ }
+
+ @Test
+ public void testMovingInvisibleHeadsUpAllowed() {
+ // GIVEN stability enforcing conditions
+ setPanelExpanded(true);
+ setSleepy(false);
+
+ // WHEN a notification is alerting but not visible
+ when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
+ .thenReturn(false);
+
+ // VERIFY the notification can be reordered
+ assertThat(mNotifStabilityManager.isEntryReorderingAllowed(mEntry)).isTrue();
+ assertThat(mNotifStabilityManager.isSectionChangeAllowed(mEntry)).isTrue();
+ }
+
+ @Test
public void testNeverSuppressedChanges_noInvalidationCalled() {
// GIVEN no notifications are currently being suppressed from grouping nor being sorted
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 90061b078afe..026c82eda42d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -61,6 +61,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
@@ -122,6 +123,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private UiEventLogger mUiEventLogger;
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
+ @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
@Mock private ShadeController mShadeController;
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private StackStateLogger mStackLogger;
@@ -173,6 +175,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mShadeTransitionController,
mUiEventLogger,
mRemoteInputManager,
+ mVisibilityLocationProviderDelegator,
mShadeController,
mJankMonitor,
mStackLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index fee3ccb21792..038af8ff5396 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -14,23 +14,37 @@
package com.android.systemui.statusbar.phone
-import androidx.test.filters.SmallTest
+import android.content.res.Configuration
+import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_LTR
+import android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
+import android.content.res.Configuration.UI_MODE_NIGHT_NO
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.content.res.Configuration.UI_MODE_TYPE_CAR
+import android.os.LocaleList
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import java.util.Locale
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ConfigurationControllerImplTest : SysuiTestCase() {
- private val mConfigurationController =
- com.android.systemui.statusbar.phone.ConfigurationControllerImpl(mContext)
+ private lateinit var mConfigurationController: ConfigurationControllerImpl
+
+ @Before
+ fun setUp() {
+ mConfigurationController = ConfigurationControllerImpl(mContext)
+ }
@Test
fun testThemeChange() {
@@ -57,4 +71,303 @@ class ConfigurationControllerImplTest : SysuiTestCase() {
verify(listener).onThemeChanged()
verify(listener2, never()).onThemeChanged()
}
+
+ @Test
+ fun configChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.densityDpi = 12
+ config.smallestScreenWidthDp = 240
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the config is updated
+ config.densityDpi = 20
+ config.smallestScreenWidthDp = 300
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.changedConfig?.densityDpi).isEqualTo(20)
+ assertThat(listener.changedConfig?.smallestScreenWidthDp).isEqualTo(300)
+ }
+
+ @Test
+ fun densityChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.densityDpi = 12
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the density is updated
+ config.densityDpi = 20
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ }
+
+ @Test
+ fun fontChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.fontScale = 1.5f
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the font is updated
+ config.fontScale = 1.4f
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ }
+
+ @Test
+ fun isCarAndUiModeChanged_densityListenerNotified() {
+ val config = mContext.resources.configuration
+ config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_YES
+ // Re-create the controller since we calculate car mode on creation
+ mConfigurationController = ConfigurationControllerImpl(mContext)
+
+ val listener = createAndAddListener()
+
+ // WHEN the ui mode is updated
+ config.uiMode = UI_MODE_TYPE_CAR or UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ }
+
+ @Test
+ fun isNotCarAndUiModeChanged_densityListenerNotNotified() {
+ val config = mContext.resources.configuration
+ config.uiMode = UI_MODE_NIGHT_YES
+ // Re-create the controller since we calculate car mode on creation
+ mConfigurationController = ConfigurationControllerImpl(mContext)
+
+ val listener = createAndAddListener()
+
+ // WHEN the ui mode is updated
+ config.uiMode = UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is not notified because it's not car mode
+ assertThat(listener.densityOrFontScaleChanged).isFalse()
+ }
+
+ @Test
+ fun smallestScreenWidthChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.smallestScreenWidthDp = 240
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the width is updated
+ config.smallestScreenWidthDp = 300
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.smallestScreenWidthChanged).isTrue()
+ }
+
+ @Test
+ fun maxBoundsChange_newConfigObject_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN a new configuration object with new bounds is sent
+ val newConfig = Configuration()
+ newConfig.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ mConfigurationController.onConfigurationChanged(newConfig)
+
+ // THEN the listener is notified
+ assertThat(listener.maxBoundsChanged).isTrue()
+ }
+
+ // Regression test for b/245799099
+ @Test
+ fun maxBoundsChange_sameObject_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.windowConfiguration.setMaxBounds(0, 0, 200, 200)
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the existing config is updated with new bounds
+ config.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.maxBoundsChanged).isTrue()
+ }
+
+
+ @Test
+ fun localeListChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.locales = LocaleList(Locale.CANADA, Locale.GERMANY)
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the locales are updated
+ config.locales = LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE)
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.localeListChanged).isTrue()
+ }
+
+ @Test
+ fun uiModeChanged_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.uiMode = UI_MODE_NIGHT_YES
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the ui mode is updated
+ config.uiMode = UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.uiModeChanged).isTrue()
+ }
+
+ @Test
+ fun layoutDirectionUpdated_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.screenLayout = SCREENLAYOUT_LAYOUTDIR_LTR
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the layout is updated
+ config.screenLayout = SCREENLAYOUT_LAYOUTDIR_RTL
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.layoutDirectionChanged).isTrue()
+ }
+
+ @Test
+ fun assetPathsUpdated_listenerNotified() {
+ val config = mContext.resources.configuration
+ config.assetsSeq = 45
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN the assets sequence is updated
+ config.assetsSeq = 46
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified
+ assertThat(listener.themeChanged).isTrue()
+ }
+
+ @Test
+ fun multipleUpdates_listenerNotifiedOfAll() {
+ val config = mContext.resources.configuration
+ config.densityDpi = 14
+ config.windowConfiguration.setMaxBounds(0, 0, 2, 2)
+ config.uiMode = UI_MODE_NIGHT_YES
+ mConfigurationController.onConfigurationChanged(config)
+
+ val listener = createAndAddListener()
+
+ // WHEN multiple fields are updated
+ config.densityDpi = 20
+ config.windowConfiguration.setMaxBounds(0, 0, 3, 3)
+ config.uiMode = UI_MODE_NIGHT_NO
+ mConfigurationController.onConfigurationChanged(config)
+
+ // THEN the listener is notified of all of them
+ assertThat(listener.densityOrFontScaleChanged).isTrue()
+ assertThat(listener.maxBoundsChanged).isTrue()
+ assertThat(listener.uiModeChanged).isTrue()
+ }
+
+ @Test
+ fun equivalentConfigObject_listenerNotNotified() {
+ val config = mContext.resources.configuration
+ val listener = createAndAddListener()
+
+ // WHEN we update with the new object that has all the same fields
+ mConfigurationController.onConfigurationChanged(Configuration(config))
+
+ listener.assertNoMethodsCalled()
+ }
+
+ private fun createAndAddListener(): TestListener {
+ val listener = TestListener()
+ mConfigurationController.addCallback(listener)
+ // Adding a listener can trigger some callbacks, so we want to reset the values right
+ // after the listener is added
+ listener.reset()
+ return listener
+ }
+
+ private class TestListener : ConfigurationListener {
+ var changedConfig: Configuration? = null
+ var densityOrFontScaleChanged = false
+ var smallestScreenWidthChanged = false
+ var maxBoundsChanged = false
+ var uiModeChanged = false
+ var themeChanged = false
+ var localeListChanged = false
+ var layoutDirectionChanged = false
+
+ override fun onConfigChanged(newConfig: Configuration?) {
+ changedConfig = newConfig
+ }
+ override fun onDensityOrFontScaleChanged() {
+ densityOrFontScaleChanged = true
+ }
+ override fun onSmallestScreenWidthChanged() {
+ smallestScreenWidthChanged = true
+ }
+ override fun onMaxBoundsChanged() {
+ maxBoundsChanged = true
+ }
+ override fun onUiModeChanged() {
+ uiModeChanged = true
+ }
+ override fun onThemeChanged() {
+ themeChanged = true
+ }
+ override fun onLocaleListChanged() {
+ localeListChanged = true
+ }
+ override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
+ layoutDirectionChanged = true
+ }
+
+ fun assertNoMethodsCalled() {
+ assertThat(densityOrFontScaleChanged).isFalse()
+ assertThat(smallestScreenWidthChanged).isFalse()
+ assertThat(maxBoundsChanged).isFalse()
+ assertThat(uiModeChanged).isFalse()
+ assertThat(themeChanged).isFalse()
+ assertThat(localeListChanged).isFalse()
+ assertThat(layoutDirectionChanged).isFalse()
+ }
+
+ fun reset() {
+ changedConfig = null
+ densityOrFontScaleChanged = false
+ smallestScreenWidthChanged = false
+ maxBoundsChanged = false
+ uiModeChanged = false
+ themeChanged = false
+ localeListChanged = false
+ layoutDirectionChanged = false
+ }
+ }
}
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 6ec5cf82a81e..eb0b9b3a3fb1 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
@@ -56,14 +56,12 @@ import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherFeatureController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -112,16 +110,12 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
@Mock
private UserManager mUserManager;
+ @Mock
+ private StatusBarUserChipViewModel mStatusBarUserChipViewModel;
@Captor
private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
- @Mock
- private StatusBarUserSwitcherFeatureController mStatusBarUserSwitcherFeatureController;
- @Mock
- private StatusBarUserSwitcherController mStatusBarUserSwitcherController;
- @Mock
- private StatusBarUserInfoTracker mStatusBarUserInfoTracker;
@Mock private SecureSettings mSecureSettings;
@Mock private CommandQueue mCommandQueue;
@Mock private KeyguardLogger mLogger;
@@ -169,9 +163,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
mStatusBarStateController,
mStatusBarContentInsetsProvider,
mUserManager,
- mStatusBarUserSwitcherFeatureController,
- mStatusBarUserSwitcherController,
- mStatusBarUserInfoTracker,
+ mStatusBarUserChipViewModel,
mSecureSettings,
mCommandQueue,
mFakeExecutor,
@@ -479,8 +471,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
@Test
public void testNewUserSwitcherDisablesAvatar_newUiOn() {
// GIVEN the status bar user switcher chip is enabled
- when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled())
- .thenReturn(true);
+ when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(true);
// WHEN the controller is created
mController = createController();
@@ -492,8 +483,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
@Test
public void testNewUserSwitcherDisablesAvatar_newUiOff() {
// GIVEN the status bar user switcher chip is disabled
- when(mStatusBarUserSwitcherFeatureController.isStatusBarUserSwitcherFeatureEnabled())
- .thenReturn(false);
+ when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(false);
// WHEN the controller is created
mController = createController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index a61fba5c4000..320a08315843 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -27,11 +27,11 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.shade.NotificationPanelViewController
-import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
import com.android.systemui.util.mockito.any
import com.android.systemui.util.view.ViewUtil
import com.google.common.truth.Truth.assertThat
@@ -64,7 +64,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var configurationController: ConfigurationController
@Mock
- private lateinit var userSwitcherController: StatusBarUserSwitcherController
+ private lateinit var userChipViewModel: StatusBarUserChipViewModel
@Mock
private lateinit var viewUtil: ViewUtil
@@ -79,14 +79,13 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
`when`(notificationPanelViewController.view).thenReturn(panelView)
`when`(sysuiUnfoldComponent.getStatusBarMoveFromCenterAnimationController())
.thenReturn(moveFromCenterAnimation)
- // create the view on main thread as it requires main looper
+ // create the view and controller on main thread as it requires main looper
InstrumentationRegistry.getInstrumentation().runOnMainSync {
val parent = FrameLayout(mContext) // add parent to keep layout params
view = LayoutInflater.from(mContext)
.inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
+ controller = createAndInitController(view)
}
-
- controller = createAndInitController(view)
}
@Test
@@ -106,7 +105,10 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
val view = createViewMock()
val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
unfoldConfig.isEnabled = true
- controller = createAndInitController(view)
+ // create the controller on main thread as it requires main looper
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ controller = createAndInitController(view)
+ }
verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
argumentCaptor.value.onPreDraw()
@@ -126,7 +128,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
return PhoneStatusBarViewController.Factory(
Optional.of(sysuiUnfoldComponent),
Optional.of(progressProvider),
- userSwitcherController,
+ userChipViewModel,
viewUtil,
configurationController
).create(view, touchEventHandler).also {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index e86676b81f8e..1759fb794bd6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -19,9 +19,9 @@ package com.android.systemui.statusbar.phone
import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
-import android.test.suitebuilder.annotation.SmallTest
import android.view.Display
import android.view.DisplayCutout
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -463,16 +463,10 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
mock(DumpManager::class.java))
- givenDisplay(
- screenBounds = Rect(0, 0, 1080, 2160),
- displayUniqueId = "1"
- )
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
- givenDisplay(
- screenBounds = Rect(0, 0, 800, 600),
- displayUniqueId = "2"
- )
- configurationController.onConfigurationChanged(configuration)
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
// WHEN: get insets on the second display
val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -487,23 +481,15 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
// get insets and switch back
val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
mock(DumpManager::class.java))
- givenDisplay(
- screenBounds = Rect(0, 0, 1080, 2160),
- displayUniqueId = "1"
- )
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
val firstDisplayInsetsFirstCall = provider
.getStatusBarContentAreaForRotation(ROTATION_NONE)
- givenDisplay(
- screenBounds = Rect(0, 0, 800, 600),
- displayUniqueId = "2"
- )
- configurationController.onConfigurationChanged(configuration)
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
- givenDisplay(
- screenBounds = Rect(0, 0, 1080, 2160),
- displayUniqueId = "1"
- )
- configurationController.onConfigurationChanged(configuration)
+
+ configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
// WHEN: get insets on the first display again
val firstDisplayInsetsSecondCall = provider
@@ -513,9 +499,70 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() {
assertThat(firstDisplayInsetsFirstCall).isEqualTo(firstDisplayInsetsSecondCall)
}
- private fun givenDisplay(screenBounds: Rect, displayUniqueId: String) {
- `when`(display.uniqueId).thenReturn(displayUniqueId)
- configuration.windowConfiguration.maxBounds = screenBounds
+ // Regression test for b/245799099
+ @Test
+ fun onMaxBoundsChanged_listenerNotified() {
+ // Start out with an existing configuration with bounds
+ configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100)
+ configurationController.onConfigurationChanged(configuration)
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ val listener = object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ // WHEN the config is updated with new bounds
+ configuration.windowConfiguration.setMaxBounds(0, 0, 456, 789)
+ configurationController.onConfigurationChanged(configuration)
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
+ }
+
+ @Test
+ fun onDensityOrFontScaleChanged_listenerNotified() {
+ configuration.densityDpi = 12
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ val listener = object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ // WHEN the config is updated
+ configuration.densityDpi = 20
+ configurationController.onConfigurationChanged(configuration)
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
+ }
+
+ @Test
+ fun onThemeChanged_listenerNotified() {
+ val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
+ mock(DumpManager::class.java))
+ val listener = object : StatusBarContentInsetsChangedListener {
+ var triggered = false
+
+ override fun onStatusBarContentInsetsChanged() {
+ triggered = true
+ }
+ }
+ provider.addCallback(listener)
+
+ configurationController.notifyThemeChanged()
+
+ // THEN the listener is notified
+ assertThat(listener.triggered).isTrue()
}
private fun assertRects(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
deleted file mode 100644
index eba3b04f3472..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherControllerOldImplTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.userswitcher
-
-import android.content.Intent
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.user.UserSwitchDialogController
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
-@SmallTest
-class StatusBarUserSwitcherControllerOldImplTest : SysuiTestCase() {
- @Mock
- private lateinit var tracker: StatusBarUserInfoTracker
-
- @Mock
- private lateinit var featureController: StatusBarUserSwitcherFeatureController
-
- @Mock
- private lateinit var userSwitcherDialogController: UserSwitchDialogController
-
- @Mock
- private lateinit var featureFlags: FeatureFlags
-
- @Mock
- private lateinit var activityStarter: ActivityStarter
-
- @Mock
- private lateinit var falsingManager: FalsingManager
-
- private lateinit var statusBarUserSwitcherContainer: StatusBarUserSwitcherContainer
- private lateinit var controller: StatusBarUserSwitcherControllerImpl
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- statusBarUserSwitcherContainer = StatusBarUserSwitcherContainer(mContext, null)
- statusBarUserSwitcherContainer
- controller = StatusBarUserSwitcherControllerImpl(
- statusBarUserSwitcherContainer,
- tracker,
- featureController,
- userSwitcherDialogController,
- featureFlags,
- activityStarter,
- falsingManager
- )
- controller.init()
- controller.onViewAttached()
- }
-
- @Test
- fun testFalsingManager() {
- statusBarUserSwitcherContainer.callOnClick()
- verify(falsingManager).isFalseTap(FalsingManager.LOW_PENALTY)
- }
-
- @Test
- fun testStartActivity() {
- `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(false)
- statusBarUserSwitcherContainer.callOnClick()
- verify(userSwitcherDialogController).showDialog(any(), any())
- `when`(featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)).thenReturn(true)
- statusBarUserSwitcherContainer.callOnClick()
- verify(activityStarter).startActivity(any(Intent::class.java),
- eq(true) /* dismissShade */,
- eq(null) /* animationController */,
- eq(true) /* showOverLockscreenWhenLocked */,
- eq(UserHandle.SYSTEM))
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 8fb98c12d6ff..4b49420c99be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.user.domain.interactor
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
+import android.content.ComponentName
import android.content.Intent
import android.content.pm.UserInfo
import android.graphics.Bitmap
@@ -33,7 +34,10 @@ import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
@@ -41,6 +45,7 @@ import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.UserSwitcherActivity
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -48,9 +53,11 @@ import com.android.systemui.user.domain.model.ShowDialogRequestModel
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
@@ -90,6 +97,7 @@ class UserInteractorTest : SysuiTestCase() {
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var telephonyRepository: FakeTelephonyRepository
+ private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setUp() {
@@ -104,6 +112,7 @@ class UserInteractorTest : SysuiTestCase() {
SUPERVISED_USER_CREATION_APP_PACKAGE,
)
+ featureFlags = FakeFeatureFlags()
userRepository = FakeUserRepository()
keyguardRepository = FakeKeyguardRepository()
telephonyRepository = FakeTelephonyRepository()
@@ -147,7 +156,8 @@ class UserInteractorTest : SysuiTestCase() {
uiEventLogger = uiEventLogger,
resumeSessionReceiver = resumeSessionReceiver,
resetOrExitSessionReceiver = resetOrExitSessionReceiver,
- )
+ ),
+ featureFlags = featureFlags,
)
}
@@ -672,6 +682,95 @@ class UserInteractorTest : SysuiTestCase() {
)
}
+ @Test
+ fun `users - secondary user - no guest user`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserModel>? = null
+ val job = underTest.users.onEach { res = it }.launchIn(this)
+ assertThat(res?.size == 2).isTrue()
+ assertThat(res?.find { it.isGuest }).isNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `users - secondary user - no guest action`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserActionModel>? = null
+ val job = underTest.actions.onEach { res = it }.launchIn(this)
+ assertThat(res?.find { it == UserActionModel.ENTER_GUEST_MODE }).isNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `users - secondary user - no guest user record`() =
+ runBlocking(IMMEDIATE) {
+ val userInfos = createUserInfos(count = 3, includeGuest = true)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[1])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ var res: List<UserRecord>? = null
+ val job = underTest.userRecords.onEach { res = it }.launchIn(this)
+ assertThat(res?.find { it.isGuest }).isNull()
+ job.cancel()
+ }
+
+ @Test
+ fun `show user switcher - full screen disabled - shows dialog switcher`() =
+ runBlocking(IMMEDIATE) {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+
+ var dialogRequest: ShowDialogRequestModel? = null
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(context, expandable)
+
+ val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
+
+ // Dialog is shown.
+ assertThat(dialogRequest).isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog)
+
+ underTest.onDialogShown()
+ assertThat(dialogRequest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `show user switcher - full screen enabled - launches activity`() {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(context, expandable)
+
+ // Dialog is shown.
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(activityStarter)
+ .startActivity(
+ intentCaptor.capture(),
+ /* dismissShade= */ eq(true),
+ /* ActivityLaunchAnimator.Controller= */ nullable(),
+ /* showOverLockscreenWhenLocked= */ eq(true),
+ eq(UserHandle.SYSTEM),
+ )
+ assertThat(intentCaptor.value.component)
+ .isEqualTo(
+ ComponentName(
+ context,
+ UserSwitcherActivity::class.java,
+ )
+ )
+ }
+
private fun assertUsers(
models: List<UserModel>?,
count: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
new file mode 100644
index 000000000000..db348b8029a0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.user.ui.viewmodel
+
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.model.UserSwitcherSettingsModel
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.yield
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class StatusBarUserChipViewModelTest : SysuiTestCase() {
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var activityManager: ActivityManager
+ @Mock private lateinit var manager: UserManager
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+ @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
+
+ private lateinit var underTest: StatusBarUserChipViewModel
+
+ private val userRepository = FakeUserRepository()
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val featureFlags = FakeFeatureFlags()
+ private lateinit var guestUserInteractor: GuestUserInteractor
+ private lateinit var refreshUsersScheduler: RefreshUsersScheduler
+
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ doAnswer { invocation ->
+ val userId = invocation.arguments[0] as Int
+ when (userId) {
+ USER_ID_0 -> return@doAnswer USER_IMAGE_0
+ USER_ID_1 -> return@doAnswer USER_IMAGE_1
+ USER_ID_2 -> return@doAnswer USER_IMAGE_2
+ else -> return@doAnswer mock<Bitmap>()
+ }
+ }
+ .`when`(manager)
+ .getUserIcon(anyInt())
+
+ userRepository.isStatusBarUserChipEnabled = true
+
+ refreshUsersScheduler =
+ RefreshUsersScheduler(
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ repository = userRepository,
+ )
+ guestUserInteractor =
+ GuestUserInteractor(
+ applicationContext = context,
+ applicationScope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ backgroundDispatcher = testDispatcher,
+ manager = manager,
+ repository = userRepository,
+ deviceProvisionedController = deviceProvisionedController,
+ devicePolicyManager = devicePolicyManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ uiEventLogger = uiEventLogger,
+ resumeSessionReceiver = resumeSessionReceiver,
+ resetOrExitSessionReceiver = resetOrExitSessionReceiver,
+ )
+
+ underTest = viewModel()
+ }
+
+ @Test
+ fun `config is false - chip is disabled`() {
+ // the enabled bit is set at SystemUI startup, so recreate the view model here
+ userRepository.isStatusBarUserChipEnabled = false
+ underTest = viewModel()
+
+ assertThat(underTest.chipEnabled).isFalse()
+ }
+
+ @Test
+ fun `config is true - chip is enabled`() {
+ // the enabled bit is set at SystemUI startup, so recreate the view model here
+ userRepository.isStatusBarUserChipEnabled = true
+ underTest = viewModel()
+
+ assertThat(underTest.chipEnabled).isTrue()
+ }
+
+ @Test
+ fun `should show chip criteria - single user`() =
+ testScope.runTest {
+ userRepository.setUserInfos(listOf(USER_0))
+ userRepository.setSelectedUserInfo(USER_0)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+ val values = mutableListOf<Boolean>()
+
+ val job = launch { underTest.isChipVisible.toList(values) }
+ advanceUntilIdle()
+
+ assertThat(values).containsExactly(false)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `should show chip criteria - multiple users`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ var latest: Boolean? = null
+ val job = underTest.isChipVisible.onEach { latest = it }.launchIn(this)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun `user chip name - shows selected user info`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ var latest: Text? = null
+ val job = underTest.userName.onEach { latest = it }.launchIn(this)
+
+ userRepository.setSelectedUserInfo(USER_0)
+ assertThat(latest).isEqualTo(USER_NAME_0)
+
+ userRepository.setSelectedUserInfo(USER_1)
+ assertThat(latest).isEqualTo(USER_NAME_1)
+
+ userRepository.setSelectedUserInfo(USER_2)
+ assertThat(latest).isEqualTo(USER_NAME_2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun `user chip avatar - shows selected user info`() =
+ testScope.runTest {
+ setMultipleUsers()
+
+ // A little hacky. System server passes us bitmaps and we wrap them in the interactor.
+ // Unwrap them to make sure we're always tracking the current user's bitmap
+ var latest: Bitmap? = null
+ val job =
+ underTest.userAvatar
+ .onEach {
+ if (it !is BitmapDrawable) {
+ latest = null
+ }
+
+ latest = (it as BitmapDrawable).bitmap
+ }
+ .launchIn(this)
+
+ userRepository.setSelectedUserInfo(USER_0)
+ assertThat(latest).isEqualTo(USER_IMAGE_0)
+
+ userRepository.setSelectedUserInfo(USER_1)
+ assertThat(latest).isEqualTo(USER_IMAGE_1)
+
+ userRepository.setSelectedUserInfo(USER_2)
+ assertThat(latest).isEqualTo(USER_IMAGE_2)
+
+ job.cancel()
+ }
+
+ private fun viewModel(): StatusBarUserChipViewModel {
+ return StatusBarUserChipViewModel(
+ context = context,
+ interactor =
+ UserInteractor(
+ applicationContext = context,
+ repository = userRepository,
+ activityStarter = activityStarter,
+ keyguardInteractor =
+ KeyguardInteractor(
+ repository = keyguardRepository,
+ ),
+ featureFlags = featureFlags,
+ manager = manager,
+ applicationScope = testScope.backgroundScope,
+ telephonyInteractor =
+ TelephonyInteractor(
+ repository = FakeTelephonyRepository(),
+ ),
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ backgroundDispatcher = testDispatcher,
+ activityManager = activityManager,
+ refreshUsersScheduler = refreshUsersScheduler,
+ guestUserInteractor = guestUserInteractor,
+ )
+ )
+ }
+
+ private suspend fun setMultipleUsers() {
+ userRepository.setUserInfos(listOf(USER_0, USER_1, USER_2))
+ userRepository.setSelectedUserInfo(USER_0)
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+ }
+
+ companion object {
+ private const val USER_ID_0 = 0
+ private val USER_IMAGE_0 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_0 = Text.Loaded("zero")
+
+ private const val USER_ID_1 = 1
+ private val USER_IMAGE_1 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_1 = Text.Loaded("one")
+
+ private const val USER_ID_2 = 2
+ private val USER_IMAGE_2 = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ private val USER_NAME_2 = Text.Loaded("two")
+
+ private val USER_0 =
+ UserInfo(
+ USER_ID_0,
+ USER_NAME_0.text!!,
+ /* iconPath */ "",
+ /* flags */ 0,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+
+ private val USER_1 =
+ UserInfo(
+ USER_ID_1,
+ USER_NAME_1.text!!,
+ /* iconPath */ "",
+ /* flags */ 0,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+
+ private val USER_2 =
+ UserInfo(
+ USER_ID_2,
+ USER_NAME_2.text!!,
+ /* iconPath */ "",
+ /* flags */ 0,
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index db136800a3cc..eac7fc21e505 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -27,6 +27,7 @@ import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
@@ -147,6 +148,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
KeyguardInteractor(
repository = keyguardRepository,
),
+ featureFlags = FakeFeatureFlags(),
manager = manager,
applicationScope = injectedScope,
telephonyInteractor =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 325da4ead666..63448e236867 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -28,8 +28,6 @@ import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.globalactions.GlobalActionsDialogLite
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -43,7 +41,6 @@ import com.android.systemui.qs.footer.data.repository.UserSwitcherRepositoryImpl
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.security.data.repository.SecurityRepository
import com.android.systemui.security.data.repository.SecurityRepositoryImpl
import com.android.systemui.settings.FakeUserTracker
@@ -54,6 +51,7 @@ import com.android.systemui.statusbar.policy.FakeUserInfoController
import com.android.systemui.statusbar.policy.SecurityController
import com.android.systemui.statusbar.policy.UserInfoController
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.GlobalSettings
@@ -97,13 +95,12 @@ class FooterActionsTestUtils(
/** Create a [FooterActionsInteractor] to be used in tests. */
fun footerActionsInteractor(
activityStarter: ActivityStarter = mock(),
- featureFlags: FeatureFlags = FakeFeatureFlags(),
metricsLogger: MetricsLogger = FakeMetricsLogger(),
uiEventLogger: UiEventLogger = UiEventLoggerFake(),
deviceProvisionedController: DeviceProvisionedController = mock(),
qsSecurityFooterUtils: QSSecurityFooterUtils = mock(),
fgsManagerController: FgsManagerController = mock(),
- userSwitchDialogController: UserSwitchDialogController = mock(),
+ userInteractor: UserInteractor = mock(),
securityRepository: SecurityRepository = securityRepository(),
foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
@@ -112,13 +109,12 @@ class FooterActionsTestUtils(
): FooterActionsInteractor {
return FooterActionsInteractorImpl(
activityStarter,
- featureFlags,
metricsLogger,
uiEventLogger,
deviceProvisionedController,
qsSecurityFooterUtils,
fgsManagerController,
- userSwitchDialogController,
+ userInteractor,
securityRepository,
foregroundServicesRepository,
userSwitcherRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index b7c8cbf40bea..ea5a302ce05a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -49,6 +49,8 @@ class FakeUserRepository : UserRepository {
override val isGuestUserCreationScheduled = AtomicBoolean()
+ override var isStatusBarUserChipEnabled: Boolean = false
+
override var secondaryUserId: Int = UserHandle.USER_NULL
override var isRefreshUsersPaused: Boolean = false
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
index 33ece0084906..21e16a1e7be4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
@@ -16,7 +16,6 @@
package com.android.systemui.util;
-import android.content.Context;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.OnPropertiesChangedListener;
import android.provider.DeviceConfig.Properties;
@@ -83,7 +82,7 @@ public class DeviceConfigProxyFake extends DeviceConfigProxy {
}
@Override
- public void enforceReadPermission(Context context, String namespace) {
+ public void enforceReadPermission(String namespace) {
// no-op
}
diff --git a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml
index 9db960fe68c1..e7ec332a6f7a 100644
--- a/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml
+++ b/packages/overlays/DisplayCutoutEmulationHoleOverlay/res/values-en-rCA/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="display_cutout_emulation_overlay" msgid="7305489596221077240">"Punch hole cutout"</string>
+ <string name="display_cutout_emulation_overlay" msgid="7305489596221077240">"Punch Hole cutout"</string>
</resources>
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
index 3ea1bcbbbcd9..7d8bb51ff586 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetXmlUtil.java
@@ -114,7 +114,7 @@ public class AppWidgetXmlUtil {
info.minWidth = parser.getAttributeInt(null, ATTR_MIN_WIDTH, 0);
info.minHeight = parser.getAttributeInt(null, ATTR_MIN_HEIGHT, 0);
info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_WIDTH, 0);
- info.minResizeWidth = parser.getAttributeInt(null, ATTR_MIN_RESIZE_HEIGHT, 0);
+ info.minResizeHeight = parser.getAttributeInt(null, ATTR_MIN_RESIZE_HEIGHT, 0);
info.maxResizeWidth = parser.getAttributeInt(null, ATTR_MAX_RESIZE_WIDTH, 0);
info.maxResizeHeight = parser.getAttributeInt(null, ATTR_MAX_RESIZE_HEIGHT, 0);
info.targetCellWidth = parser.getAttributeInt(null, ATTR_TARGET_CELL_WIDTH, 0);
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index be2107529f8b..fbde9e0ea5d1 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -227,6 +227,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return mParams.getName();
}
+ /** Returns the policy specified for this policy type */
+ public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
+ @VirtualDeviceParams.PolicyType int policyType) {
+ return mParams.getDevicePolicy(policyType);
+ }
+
/** Returns the unique device ID of this device. */
@Override // Binder call
public int getDeviceId() {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index c400a74da4ce..a8797a05ed24 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -233,6 +233,13 @@ public class VirtualDeviceManagerService extends SystemService {
mLocalService.onAppsOnVirtualDeviceChanged();
}
+ @VisibleForTesting
+ void addVirtualDevice(VirtualDeviceImpl virtualDevice) {
+ synchronized (mVirtualDeviceManagerLock) {
+ mVirtualDevices.put(virtualDevice.getAssociationId(), virtualDevice);
+ }
+ }
+
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements
VirtualDeviceImpl.PendingTrampolineCallback {
@@ -358,6 +365,12 @@ public class VirtualDeviceManagerService extends SystemService {
return virtualDevices;
}
+ @Override // BinderCall
+ @VirtualDeviceParams.DevicePolicy
+ public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
+ return mLocalService.getDevicePolicy(deviceId, policyType);
+ }
+
@Nullable
private AssociationInfo getAssociationInfo(String packageName, int associationId) {
final int callingUserId = getCallingUserHandle().getIdentifier();
@@ -439,6 +452,20 @@ public class VirtualDeviceManagerService extends SystemService {
}
@Override
+ @VirtualDeviceParams.DevicePolicy
+ public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
+ synchronized (mVirtualDeviceManagerLock) {
+ for (int i = 0; i < mVirtualDevices.size(); i++) {
+ final VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
+ if (device.getDeviceId() == deviceId) {
+ return device.getDevicePolicy(policyType);
+ }
+ }
+ }
+ return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+ }
+
+ @Override
public void onVirtualDisplayCreated(int displayId) {
final VirtualDisplayListener[] listeners;
synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 2662e03a5627..544dd4e6dcff 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -26,16 +26,21 @@ import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.InstallSourceInfo;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.SigningInfo;
+import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.Build;
+import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -43,6 +48,8 @@ import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.PackageUtils;
import android.util.Slog;
import android.util.apk.ApkSignatureVerifier;
@@ -54,14 +61,16 @@ import com.android.internal.util.FrameworkStatsLog;
import libcore.util.HexEncoding;
+import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@@ -82,10 +91,36 @@ public class BinaryTransparencyService extends SystemService {
@VisibleForTesting
static final String BINARY_HASH_ERROR = "SHA256HashError";
+ static final int MEASURE_APEX_AND_MODULES = 1;
+ static final int MEASURE_PRELOADS = 2;
+ static final int MEASURE_NEW_MBAS = 3;
+
+ static final long RECORD_MEASUREMENTS_COOLDOWN_MS = 24 * 60 * 60 * 1000;
+
+ @VisibleForTesting
+ static final String BUNDLE_PACKAGE_INFO = "package-info";
+ @VisibleForTesting
+ static final String BUNDLE_CONTENT_DIGEST_ALGORITHM = "content-digest-algo";
+ @VisibleForTesting
+ static final String BUNDLE_CONTENT_DIGEST = "content-digest";
+
+ // used for indicating any type of error during MBA measurement
+ static final int MBA_STATUS_ERROR = 0;
+ // used for indicating factory condition preloads
+ static final int MBA_STATUS_PRELOADED = 1;
+ // used for indicating preloaded apps that are updated
+ static final int MBA_STATUS_UPDATED_PRELOAD = 2;
+ // used for indicating newly installed MBAs
+ static final int MBA_STATUS_NEW_INSTALL = 3;
+ // used for indicating newly installed MBAs that are updated (but unused currently)
+ static final int MBA_STATUS_UPDATED_NEW_INSTALL = 4;
+
+ private static final boolean DEBUG = true; // set this to false upon submission
+
private final Context mContext;
private String mVbmetaDigest;
- private HashMap<String, String> mBinaryHashes;
- private HashMap<String, Long> mBinaryLastUpdateTimes;
+ // the system time (in ms) the last measurement was taken
+ private long mMeasurementsLastRecordedMs;
final class BinaryTransparencyServiceImpl extends IBinaryTransparencyService.Stub {
@@ -95,25 +130,298 @@ public class BinaryTransparencyService extends SystemService {
}
@Override
- public Map getApexInfo() {
- HashMap results = new HashMap();
- if (!updateBinaryMeasurements()) {
- Slog.e(TAG, "Error refreshing APEX measurements.");
- return results;
+ public List getApexInfo() {
+ List<Bundle> results = new ArrayList<>();
+
+ for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
+ Bundle apexMeasurement = measurePackage(packageInfo);
+ results.add(apexMeasurement);
}
+
+ return results;
+ }
+
+ /**
+ * A helper function to compute the SHA256 digest of APK package signer.
+ * @param signingInfo The signingInfo of a package, usually {@link PackageInfo#signingInfo}.
+ * @return an array of {@code String} representing hex encoded string of the
+ * SHA256 digest of APK signer(s). The number of signers will be reflected by the
+ * size of the array.
+ * However, {@code null} is returned if there is any error.
+ */
+ private String[] computePackageSignerSha256Digests(@Nullable SigningInfo signingInfo) {
+ if (signingInfo == null) {
+ Slog.e(TAG, "signingInfo is null");
+ return null;
+ }
+
+ Signature[] packageSigners = signingInfo.getApkContentsSigners();
+ List<String> resultList = new ArrayList<>();
+ for (Signature packageSigner : packageSigners) {
+ byte[] digest = PackageUtils.computeSha256DigestBytes(packageSigner.toByteArray());
+ String digestHexString = HexEncoding.encodeToString(digest, false);
+ resultList.add(digestHexString);
+ }
+ return resultList.toArray(new String[1]);
+ }
+
+ /**
+ * Perform basic measurement (i.e. content digest) on a given package.
+ * @param packageInfo The package to be measured.
+ * @return a {@link android.os.Bundle} that packs the measurement result with the following
+ * keys: {@link #BUNDLE_PACKAGE_INFO},
+ * {@link #BUNDLE_CONTENT_DIGEST_ALGORITHM}
+ * {@link #BUNDLE_CONTENT_DIGEST}
+ */
+ private @NonNull Bundle measurePackage(PackageInfo packageInfo) {
+ Bundle result = new Bundle();
+
+ // compute content digest
+ if (DEBUG) {
+ Slog.d(TAG, "Computing content digest for " + packageInfo.packageName + " at "
+ + packageInfo.applicationInfo.sourceDir);
+ }
+ Map<Integer, byte[]> contentDigests = computeApkContentDigest(
+ packageInfo.applicationInfo.sourceDir);
+ result.putParcelable(BUNDLE_PACKAGE_INFO, packageInfo);
+ if (contentDigests == null) {
+ Slog.d(TAG, "Failed to compute content digest for "
+ + packageInfo.applicationInfo.sourceDir);
+ result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
+ result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
+ return result;
+ }
+
+ // in this iteration, we'll be supporting only 2 types of digests:
+ // CHUNKED_SHA256 and CHUNKED_SHA512.
+ // And only one of them will be available per package.
+ if (contentDigests.containsKey(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256)) {
+ Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
+ result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId);
+ result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId));
+ } else if (contentDigests.containsKey(
+ ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512)) {
+ Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
+ result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId);
+ result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId));
+ } else {
+ // TODO(b/259423111): considering putting the raw values for the algorithm & digest
+ // into the bundle to track potential other digest algorithms that may be in use
+ result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
+ result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
+ }
+
+ return result;
+ }
+
+
+ /**
+ * Measures and records digests for *all* covered binaries/packages.
+ *
+ * This method will be called in a Job scheduled to take measurements periodically.
+ *
+ * Packages that are covered so far are:
+ * - all APEXs (introduced in Android T)
+ * - all mainline modules (introduced in Android T)
+ * - all preloaded apps and their update(s) (new in Android U)
+ * - dynamically installed mobile bundled apps (MBAs) (new in Android U)
+ *
+ * @return a {@code List<Bundle>}. Each Bundle item contains values as
+ * defined by the return value of {@link #measurePackage(PackageInfo)}.
+ */
+ public List getMeasurementsForAllPackages() {
+ List<Bundle> results = new ArrayList<>();
PackageManager pm = mContext.getPackageManager();
- if (pm == null) {
- Slog.e(TAG, "Error obtaining an instance of PackageManager.");
- return results;
+ Set<String> packagesMeasured = new HashSet<>();
+
+ // check if we should record the resulting measurements
+ long currentTimeMs = System.currentTimeMillis();
+ boolean record = false;
+ if ((currentTimeMs - mMeasurementsLastRecordedMs) >= RECORD_MEASUREMENTS_COOLDOWN_MS) {
+ Slog.d(TAG, "Measurement was last taken at " + mMeasurementsLastRecordedMs
+ + " and is now updated to: " + currentTimeMs);
+ mMeasurementsLastRecordedMs = currentTimeMs;
+ record = true;
+ }
+
+ // measure all APEXs first
+ if (DEBUG) {
+ Slog.d(TAG, "Measuring APEXs...");
+ }
+ for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
+ packagesMeasured.add(packageInfo.packageName);
+
+ Bundle apexMeasurement = measurePackage(packageInfo);
+ results.add(apexMeasurement);
+
+ if (record) {
+ // compute digests of signing info
+ String[] signerDigestHexStrings = computePackageSignerSha256Digests(
+ packageInfo.signingInfo);
+
+ // log to Westworld
+ FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
+ packageInfo.packageName,
+ packageInfo.getLongVersionCode(),
+ HexEncoding.encodeToString(apexMeasurement.getByteArray(
+ BUNDLE_CONTENT_DIGEST), false),
+ apexMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
+ signerDigestHexStrings);
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Measured " + packagesMeasured.size()
+ + " packages after considering APEXs.");
+ }
+
+ // proceed with all preloaded apps
+ for (PackageInfo packageInfo : pm.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_FACTORY_ONLY
+ | PackageManager.GET_SIGNING_CERTIFICATES))) {
+ if (packagesMeasured.contains(packageInfo.packageName)) {
+ continue;
+ }
+ packagesMeasured.add(packageInfo.packageName);
+
+ int mba_status = MBA_STATUS_PRELOADED;
+ if (packageInfo.signingInfo == null) {
+ Slog.d(TAG, "Preload " + packageInfo.packageName + " at "
+ + packageInfo.applicationInfo.sourceDir + " has likely been updated.");
+ mba_status = MBA_STATUS_UPDATED_PRELOAD;
+
+ PackageInfo origPackageInfo = packageInfo;
+ try {
+ packageInfo = pm.getPackageInfo(packageInfo.packageName,
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL
+ | PackageManager.GET_SIGNING_CERTIFICATES));
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Failed to obtain an updated PackageInfo of "
+ + origPackageInfo.packageName, e);
+ packageInfo = origPackageInfo;
+ mba_status = MBA_STATUS_ERROR;
+ }
+ }
+
+
+ Bundle packageMeasurement = measurePackage(packageInfo);
+ results.add(packageMeasurement);
+
+ if (record) {
+ // compute digests of signing info
+ String[] signerDigestHexStrings = computePackageSignerSha256Digests(
+ packageInfo.signingInfo);
+
+ // now we should have all the bits for the atom
+ /* TODO: Uncomment and test after merging new atom definition.
+ FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
+ packageInfo.packageName,
+ packageInfo.getLongVersionCode(),
+ HexEncoding.encodeToString(packageMeasurement.getByteArray(
+ BUNDLE_CONTENT_DIGEST), false),
+ packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
+ signerDigestHexStrings, // signer_cert_digest
+ mba_status, // mba_status
+ null, // initiator
+ null, // initiator_signer_digest
+ null, // installer
+ null // originator
+ );
+ */
+ }
}
+ if (DEBUG) {
+ Slog.d(TAG, "Measured " + packagesMeasured.size()
+ + " packages after considering preloads");
+ }
+
+ // lastly measure all newly installed MBAs
+ for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
+ if (packagesMeasured.contains(packageInfo.packageName)) {
+ continue;
+ }
+ packagesMeasured.add(packageInfo.packageName);
+
+ Bundle packageMeasurement = measurePackage(packageInfo);
+ results.add(packageMeasurement);
+
+ if (record) {
+ // compute digests of signing info
+ String[] signerDigestHexStrings = computePackageSignerSha256Digests(
+ packageInfo.signingInfo);
+
+ // then extract package's InstallSourceInfo
+ if (DEBUG) {
+ Slog.d(TAG, "Extracting InstallSourceInfo for " + packageInfo.packageName);
+ }
+ InstallSourceInfo installSourceInfo = getInstallSourceInfo(
+ packageInfo.packageName);
+ String initiator = null;
+ SigningInfo initiatorSignerInfo = null;
+ String[] initiatorSignerInfoDigest = null;
+ String installer = null;
+ String originator = null;
+
+ if (installSourceInfo != null) {
+ initiator = installSourceInfo.getInitiatingPackageName();
+ initiatorSignerInfo = installSourceInfo.getInitiatingPackageSigningInfo();
+ if (initiatorSignerInfo != null) {
+ initiatorSignerInfoDigest = computePackageSignerSha256Digests(
+ initiatorSignerInfo);
+ }
+ installer = installSourceInfo.getInstallingPackageName();
+ originator = installSourceInfo.getOriginatingPackageName();
+ }
- for (PackageInfo packageInfo : getInstalledApexs()) {
- results.put(packageInfo, mBinaryHashes.get(packageInfo.packageName));
+ // we should now have all the info needed for the atom
+ /* TODO: Uncomment and test after merging new atom definition.
+ FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
+ packageInfo.packageName,
+ packageInfo.getLongVersionCode(),
+ HexEncoding.encodeToString(packageMeasurement.getByteArray(
+ BUNDLE_CONTENT_DIGEST), false),
+ packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
+ signerDigestHexStrings,
+ MBA_STATUS_NEW_INSTALL, // mba_status
+ initiator,
+ initiatorSignerInfoDigest,
+ installer,
+ originator
+ );
+ */
+ }
+ }
+ if (DEBUG) {
+ long timeSpentMeasuring = System.currentTimeMillis() - currentTimeMs;
+ Slog.d(TAG, "Measured " + packagesMeasured.size()
+ + " packages altogether in " + timeSpentMeasuring + "ms");
}
return results;
}
+ /**
+ * A wrapper around
+ * {@link ApkSignatureVerifier#verifySignaturesInternal(ParseInput, String, int, boolean)}.
+ * @param pathToApk The APK's installation path
+ * @return a {@code Map<Integer, byte[]>} with algorithm type as the key and content
+ * digest as the value.
+ * a {@code null} is returned upon encountering any error.
+ */
+ private Map<Integer, byte[]> computeApkContentDigest(String pathToApk) {
+ final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+ ParseResult<ApkSignatureVerifier.SigningDetailsWithDigests> parseResult =
+ ApkSignatureVerifier.verifySignaturesInternal(input,
+ pathToApk,
+ SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2, false);
+ if (parseResult.isError()) {
+ Slog.e(TAG, "Failed to compute content digest for "
+ + pathToApk + " due to: "
+ + parseResult.getErrorMessage());
+ return null;
+ }
+ return parseResult.getResult().contentDigests;
+ }
+
@Override
public void onShellCommand(@Nullable FileDescriptor in,
@Nullable FileDescriptor out,
@@ -165,43 +473,36 @@ public class BinaryTransparencyService extends SystemService {
private void printPackageMeasurements(PackageInfo packageInfo,
final PrintWriter pw) {
- pw.print(mBinaryHashes.get(packageInfo.packageName) + ",");
- // TODO: To be moved to somewhere more suitable
- final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
- ParseResult<ApkSignatureVerifier.SigningDetailsWithDigests> parseResult =
- ApkSignatureVerifier.verifySignaturesInternal(input,
- packageInfo.applicationInfo.sourceDir,
- SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, false);
- if (parseResult.isError()) {
+ Map<Integer, byte[]> contentDigests = computeApkContentDigest(
+ packageInfo.applicationInfo.sourceDir);
+ if (contentDigests == null) {
pw.println("ERROR: Failed to compute package content digest for "
- + packageInfo.applicationInfo.sourceDir + "due to: "
- + parseResult.getErrorMessage());
- } else {
- ApkSignatureVerifier.SigningDetailsWithDigests signingDetails =
- parseResult.getResult();
- Map<Integer, byte[]> contentDigests = signingDetails.contentDigests;
- for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
- Integer algorithmId = entry.getKey();
- byte[] cDigest = entry.getValue();
- //pw.print("Content digest algorithm: ");
- switch (algorithmId) {
- case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
- pw.print("CHUNKED_SHA256:");
- break;
- case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
- pw.print("CHUNKED_SHA512:");
- break;
- case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
- pw.print("VERITY_CHUNKED_SHA256:");
- break;
- case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
- pw.print("SHA256:");
- break;
- default:
- pw.print("UNKNOWN:");
- }
- pw.print(HexEncoding.encodeToString(cDigest, false));
+ + packageInfo.applicationInfo.sourceDir);
+ return;
+ }
+
+ for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
+ Integer algorithmId = entry.getKey();
+ byte[] contentDigest = entry.getValue();
+
+ // TODO(b/259348134): consider refactoring the following to a helper method
+ switch (algorithmId) {
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+ pw.print("CHUNKED_SHA256:");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+ pw.print("CHUNKED_SHA512:");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+ pw.print("VERITY_CHUNKED_SHA256:");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+ pw.print("SHA256:");
+ break;
+ default:
+ pw.print("UNKNOWN_ALGO_ID(" + algorithmId + "):");
}
+ pw.print(HexEncoding.encodeToString(contentDigest, false));
}
}
@@ -211,14 +512,49 @@ public class BinaryTransparencyService extends SystemService {
pw.println("Current install location: "
+ packageInfo.applicationInfo.sourceDir);
if (packageInfo.applicationInfo.sourceDir.startsWith("/data/apex/")) {
- String origPackageFilepath = getOriginalPreinstalledLocation(
+ String origPackageFilepath = getOriginalApexPreinstalledLocation(
packageInfo.packageName, packageInfo.applicationInfo.sourceDir);
- pw.println("|--> Package pre-installed location: " + origPackageFilepath);
- String digest = mBinaryHashes.get(origPackageFilepath);
- if (digest == null) {
- pw.println("ERROR finding SHA256-digest from cache...");
+ pw.println("|--> Pre-installed package install location: "
+ + origPackageFilepath);
+
+ // TODO(b/259347186): revive this with the proper cmd options.
+ /*
+ String digest = PackageUtils.computeSha256DigestForLargeFile(
+ origPackageFilepath, PackageUtils.createLargeFileBuffer());
+ */
+
+ Map<Integer, byte[]> contentDigests = computeApkContentDigest(
+ origPackageFilepath);
+ if (contentDigests == null) {
+ pw.println("ERROR: Failed to compute package content digest for "
+ + origPackageFilepath);
} else {
- pw.println("|--> Pre-installed package SHA256-digest: " + digest);
+ // TODO(b/259348134): consider refactoring this to a helper method
+ for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
+ Integer algorithmId = entry.getKey();
+ byte[] contentDigest = entry.getValue();
+ pw.print("|--> Pre-installed package content digest algorithm: ");
+ switch (algorithmId) {
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+ pw.print("CHUNKED_SHA256");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+ pw.print("CHUNKED_SHA512");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+ pw.print("VERITY_CHUNKED_SHA256");
+ break;
+ case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+ pw.print("SHA256");
+ break;
+ default:
+ pw.print("UNKNOWN");
+ }
+ pw.print("\n");
+ pw.print("|--> Pre-installed package content digest: ");
+ pw.print(HexEncoding.encodeToString(contentDigest, false));
+ pw.print("\n");
+ }
}
}
pw.println("First install time (ms): " + packageInfo.firstInstallTime);
@@ -281,21 +617,90 @@ public class BinaryTransparencyService extends SystemService {
+ (moduleInfo.isHidden() ? "hidden" : "visible"));
}
+ private void printAppDetails(PackageInfo packageInfo,
+ boolean printLibraries,
+ final PrintWriter pw) {
+ pw.println("--- App Details ---");
+ pw.println("Name: " + packageInfo.applicationInfo.name);
+ pw.println("Label: " + mContext.getPackageManager().getApplicationLabel(
+ packageInfo.applicationInfo));
+ pw.println("Description: " + packageInfo.applicationInfo.loadDescription(
+ mContext.getPackageManager()));
+ pw.println("Has code: " + packageInfo.applicationInfo.hasCode());
+ pw.println("Is enabled: " + packageInfo.applicationInfo.enabled);
+ pw.println("Is suspended: " + ((packageInfo.applicationInfo.flags
+ & ApplicationInfo.FLAG_SUSPENDED) != 0));
+
+ pw.println("Compile SDK version: " + packageInfo.compileSdkVersion);
+ pw.println("Target SDK version: "
+ + packageInfo.applicationInfo.targetSdkVersion);
+
+ pw.println("Is privileged: "
+ + packageInfo.applicationInfo.isPrivilegedApp());
+ pw.println("Is a stub: " + packageInfo.isStub);
+ pw.println("Is a core app: " + packageInfo.coreApp);
+ pw.println("SEInfo: " + packageInfo.applicationInfo.seInfo);
+ pw.println("Component factory: "
+ + packageInfo.applicationInfo.appComponentFactory);
+ pw.println("Process name: " + packageInfo.applicationInfo.processName);
+ pw.println("Task affinity : " + packageInfo.applicationInfo.taskAffinity);
+ pw.println("UID: " + packageInfo.applicationInfo.uid);
+ pw.println("Shared UID: " + packageInfo.sharedUserId);
+
+ if (printLibraries) {
+ pw.println("== App's Shared Libraries ==");
+ List<SharedLibraryInfo> sharedLibraryInfos =
+ packageInfo.applicationInfo.getSharedLibraryInfos();
+ if (sharedLibraryInfos == null || sharedLibraryInfos.isEmpty()) {
+ pw.println("<none>");
+ }
+
+ for (int i = 0; i < sharedLibraryInfos.size(); i++) {
+ SharedLibraryInfo sharedLibraryInfo = sharedLibraryInfos.get(i);
+ pw.println(" ++ Library #" + (i + 1) + " ++");
+ pw.println(" Lib name: " + sharedLibraryInfo.getName());
+ long libVersion = sharedLibraryInfo.getLongVersion();
+ pw.print(" Lib version: ");
+ if (libVersion == SharedLibraryInfo.VERSION_UNDEFINED) {
+ pw.print("undefined");
+ } else {
+ pw.print(libVersion);
+ }
+ pw.print("\n");
+
+ pw.println(" Lib package name (if available): "
+ + sharedLibraryInfo.getPackageName());
+ pw.println(" Lib path: " + sharedLibraryInfo.getPath());
+ pw.print(" Lib type: ");
+ switch (sharedLibraryInfo.getType()) {
+ case SharedLibraryInfo.TYPE_BUILTIN:
+ pw.print("built-in");
+ break;
+ case SharedLibraryInfo.TYPE_DYNAMIC:
+ pw.print("dynamic");
+ break;
+ case SharedLibraryInfo.TYPE_STATIC:
+ pw.print("static");
+ break;
+ case SharedLibraryInfo.TYPE_SDK_PACKAGE:
+ pw.print("SDK");
+ break;
+ case SharedLibraryInfo.VERSION_UNDEFINED:
+ default:
+ pw.print("undefined");
+ break;
+ }
+ pw.print("\n");
+ pw.println(" Is a native lib: " + sharedLibraryInfo.isNative());
+ }
+ }
+
+ }
+
private int printAllApexs() {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
String opt;
-
- // refresh cache to make sure info is most up-to-date
- if (!updateBinaryMeasurements()) {
- pw.println("ERROR: Failed to refresh info for APEXs.");
- return -1;
- }
- if (mBinaryHashes == null || (mBinaryHashes.size() == 0)) {
- pw.println("ERROR: Unable to obtain apex_info at this time.");
- return -1;
- }
-
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
@@ -315,13 +720,15 @@ public class BinaryTransparencyService extends SystemService {
if (!verbose) {
pw.println("APEX Info [Format: package_name,package_version,"
- + "package_sha256_digest,"
+ // TODO(b/259347186): revive via special cmd line option
+ //+ "package_sha256_digest,"
+ "content_digest_algorithm:content_digest]:");
}
- for (PackageInfo packageInfo : getInstalledApexs()) {
+ for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
if (verbose) {
pw.println("APEX Info [Format: package_name,package_version,"
- + "package_sha256_digest,"
+ // TODO(b/259347186): revive via special cmd line option
+ //+ "package_sha256_digest,"
+ "content_digest_algorithm:content_digest]:");
}
String packageName = packageInfo.packageName;
@@ -352,17 +759,6 @@ public class BinaryTransparencyService extends SystemService {
final PrintWriter pw = getOutPrintWriter();
boolean verbose = false;
String opt;
-
- // refresh cache to make sure info is most up-to-date
- if (!updateBinaryMeasurements()) {
- pw.println("ERROR: Failed to refresh info for Modules.");
- return -1;
- }
- if (mBinaryHashes == null || (mBinaryHashes.size() == 0)) {
- pw.println("ERROR: Unable to obtain module_info at this time.");
- return -1;
- }
-
while ((opt = getNextOption()) != null) {
switch (opt) {
case "-v":
@@ -382,14 +778,16 @@ public class BinaryTransparencyService extends SystemService {
if (!verbose) {
pw.println("Module Info [Format: package_name,package_version,"
- + "package_sha256_digest,"
+ // TODO(b/259347186): revive via special cmd line option
+ //+ "package_sha256_digest,"
+ "content_digest_algorithm:content_digest]:");
}
for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
String packageName = module.getPackageName();
if (verbose) {
pw.println("Module Info [Format: package_name,package_version,"
- + "package_sha256_digest,"
+ // TODO(b/259347186): revive via special cmd line option
+ //+ "package_sha256_digest,"
+ "content_digest_algorithm:content_digest]:");
}
try {
@@ -421,6 +819,72 @@ public class BinaryTransparencyService extends SystemService {
return 0;
}
+ private int printAllMbas() {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean verbose = false;
+ boolean printLibraries = false;
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-v":
+ verbose = true;
+ break;
+ case "-l":
+ printLibraries = true;
+ break;
+ default:
+ pw.println("ERROR: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ if (!verbose) {
+ pw.println("MBA Info [Format: package_name,package_version,"
+ // TODO(b/259347186): revive via special cmd line option
+ //+ "package_sha256_digest,"
+ + "content_digest_algorithm:content_digest]:");
+ }
+ for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
+ if (verbose) {
+ pw.println("MBA Info [Format: package_name,package_version,"
+ // TODO(b/259347186): revive via special cmd line option
+ //+ "package_sha256_digest,"
+ + "content_digest_algorithm:content_digest]:");
+ }
+ pw.print(packageInfo.packageName + ",");
+ pw.print(packageInfo.getLongVersionCode() + ",");
+ printPackageMeasurements(packageInfo, pw);
+ pw.print("\n");
+
+ if (verbose) {
+ printAppDetails(packageInfo, printLibraries, pw);
+ printPackageInstallationInfo(packageInfo, pw);
+ printPackageSignerDetails(packageInfo.signingInfo, pw);
+ pw.println("");
+ }
+ }
+ return 0;
+ }
+
+ // TODO(b/259347186): add option handling full file-based SHA256 digest
+ private int printAllPreloads() {
+ final PrintWriter pw = getOutPrintWriter();
+
+ PackageManager pm = mContext.getPackageManager();
+ if (pm == null) {
+ Slog.e(TAG, "Failed to obtain PackageManager.");
+ return -1;
+ }
+ List<PackageInfo> factoryApps = pm.getInstalledPackages(
+ PackageManager.PackageInfoFlags.of(PackageManager.MATCH_FACTORY_ONLY));
+
+ pw.println("Preload Info [Format: package_name]");
+ for (PackageInfo packageInfo : factoryApps) {
+ pw.println(packageInfo.packageName);
+ }
+ return 0;
+ }
+
@Override
public int onCommand(String cmd) {
if (cmd == null) {
@@ -443,6 +907,10 @@ public class BinaryTransparencyService extends SystemService {
return printAllApexs();
case "module_info":
return printAllModules();
+ case "mba_info":
+ return printAllMbas();
+ case "preload_info":
+ return printAllPreloads();
default:
pw.println(String.format("ERROR: Unknown info type '%s'",
infoType));
@@ -466,11 +934,18 @@ public class BinaryTransparencyService extends SystemService {
pw.println("");
pw.println(" get apex_info [-v]");
pw.println(" Print information about installed APEXs on device.");
- pw.println(" -v: lists more verbose information about each APEX");
+ pw.println(" -v: lists more verbose information about each APEX.");
pw.println("");
pw.println(" get module_info [-v]");
pw.println(" Print information about installed modules on device.");
- pw.println(" -v: lists more verbose information about each module");
+ pw.println(" -v: lists more verbose information about each module.");
+ pw.println("");
+ pw.println(" get mba_info [-v] [-l]");
+ pw.println(" Print information about installed mobile bundle apps "
+ + "(MBAs on device).");
+ pw.println(" -v: lists more verbose information about each app.");
+ pw.println(" -l: lists shared library info. This will only be "
+ + "listed with -v");
pw.println("");
}
@@ -488,8 +963,7 @@ public class BinaryTransparencyService extends SystemService {
mContext = context;
mServiceImpl = new BinaryTransparencyServiceImpl();
mVbmetaDigest = VBMETA_DIGEST_UNINITIALIZED;
- mBinaryHashes = new HashMap<>();
- mBinaryLastUpdateTimes = new HashMap<>();
+ mMeasurementsLastRecordedMs = 0;
}
/**
@@ -520,44 +994,43 @@ public class BinaryTransparencyService extends SystemService {
Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
getVBMetaDigestInformation();
- // due to potentially long computation that holds up boot time, computations for
- // SHA256 digests of APEX and Module packages are scheduled here,
- // but only executed when device is idle.
- Slog.i(TAG, "Scheduling APEX and Module measurements to be updated.");
+ // to avoid the risk of holding up boot time, computations to measure APEX, Module, and
+ // MBA digests are scheduled here, but only executed when the device is idle and plugged
+ // in.
+ Slog.i(TAG, "Scheduling measurements to be taken.");
UpdateMeasurementsJobService.scheduleBinaryMeasurements(mContext,
BinaryTransparencyService.this);
}
}
/**
- * JobService to update binary measurements and update internal cache.
+ * JobService to measure all covered binaries and record result to Westworld.
*/
public static class UpdateMeasurementsJobService extends JobService {
- private static final int COMPUTE_APEX_MODULE_SHA256_JOB_ID =
- BinaryTransparencyService.UpdateMeasurementsJobService.class.hashCode();
+ private static final int DO_BINARY_MEASUREMENTS_JOB_ID =
+ UpdateMeasurementsJobService.class.hashCode();
@Override
public boolean onStartJob(JobParameters params) {
Slog.d(TAG, "Job to update binary measurements started.");
- if (params.getJobId() != COMPUTE_APEX_MODULE_SHA256_JOB_ID) {
+ if (params.getJobId() != DO_BINARY_MEASUREMENTS_JOB_ID) {
return false;
}
- // we'll still update the measurements via threads to be mindful of low-end devices
+ // we'll perform binary measurements via threads to be mindful of low-end devices
// where this operation might take longer than expected, and so that we don't block
// system_server's main thread.
Executors.defaultThreadFactory().newThread(() -> {
- // since we can't call updateBinaryMeasurements() directly, calling
- // getApexInfo() achieves the same effect, and we simply discard the return
- // value
-
+ // we discard the return value of getMeasurementsForAllPackages() as the
+ // results of the measurements will be recorded, and that is what we're aiming
+ // for with this job.
IBinder b = ServiceManager.getService(Context.BINARY_TRANSPARENCY_SERVICE);
IBinaryTransparencyService iBtsService =
IBinaryTransparencyService.Stub.asInterface(b);
try {
- iBtsService.getApexInfo();
+ iBtsService.getMeasurementsForAllPackages();
} catch (RemoteException e) {
- Slog.e(TAG, "Updating binary measurements was interrupted.", e);
+ Slog.e(TAG, "Taking binary measurements was interrupted.", e);
return;
}
jobFinished(params, false);
@@ -573,25 +1046,26 @@ public class BinaryTransparencyService extends SystemService {
@SuppressLint("DefaultLocale")
static void scheduleBinaryMeasurements(Context context, BinaryTransparencyService service) {
- Slog.i(TAG, "Scheduling APEX & Module SHA256 digest computation job");
+ Slog.i(TAG, "Scheduling binary content-digest computation job");
final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
if (jobScheduler == null) {
Slog.e(TAG, "Failed to obtain an instance of JobScheduler.");
return;
}
- final JobInfo jobInfo = new JobInfo.Builder(COMPUTE_APEX_MODULE_SHA256_JOB_ID,
+ final JobInfo jobInfo = new JobInfo.Builder(DO_BINARY_MEASUREMENTS_JOB_ID,
new ComponentName(context, UpdateMeasurementsJobService.class))
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
+ .setPeriodic(RECORD_MEASUREMENTS_COOLDOWN_MS)
.build();
if (jobScheduler.schedule(jobInfo) != JobScheduler.RESULT_SUCCESS) {
- Slog.e(TAG, "Failed to schedule job to update binary measurements.");
+ Slog.e(TAG, "Failed to schedule job to measure binaries.");
return;
}
- Slog.d(TAG, String.format(
- "Job %d to update binary measurements scheduled successfully.",
- COMPUTE_APEX_MODULE_SHA256_JOB_ID));
+ Slog.d(TAG, TextUtils.formatSimple(
+ "Job %d to measure binaries was scheduled successfully.",
+ DO_BINARY_MEASUREMENTS_JOB_ID));
}
}
@@ -602,7 +1076,7 @@ public class BinaryTransparencyService extends SystemService {
}
@NonNull
- private List<PackageInfo> getInstalledApexs() {
+ private List<PackageInfo> getCurrentInstalledApexs() {
List<PackageInfo> results = new ArrayList<>();
PackageManager pm = mContext.getPackageManager();
if (pm == null) {
@@ -636,164 +1110,52 @@ public class BinaryTransparencyService extends SystemService {
}
}
-
- /**
- * Updates the internal data structure with the most current APEX measurements.
- * @return true if update is successful; false otherwise.
- */
- private boolean updateBinaryMeasurements() {
- if (mBinaryHashes.size() == 0) {
- Slog.d(TAG, "No apex in cache yet.");
- doFreshBinaryMeasurements();
- return true;
- }
-
- PackageManager pm = mContext.getPackageManager();
- if (pm == null) {
- Slog.e(TAG, "Failed to obtain a valid PackageManager instance.");
- return false;
- }
-
- // We're assuming updates to existing modules and APEXs can happen, but not brand new
- // ones appearing out of the blue. Thus, we're going to only go through our cache to check
- // for changes, rather than freshly invoking `getInstalledPackages()` and
- // `getInstalledModules()`
- byte[] largeFileBuffer = PackageUtils.createLargeFileBuffer();
- for (Map.Entry<String, Long> entry : mBinaryLastUpdateTimes.entrySet()) {
- String packageName = entry.getKey();
- try {
- PackageInfo packageInfo = pm.getPackageInfo(packageName,
- PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
- long cachedUpdateTime = entry.getValue();
-
- if (packageInfo.lastUpdateTime > cachedUpdateTime) {
- Slog.d(TAG, packageName + " has been updated!");
- entry.setValue(packageInfo.lastUpdateTime);
-
- // compute the digest for the updated package
- String sha256digest = PackageUtils.computeSha256DigestForLargeFile(
- packageInfo.applicationInfo.sourceDir, largeFileBuffer);
- if (sha256digest == null) {
- Slog.e(TAG, "Failed to compute SHA256sum for file at "
- + packageInfo.applicationInfo.sourceDir);
- mBinaryHashes.put(packageName, BINARY_HASH_ERROR);
- } else {
- mBinaryHashes.put(packageName, sha256digest);
- }
-
- if (packageInfo.isApex) {
- FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED,
- packageInfo.packageName,
- packageInfo.getLongVersionCode(),
- mBinaryHashes.get(packageInfo.packageName),
- 4, // indicating that the digest is SHA256
- null); // TODO: This is to comform to the extended schema.
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "Could not find package with name " + packageName);
- continue;
- }
- }
-
- return true;
- }
-
- private String getOriginalPreinstalledLocation(String packageName,
+ // TODO(b/259349011): Need to be more robust against package name mismatch in the filename
+ private String getOriginalApexPreinstalledLocation(String packageName,
String currentInstalledLocation) {
if (currentInstalledLocation.contains("/decompressed/")) {
- return "/system/apex/" + packageName + ".capex";
+ String resultPath = "system/apex" + packageName + ".capex";
+ File f = new File(resultPath);
+ if (f.exists()) {
+ return resultPath;
+ }
+ return "/system/apex/" + packageName + ".next.capex";
}
return "/system/apex" + packageName + "apex";
}
- private void doFreshBinaryMeasurements() {
- PackageManager pm = mContext.getPackageManager();
- Slog.d(TAG, "Obtained package manager");
-
- // In general, we care about all APEXs, *and* all Modules, which may include some APKs.
-
- // First, we deal with all installed APEXs.
- byte[] largeFileBuffer = PackageUtils.createLargeFileBuffer();
- for (PackageInfo packageInfo : getInstalledApexs()) {
- ApplicationInfo appInfo = packageInfo.applicationInfo;
-
- // compute SHA256 for these APEXs
- String sha256digest = PackageUtils.computeSha256DigestForLargeFile(appInfo.sourceDir,
- largeFileBuffer);
- if (sha256digest == null) {
- Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
- packageInfo.packageName));
- mBinaryHashes.put(packageInfo.packageName, BINARY_HASH_ERROR);
- } else {
- mBinaryHashes.put(packageInfo.packageName, sha256digest);
- }
- FrameworkStatsLog.write(FrameworkStatsLog.APEX_INFO_GATHERED, packageInfo.packageName,
- packageInfo.getLongVersionCode(), mBinaryHashes.get(packageInfo.packageName));
- Slog.d(TAG, String.format("Last update time for %s: %d", packageInfo.packageName,
- packageInfo.lastUpdateTime));
- mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime);
- }
-
- for (PackageInfo packageInfo : getInstalledApexs()) {
- ApplicationInfo appInfo = packageInfo.applicationInfo;
- if (!appInfo.sourceDir.startsWith("/data/")) {
- continue;
- }
- String origInstallPath = getOriginalPreinstalledLocation(packageInfo.packageName,
- appInfo.sourceDir);
-
- // compute SHA256 for the orig /system APEXs
- String sha256digest = PackageUtils.computeSha256DigestForLargeFile(origInstallPath,
- largeFileBuffer);
- if (sha256digest == null) {
- Slog.e(TAG, String.format("Failed to compute SHA256 digest for file at %s",
- origInstallPath));
- mBinaryHashes.put(origInstallPath, BINARY_HASH_ERROR);
- } else {
- mBinaryHashes.put(origInstallPath, sha256digest);
- }
- // there'd be no entry into mBinaryLastUpdateTimes
+ /**
+ * Wrapper method to call into IBICS to get a list of all newly installed MBAs.
+ *
+ * We expect IBICS to maintain an accurate list of installed MBAs, and we merely make use of
+ * the results within this service. This means we do not further check whether the
+ * apps in the returned slice is still installed or not, esp. considering that preloaded apps
+ * could be updated, or post-setup installed apps *might* be deleted in real time.
+ *
+ * Note that we do *not* cache the results from IBICS because of the more dynamic nature of
+ * MBAs v.s. other binaries that we measure.
+ *
+ * @return a list of preloaded apps + dynamically installed apps that fit the definition of MBA.
+ */
+ @NonNull
+ private List<PackageInfo> getNewlyInstalledMbas() {
+ List<PackageInfo> result = new ArrayList<>();
+ IBackgroundInstallControlService iBics = IBackgroundInstallControlService.Stub.asInterface(
+ ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+ if (iBics == null) {
+ Slog.e(TAG,
+ "Failed to obtain an IBinder instance of IBackgroundInstallControlService");
+ return result;
}
-
- // Next, get all installed modules from PackageManager - skip over those APEXs we've
- // processed above
- for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
- String packageName = module.getPackageName();
- if (packageName == null) {
- Slog.e(TAG, "ERROR: Encountered null package name for module "
- + module.getApexModuleName());
- continue;
- }
- if (mBinaryHashes.containsKey(module.getPackageName())) {
- continue;
- }
-
- // get PackageInfo for this module
- try {
- PackageInfo packageInfo = pm.getPackageInfo(packageName,
- PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
- ApplicationInfo appInfo = packageInfo.applicationInfo;
-
- // compute SHA256 digest for these modules
- String sha256digest = PackageUtils.computeSha256DigestForLargeFile(
- appInfo.sourceDir, largeFileBuffer);
- if (sha256digest == null) {
- Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s",
- packageName));
- mBinaryHashes.put(packageName, BINARY_HASH_ERROR);
- } else {
- mBinaryHashes.put(packageName, sha256digest);
- }
- Slog.d(TAG, String.format("Last update time for %s: %d", packageName,
- packageInfo.lastUpdateTime));
- mBinaryLastUpdateTimes.put(packageName, packageInfo.lastUpdateTime);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "ERROR: Could not obtain PackageInfo for package name: "
- + packageName);
- continue;
- }
+ ParceledListSlice<PackageInfo> slice;
+ try {
+ slice = iBics.getBackgroundInstalledPackages(
+ PackageManager.MATCH_ALL | PackageManager.GET_SIGNING_CERTIFICATES,
+ UserHandle.USER_SYSTEM);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to get a list of MBAs.", e);
+ return result;
}
+ return slice.getList();
}
-
}
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index e529010e6b7d..7d2e2766fd0b 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -466,7 +466,8 @@ public class GestureLauncherService extends SystemService {
public static boolean isEmergencyGestureSettingEnabled(Context context, int userId) {
return isEmergencyGestureEnabled(context.getResources())
&& Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.EMERGENCY_GESTURE_ENABLED, 1, userId) != 0;
+ Settings.Secure.EMERGENCY_GESTURE_ENABLED,
+ isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0;
}
/**
@@ -513,6 +514,11 @@ public class GestureLauncherService extends SystemService {
return resources.getBoolean(com.android.internal.R.bool.config_emergencyGestureEnabled);
}
+ private static boolean isDefaultEmergencyGestureEnabled(Resources resources) {
+ return resources.getBoolean(
+ com.android.internal.R.bool.config_defaultEmergencyGestureEnabled);
+ }
+
/**
* Whether GestureLauncherService should be enabled according to system properties.
*/
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index c3cd1359cd5f..a05b84baf667 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -351,12 +351,24 @@ public final class SystemServiceManager implements Dumpable {
* Starts the given user.
*/
public void onUserStarting(@NonNull TimingsTraceAndSlog t, @UserIdInt int userId) {
- EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
-
final TargetUser targetUser = newTargetUser(userId);
synchronized (mTargetUsers) {
+ // On Automotive / Headless System User Mode, the system user will be started twice:
+ // - Once by some external or local service that switches the system user to
+ // the background.
+ // - Once by the ActivityManagerService, when the system is marked ready.
+ // These two events are not synchronized and the order of execution is
+ // non-deterministic. To avoid starting the system user twice, verify whether
+ // the system user has already been started by checking the mTargetUsers.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move
+ // the headless-user start logic to UserManager-land.
+ if (userId == UserHandle.USER_SYSTEM && mTargetUsers.contains(userId)) {
+ Slog.e(TAG, "Skipping starting system user twice");
+ return;
+ }
mTargetUsers.put(userId, targetUser);
}
+ EventLog.writeEvent(EventLogTags.SSM_USER_STARTING, userId);
onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 86bb699f07d2..5c18635f570c 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -137,6 +137,7 @@ import android.content.Context;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
@@ -309,6 +310,12 @@ public final class ActiveServices {
final ArrayList<ServiceRecord> mPendingFgsNotifications = new ArrayList<>();
/**
+ * Map of ForegroundServiceDelegation to the delegation ServiceRecord. The delegation
+ * ServiceRecord has flag isFgsDelegate set to true.
+ */
+ final ArrayMap<ForegroundServiceDelegation, ServiceRecord> mFgsDelegations = new ArrayMap<>();
+
+ /**
* Whether there is a rate limit that suppresses immediate re-deferral of new FGS
* notifications from each app. On by default, disabled only by shell command for
* test-suite purposes. To disable the behavior more generally, use the usual
@@ -561,7 +568,7 @@ public final class ActiveServices {
try {
final ServiceRecord.StartItem si = r.pendingStarts.get(0);
startServiceInnerLocked(this, si.intent, r, false, true, si.callingId,
- r.startRequested);
+ si.mCallingProcessName, r.startRequested);
} catch (TransactionTooLargeException e) {
// Ignore, nobody upstack cares.
}
@@ -884,8 +891,10 @@ public final class ActiveServices {
// alias component name to the client, not the "target" component name, which is
// what realResult contains.
final ComponentName realResult =
- startServiceInnerLocked(r, service, callingUid, callingPid, fgRequired, callerFg,
- allowBackgroundActivityStarts, backgroundActivityStartsToken);
+ startServiceInnerLocked(r, service, callingUid, callingPid,
+ getCallingProcessNameLocked(callingUid, callingPid, callingPackage),
+ fgRequired, callerFg, allowBackgroundActivityStarts,
+ backgroundActivityStartsToken);
if (res.aliasComponent != null
&& !realResult.getPackageName().startsWith("!")
&& !realResult.getPackageName().startsWith("?")) {
@@ -895,10 +904,18 @@ public final class ActiveServices {
}
}
+ private String getCallingProcessNameLocked(int callingUid, int callingPid,
+ String callingPackage) {
+ synchronized (mAm.mPidsSelfLocked) {
+ final ProcessRecord callingApp = mAm.mPidsSelfLocked.get(callingPid);
+ return callingApp != null ? callingApp.processName : callingPackage;
+ }
+ }
+
private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service,
- int callingUid, int callingPid, boolean fgRequired, boolean callerFg,
- boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken)
- throws TransactionTooLargeException {
+ int callingUid, int callingPid, String callingProcessName, boolean fgRequired,
+ boolean callerFg, boolean allowBackgroundActivityStarts,
+ @Nullable IBinder backgroundActivityStartsToken) throws TransactionTooLargeException {
NeededUriGrants neededGrants = mAm.mUgmInternal.checkGrantUriPermissionFromIntent(
service, callingUid, r.packageName, r.userId);
if (unscheduleServiceRestartLocked(r, callingUid, false)) {
@@ -910,7 +927,7 @@ public final class ActiveServices {
r.delayedStop = false;
r.fgRequired = fgRequired;
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
- service, neededGrants, callingUid));
+ service, neededGrants, callingUid, callingProcessName));
if (fgRequired) {
// We are now effectively running a foreground service.
@@ -995,7 +1012,7 @@ public final class ActiveServices {
r.allowBgActivityStartsOnServiceStart(backgroundActivityStartsToken);
}
ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting,
- callingUid, wasStartRequested);
+ callingUid, callingProcessName, wasStartRequested);
return cmp;
}
@@ -1113,6 +1130,8 @@ public final class ActiveServices {
curPendingBringups = new ArrayList<>();
mPendingBringups.put(s, curPendingBringups);
}
+ final String callingProcessName = getCallingProcessNameLocked(
+ callingUid, callingPid, callingPackage);
curPendingBringups.add(new Runnable() {
@Override
public void run() {
@@ -1145,8 +1164,8 @@ public final class ActiveServices {
} else { // Starting a service
try {
startServiceInnerLocked(s, serviceIntent, callingUid, callingPid,
- fgRequired, callerFg, allowBackgroundActivityStarts,
- backgroundActivityStartsToken);
+ callingProcessName, fgRequired, callerFg,
+ allowBackgroundActivityStarts, backgroundActivityStartsToken);
} catch (TransactionTooLargeException e) {
/* ignore - local call */
}
@@ -1191,8 +1210,8 @@ public final class ActiveServices {
}
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
- boolean callerFg, boolean addToStarting, int callingUid, boolean wasStartRequested)
- throws TransactionTooLargeException {
+ boolean callerFg, boolean addToStarting, int callingUid, String callingProcessName,
+ boolean wasStartRequested) throws TransactionTooLargeException {
synchronized (mAm.mProcessStats.mLock) {
final ServiceState stracker = r.getTracker();
if (stracker != null) {
@@ -1226,7 +1245,8 @@ public final class ActiveServices {
? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD
: (wasStartRequested || !r.getConnections().isEmpty()
? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_HOT
- : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM));
+ : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM),
+ getShortProcessNameForStats(callingUid, callingProcessName));
if (r.startRequested && addToStarting) {
boolean first = smap.mStartingBackground.size() == 0;
@@ -1249,6 +1269,22 @@ public final class ActiveServices {
return r.name;
}
+ private @Nullable String getShortProcessNameForStats(int uid, String processName) {
+ final String[] packages = mAm.mContext.getPackageManager().getPackagesForUid(uid);
+ if (packages != null && packages.length == 1) {
+ // Not the shared UID case, let's see if the package name equals to the process name.
+ if (TextUtils.equals(packages[0], processName)) {
+ // same name, just return null here.
+ return null;
+ } else if (processName != null && processName.startsWith(packages[0])) {
+ // return the suffix of the process name
+ return processName.substring(packages[0].length());
+ }
+ }
+ // return the full process name.
+ return processName;
+ }
+
private void stopServiceLocked(ServiceRecord service, boolean enqueueOomAdj) {
try {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "stopServiceLocked()");
@@ -3043,7 +3079,7 @@ public final class ActiveServices {
ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
- isBindExternal, allowInstant);
+ isBindExternal, allowInstant, null /* fgsDelegateOptions */);
if (res == null) {
return 0;
}
@@ -3192,7 +3228,8 @@ public final class ActiveServices {
? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD
: (wasStartRequested || hadConnections
? SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_HOT
- : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM));
+ : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM),
+ getShortProcessNameForStats(callingUid, callerApp.processName));
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b
+ ": received=" + b.intent.received
@@ -3501,7 +3538,7 @@ public final class ActiveServices {
boolean allowInstant) {
return retrieveServiceLocked(service, instanceName, false, 0, null, resolvedType,
callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg,
- isBindExternal, allowInstant);
+ isBindExternal, allowInstant, null /* fgsDelegateOptions */);
}
private ServiceLookupResult retrieveServiceLocked(Intent service,
@@ -3509,7 +3546,7 @@ public final class ActiveServices {
String sdkSandboxClientAppPackage, String resolvedType,
String callingPackage, int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
- boolean allowInstant) {
+ boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions) {
if (isSdkSandboxService && instanceName == null) {
throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
}
@@ -3572,6 +3609,53 @@ public final class ActiveServices {
}
}
}
+
+ if (r == null && fgsDelegateOptions != null) {
+ // Create a ServiceRecord for FGS delegate.
+ final ServiceInfo sInfo = new ServiceInfo();
+ ApplicationInfo aInfo = null;
+ try {
+ aInfo = AppGlobals.getPackageManager().getApplicationInfo(
+ fgsDelegateOptions.mClientPackageName,
+ ActivityManagerService.STOCK_PM_FLAGS,
+ userId);
+ } catch (RemoteException ex) {
+ // pm is in same process, this will never happen.
+ }
+ if (aInfo == null) {
+ throw new SecurityException("startForegroundServiceDelegate failed, "
+ + "could not resolve client package " + callingPackage);
+ }
+ if (aInfo.uid != fgsDelegateOptions.mClientUid) {
+ throw new SecurityException("startForegroundServiceDelegate failed, "
+ + "uid:" + aInfo.uid
+ + " does not match clientUid:" + fgsDelegateOptions.mClientUid);
+ }
+ sInfo.applicationInfo = aInfo;
+ sInfo.packageName = aInfo.packageName;
+ sInfo.mForegroundServiceType = fgsDelegateOptions.mForegroundServiceTypes;
+ sInfo.processName = aInfo.processName;
+ final ComponentName cn = service.getComponent();
+ sInfo.name = cn.getClassName();
+ if (createIfNeeded) {
+ final Intent.FilterComparison filter =
+ new Intent.FilterComparison(service.cloneFilter());
+ final ServiceRestarter res = new ServiceRestarter();
+ r = new ServiceRecord(mAm, cn /* name */, cn /* instanceName */,
+ sInfo.applicationInfo.packageName, sInfo.applicationInfo.uid, filter, sInfo,
+ callingFromFg, res, null /* sdkSandboxProcessName */,
+ INVALID_UID /* sdkSandboxClientAppUid */,
+ null /* sdkSandboxClientAppPackage */);
+ res.setService(r);
+ smap.mServicesByInstanceName.put(cn, r);
+ smap.mServicesByIntent.put(filter, r);
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Retrieve created new service: " + r);
+ r.mRecentCallingPackage = callingPackage;
+ r.mRecentCallingUid = callingUid;
+ }
+ return new ServiceLookupResult(r, resolution.getAlias());
+ }
+
if (r == null) {
try {
int flags = ActivityManagerService.STOCK_PM_FLAGS
@@ -4661,7 +4745,7 @@ public final class ActiveServices {
// be called.
if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
- null, null, 0));
+ null, null, 0, null));
}
sendServiceArgsLocked(r, execInFg, true);
@@ -4983,16 +5067,31 @@ public final class ActiveServices {
// Bump the process to the top of LRU list
mAm.updateLruProcessLocked(r.app, false, null);
updateServiceForegroundLocked(r.app.mServices, false);
- try {
- oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
- oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
- mDestroyingServices.add(r);
- r.destroying = true;
- r.app.getThread().scheduleStopService(r);
- } catch (Exception e) {
- Slog.w(TAG, "Exception when destroying service "
- + r.shortInstanceName, e);
- serviceProcessGoneLocked(r, enqueueOomAdj);
+ if (r.mIsFgsDelegate) {
+ if (r.mFgsDelegation.mConnection != null) {
+ mAm.mHandler.post(() -> {
+ r.mFgsDelegation.mConnection.onServiceDisconnected(
+ r.mFgsDelegation.mOptions.getComponentName());
+ });
+ }
+ for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+ if (mFgsDelegations.valueAt(i) == r) {
+ mFgsDelegations.removeAt(i);
+ break;
+ }
+ }
+ } else {
+ try {
+ oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
+ oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mDestroyingServices.add(r);
+ r.destroying = true;
+ r.app.getThread().scheduleStopService(r);
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception when destroying service "
+ + r.shortInstanceName, e);
+ serviceProcessGoneLocked(r, enqueueOomAdj);
+ }
}
} else {
if (DEBUG_SERVICE) Slog.v(
@@ -5620,7 +5719,7 @@ public final class ActiveServices {
stopServiceLocked(sr, true);
} else {
sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
- sr.getLastStartId(), baseIntent, null, 0));
+ sr.getLastStartId(), baseIntent, null, 0, null));
if (sr.app != null && sr.app.getThread() != null) {
// We always run in the foreground, since this is called as
// part of the "remove task" UI operation.
@@ -7234,7 +7333,12 @@ public final class ActiveServices {
ActivityManagerUtils.hashComponentNameForAtom(r.shortInstanceName),
r.mFgsHasNotificationPermission,
r.foregroundServiceType,
- fgsTypeCheckCode);
+ fgsTypeCheckCode,
+ r.mIsFgsDelegate,
+ r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mClientUid : INVALID_UID,
+ r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mDelegationService
+ : ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT
+ );
int event = 0;
if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
@@ -7298,4 +7402,163 @@ public final class ActiveServices {
return "UNKNOWN";
}
}
+
+ /**
+ * Start a foreground service delegate. The delegate is not an actual service component, it is
+ * merely a delegate that promotes the client process into foreground service process state.
+ *
+ * @param options an ForegroundServiceDelegationOptions object.
+ * @param connection callback if the delegate is started successfully.
+ * @return true if delegate is started, false otherwise.
+ * @throw SecurityException if PackageManaager can not resolve
+ * {@link ForegroundServiceDelegationOptions#mClientPackageName} or the resolved
+ * package's UID is not same as {@link ForegroundServiceDelegationOptions#mClientUid}
+ */
+ boolean startForegroundServiceDelegateLocked(
+ @NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection) {
+ Slog.v(TAG, "startForegroundServiceDelegateLocked " + options.getDescription());
+ final ComponentName cn = options.getComponentName();
+ for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+ ForegroundServiceDelegation delegation = mFgsDelegations.keyAt(i);
+ if (delegation.mOptions.isSameDelegate(options)) {
+ Slog.e(TAG, "startForegroundServiceDelegate " + options.getDescription()
+ + " already exists, multiple connections are not allowed");
+ return false;
+ }
+ }
+ final int callingPid = options.mClientPid;
+ final int callingUid = options.mClientUid;
+ final int userId = UserHandle.getUserId(callingUid);
+ final String callingPackage = options.mClientPackageName;
+
+ if (!canStartForegroundServiceLocked(callingPid, callingUid, callingPackage)) {
+ Slog.d(TAG, "startForegroundServiceDelegateLocked aborted,"
+ + " app is in the background");
+ return false;
+ }
+
+ IApplicationThread caller = options.mClientAppThread;
+ ProcessRecord callerApp;
+ if (caller != null) {
+ callerApp = mAm.getRecordForAppLOSP(caller);
+ } else {
+ synchronized (mAm.mPidsSelfLocked) {
+ callerApp = mAm.mPidsSelfLocked.get(callingPid);
+ caller = callerApp.getThread();
+ }
+ }
+ if (callerApp == null) {
+ throw new SecurityException(
+ "Unable to find app for caller " + caller
+ + " (pid=" + callingPid
+ + ") when startForegroundServiceDelegateLocked " + cn);
+ }
+
+ Intent intent = new Intent();
+ intent.setComponent(cn);
+ ServiceLookupResult res = retrieveServiceLocked(intent, null /*instanceName */,
+ false /* isSdkSandboxService */, INVALID_UID /* sdkSandboxClientAppUid */,
+ null /* sdkSandboxClientAppPackage */, null /* resolvedType */, callingPackage,
+ callingPid, callingUid, userId, true /* createIfNeeded */,
+ false /* callingFromFg */, false /* isBindExternal */, false /* allowInstant */ ,
+ options);
+ if (res == null || res.record == null) {
+ Slog.d(TAG,
+ "startForegroundServiceDelegateLocked retrieveServiceLocked returns null");
+ return false;
+ }
+
+ final ServiceRecord r = res.record;
+ r.setProcess(callerApp, caller, callingPid, null);
+ r.mIsFgsDelegate = true;
+ final ForegroundServiceDelegation delegation =
+ new ForegroundServiceDelegation(options, connection);
+ r.mFgsDelegation = delegation;
+ mFgsDelegations.put(delegation, r);
+ r.isForeground = true;
+ r.mFgsEnterTime = SystemClock.uptimeMillis();
+ r.foregroundServiceType = options.mForegroundServiceTypes;
+ setFgsRestrictionLocked(callingPackage, callingPid, callingUid, intent, r, userId,
+ false, false);
+ final ProcessServiceRecord psr = callerApp.mServices;
+ final boolean newService = psr.startService(r);
+ // updateOomAdj.
+ updateServiceForegroundLocked(psr, /* oomAdj= */ true);
+
+ synchronized (mAm.mProcessStats.mLock) {
+ final ServiceState stracker = r.getTracker();
+ if (stracker != null) {
+ stracker.setForeground(true,
+ mAm.mProcessStats.getMemFactorLocked(),
+ SystemClock.uptimeMillis());
+ }
+ }
+
+ mAm.mBatteryStatsService.noteServiceStartRunning(callingUid, callingPackage,
+ cn.getClassName());
+ mAm.mAppOpsService.startOperation(AppOpsManager.getToken(mAm.mAppOpsService),
+ AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName, null,
+ true, false, null, false,
+ AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+ registerAppOpCallbackLocked(r);
+ logFGSStateChangeLocked(r,
+ FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER,
+ 0, FGS_STOP_REASON_UNKNOWN, FGS_TYPE_POLICY_CHECK_UNKNOWN);
+ // Notify the caller.
+ if (connection != null) {
+ mAm.mHandler.post(() -> {
+ connection.onServiceConnected(cn, delegation.mBinder);
+ });
+ }
+ signalForegroundServiceObserversLocked(r);
+ return true;
+ }
+
+ /**
+ * Stop the foreground service delegate. This removes the process out of foreground service
+ * process state.
+ *
+ * @param options an ForegroundServiceDelegationOptions object.
+ */
+ void stopForegroundServiceDelegateLocked(@NonNull ForegroundServiceDelegationOptions options) {
+ ServiceRecord r = null;
+ for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+ if (mFgsDelegations.keyAt(i).mOptions.isSameDelegate(options)) {
+ Slog.d(TAG, "stopForegroundServiceDelegateLocked " + options.getDescription());
+ r = mFgsDelegations.valueAt(i);
+ break;
+ }
+ }
+ if (r != null) {
+ bringDownServiceLocked(r, false);
+ } else {
+ Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist "
+ + options.getDescription());
+ }
+ }
+
+ /**
+ * Stop the foreground service delegate by its ServiceConnection.
+ * This removes the process out of foreground service process state.
+ *
+ * @param connection an ServiceConnection object.
+ */
+ void stopForegroundServiceDelegateLocked(@NonNull ServiceConnection connection) {
+ ServiceRecord r = null;
+ for (int i = mFgsDelegations.size() - 1; i >= 0; i--) {
+ final ForegroundServiceDelegation d = mFgsDelegations.keyAt(i);
+ if (d.mConnection == connection) {
+ Slog.d(TAG, "stopForegroundServiceDelegateLocked "
+ + d.mOptions.getDescription());
+ r = mFgsDelegations.valueAt(i);
+ break;
+ }
+ }
+ if (r != null) {
+ bringDownServiceLocked(r, false);
+ } else {
+ Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist");
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 003f7f0d88fb..8b5b795b8ff5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -318,6 +318,12 @@ final class ActivityManagerConstants extends ContentObserver {
"deferred_fgs_notification_interval";
/**
+ * Same as {@link #KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL} but for "short FGS".
+ */
+ private static final String KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL_FOR_SHORT =
+ "deferred_fgs_notification_interval_for_short";
+
+ /**
* Time in milliseconds; once an FGS notification for a given uid has been
* deferred, no subsequent FGS notification from that uid will be deferred
* until this amount of time has passed. Default is two minutes
@@ -327,6 +333,12 @@ final class ActivityManagerConstants extends ContentObserver {
"deferred_fgs_notification_exclusion_time";
/**
+ * Same as {@link #KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME} but for "short FGS".
+ */
+ private static final String KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME_FOR_SHORT =
+ "deferred_fgs_notification_exclusion_time_for_short";
+
+ /**
* Default value for mPushMessagingOverQuotaBehavior if not explicitly set in
* Settings.Global.
*/
@@ -583,11 +595,22 @@ final class ActivityManagerConstants extends ContentObserver {
// the foreground state.
volatile long mFgsNotificationDeferralInterval = 10_000;
+ /**
+ * Same as {@link #mFgsNotificationDeferralInterval} but used for "short FGS".
+ */
+ volatile long mFgsNotificationDeferralIntervalForShort = mFgsNotificationDeferralInterval;
+
// Rate limit: minimum time after an app's FGS notification is deferred
// before another FGS notification from that app can be deferred.
volatile long mFgsNotificationDeferralExclusionTime = 2 * 60 * 1000L;
/**
+ * Same as {@link #mFgsNotificationDeferralExclusionTime} but used for "short FGS".
+ */
+ volatile long mFgsNotificationDeferralExclusionTimeForShort =
+ mFgsNotificationDeferralExclusionTime;
+
+ /**
* When server pushing message is over the quote, select one of the temp allow list type as
* defined in {@link PowerExemptionManager.TempAllowListType}
*/
@@ -923,6 +946,32 @@ final class ActivityManagerConstants extends ContentObserver {
public static boolean PROACTIVE_KILLS_ENABLED = DEFAULT_PROACTIVE_KILLS_ENABLED;
public static float LOW_SWAP_THRESHOLD_PERCENT = DEFAULT_LOW_SWAP_THRESHOLD_PERCENT;
+ /** Timeout for a "short service" FGS, in milliseconds. */
+ private static final String KEY_SHORT_FGS_TIMEOUT_DURATION =
+ "short_fgs_timeout_duration";
+
+ /** @see #KEY_SHORT_FGS_TIMEOUT_DURATION */
+ static final long DEFAULT_SHORT_FGS_TIMEOUT_DURATION = 60_000;
+
+ /** @see #KEY_SHORT_FGS_TIMEOUT_DURATION */
+ public static volatile long mShortFgsTimeoutDuration = DEFAULT_SHORT_FGS_TIMEOUT_DURATION;
+
+ /**
+ * If a "short service" doesn't finish within this after the timeout (
+ * {@link #KEY_SHORT_FGS_TIMEOUT_DURATION}), then we'll declare an ANR.
+ * i.e. if the timeout is 60 seconds, and this ANR extra duration is 5 seconds, then
+ * the app will be ANR'ed in 65 seconds after a short service starts and it's not stopped.
+ */
+ private static final String KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION =
+ "short_fgs_anr_extra_wait_duration";
+
+ /** @see #KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION */
+ static final long DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION = 5_000;
+
+ /** @see #KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION */
+ public static volatile long mShortFgsAnrExtraWaitDuration =
+ DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION;
+
private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
new OnPropertiesChangedListener() {
@Override
@@ -962,6 +1011,12 @@ final class ActivityManagerConstants extends ContentObserver {
case KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME:
updateFgsNotificationDeferralExclusionTime();
break;
+ case KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL_FOR_SHORT:
+ updateFgsNotificationDeferralIntervalForShort();
+ break;
+ case KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME_FOR_SHORT:
+ updateFgsNotificationDeferralExclusionTimeForShort();
+ break;
case KEY_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR:
updatePushMessagingOverQuotaBehavior();
break;
@@ -1058,6 +1113,12 @@ final class ActivityManagerConstants extends ContentObserver {
case KEY_MAX_SERVICE_CONNECTIONS_PER_PROCESS:
updateMaxServiceConnectionsPerProcess();
break;
+ case KEY_SHORT_FGS_TIMEOUT_DURATION:
+ updateShortFgsTimeoutDuration();
+ break;
+ case KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION:
+ updateShortFgsAnrExtraWaitDuration();
+ break;
case KEY_PROACTIVE_KILLS_ENABLED:
updateProactiveKillsEnabled();
break;
@@ -1374,6 +1435,13 @@ final class ActivityManagerConstants extends ContentObserver {
/*default value*/ 10_000L);
}
+ private void updateFgsNotificationDeferralIntervalForShort() {
+ mFgsNotificationDeferralIntervalForShort = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL_FOR_SHORT,
+ /*default value*/ 10_000L);
+ }
+
private void updateFgsNotificationDeferralExclusionTime() {
mFgsNotificationDeferralExclusionTime = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1381,6 +1449,13 @@ final class ActivityManagerConstants extends ContentObserver {
/*default value*/ 2 * 60 * 1000L);
}
+ private void updateFgsNotificationDeferralExclusionTimeForShort() {
+ mFgsNotificationDeferralExclusionTimeForShort = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME_FOR_SHORT,
+ /*default value*/ 2 * 60 * 1000L);
+ }
+
private void updatePushMessagingOverQuotaBehavior() {
mPushMessagingOverQuotaBehavior = DeviceConfig.getInt(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1746,6 +1821,20 @@ final class ActivityManagerConstants extends ContentObserver {
DEFAULT_MAX_SERVICE_CONNECTIONS_PER_PROCESS);
}
+ private void updateShortFgsTimeoutDuration() {
+ mShortFgsTimeoutDuration = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_SHORT_FGS_TIMEOUT_DURATION,
+ DEFAULT_SHORT_FGS_TIMEOUT_DURATION);
+ }
+
+ private void updateShortFgsAnrExtraWaitDuration() {
+ mShortFgsAnrExtraWaitDuration = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION,
+ DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
void dump(PrintWriter pw) {
pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
@@ -1903,6 +1992,26 @@ final class ActivityManagerConstants extends ContentObserver {
pw.print(" "); pw.print(KEY_LOW_SWAP_THRESHOLD_PERCENT);
pw.print("="); pw.println(LOW_SWAP_THRESHOLD_PERCENT);
+ pw.print(" "); pw.print(KEY_DEFERRED_FGS_NOTIFICATIONS_ENABLED);
+ pw.print("="); pw.println(mFlagFgsNotificationDeferralEnabled);
+ pw.print(" "); pw.print(KEY_DEFERRED_FGS_NOTIFICATIONS_API_GATED);
+ pw.print("="); pw.println(mFlagFgsNotificationDeferralApiGated);
+
+ pw.print(" "); pw.print(KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL);
+ pw.print("="); pw.println(mFgsNotificationDeferralInterval);
+ pw.print(" "); pw.print(KEY_DEFERRED_FGS_NOTIFICATION_INTERVAL_FOR_SHORT);
+ pw.print("="); pw.println(mFgsNotificationDeferralIntervalForShort);
+
+ pw.print(" "); pw.print(KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME);
+ pw.print("="); pw.println(mFgsNotificationDeferralExclusionTime);
+ pw.print(" "); pw.print(KEY_DEFERRED_FGS_NOTIFICATION_EXCLUSION_TIME_FOR_SHORT);
+ pw.print("="); pw.println(mFgsNotificationDeferralExclusionTimeForShort);
+
+ pw.print(" "); pw.print(KEY_SHORT_FGS_TIMEOUT_DURATION);
+ pw.print("="); pw.println(mShortFgsTimeoutDuration);
+ pw.print(" "); pw.print(KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
+ pw.print("="); pw.println(mShortFgsAnrExtraWaitDuration);
+
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
pw.print(" mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses);
diff --git a/services/core/java/com/android/server/am/ActivityManagerLocal.java b/services/core/java/com/android/server/am/ActivityManagerLocal.java
index 1d2c36b63bda..9f2cc7f9cb44 100644
--- a/services/core/java/com/android/server/am/ActivityManagerLocal.java
+++ b/services/core/java/com/android/server/am/ActivityManagerLocal.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
@@ -92,4 +93,28 @@ public interface ActivityManagerLocal {
int clientAppUid, @NonNull String clientAppPackage, @NonNull String processName,
@Context.BindServiceFlags int flags)
throws RemoteException;
+
+ /**
+ * Start a foreground service delegate.
+ * @param options foreground service delegate options.
+ * @param connection a service connection served as callback to caller.
+ * @return true if delegate is started successfully, false otherwise.
+ * @hide
+ */
+ boolean startForegroundServiceDelegate(@NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection);
+
+ /**
+ * Stop a foreground service delegate.
+ * @param options the foreground service delegate options.
+ * @hide
+ */
+ void stopForegroundServiceDelegate(@NonNull ForegroundServiceDelegationOptions options);
+
+ /**
+ * Stop a foreground service delegate by service connection.
+ * @param connection service connection used to start delegate previously.
+ * @hide
+ */
+ void stopForegroundServiceDelegate(@NonNull ServiceConnection connection);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d44b727d724b..39f6ef2290f8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -181,6 +181,7 @@ import android.app.ApplicationErrorReport;
import android.app.ApplicationExitInfo;
import android.app.ApplicationThreadConstants;
import android.app.BroadcastOptions;
+import android.app.ComponentOptions;
import android.app.ContentProviderHolder;
import android.app.IActivityController;
import android.app.IActivityManager;
@@ -8400,13 +8401,10 @@ public class ActivityManagerService extends IActivityManager.Stub
// On Automotive / Headless System User Mode, at this point the system user has already been
// started and unlocked, and some of the tasks we do here have already been done. So skip
- // those in that case.
+ // those in that case. The duplicate system user start is guarded in SystemServiceManager.
// TODO(b/242195409): this workaround shouldn't be necessary once we move the headless-user
- // start logic to UserManager-land
- final boolean bootingSystemUser = currentUserId == UserHandle.USER_SYSTEM;
- if (bootingSystemUser) {
- mUserController.onSystemUserStarting();
- }
+ // start logic to UserManager-land.
+ mUserController.onSystemUserStarting();
synchronized (this) {
// Only start up encryption-aware persistent apps; once user is
@@ -8436,7 +8434,15 @@ public class ActivityManagerService extends IActivityManager.Stub
t.traceEnd();
}
- if (bootingSystemUser) {
+ // Some systems - like automotive - will explicitly unlock system user then switch
+ // to a secondary user. Hence, we don't want to send duplicate broadcasts for
+ // the system user here.
+ // TODO(b/242195409): this workaround shouldn't be necessary once we move
+ // the headless-user start logic to UserManager-land.
+ final boolean isBootingSystemUser = (currentUserId == UserHandle.USER_SYSTEM)
+ && !UserManager.isHeadlessSystemUserMode();
+
+ if (isBootingSystemUser) {
t.traceBegin("startHomeOnAllDisplays");
mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
t.traceEnd();
@@ -8447,7 +8453,7 @@ public class ActivityManagerService extends IActivityManager.Stub
t.traceEnd();
- if (bootingSystemUser) {
+ if (isBootingSystemUser) {
t.traceBegin("sendUserStartBroadcast");
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
@@ -8488,7 +8494,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mAtmInternal.resumeTopActivities(false /* scheduleIdle */);
t.traceEnd();
- if (bootingSystemUser) {
+ if (isBootingSystemUser) {
t.traceBegin("sendUserSwitchBroadcasts");
mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
t.traceEnd();
@@ -13504,9 +13510,19 @@ public class ActivityManagerService extends IActivityManager.Stub
// Don't enforce the flag check if we're EITHER registering for only protected
// broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
// not be used generally, so we will be marking them as exported by default
- final boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
+ boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)
&& mConstants.mEnforceReceiverExportedFlagRequirement;
+ // STOPSHIP(b/259139792): Allow apps that are currently targeting U and in process of
+ // updating their receivers to be exempt from this requirement until their receivers
+ // are flagged.
+ if (requireExplicitFlagForDynamicReceivers) {
+ if ("com.google.android.apps.messaging".equals(callerPackage)) {
+ // Note, a versionCode check for this package is not performed because it could
+ // cause breakage with a subsequent update outside the system image.
+ requireExplicitFlagForDynamicReceivers = false;
+ }
+ }
if (!onlyProtectedBroadcasts) {
if (receiver == null && !explicitExportStateDefined) {
// sticky broadcast, no flag specified (flag isn't required)
@@ -13899,10 +13915,10 @@ public class ActivityManagerService extends IActivityManager.Stub
throw new SecurityException(
"Non-system callers may not flag broadcasts as alarm");
}
- if (options.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) {
+ if (options.containsKey(ComponentOptions.KEY_INTERACTIVE)) {
enforceCallingPermission(
- android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
- "setInteractiveBroadcast");
+ android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE,
+ "setInteractive");
}
}
}
@@ -18110,6 +18126,30 @@ public class ActivityManagerService extends IActivityManager.Stub
mUidObserverController.register(observer, which, cutpoint, callingPackage,
Binder.getCallingUid());
}
+
+ @Override
+ public boolean startForegroundServiceDelegate(
+ @NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection) {
+ synchronized (ActivityManagerService.this) {
+ return mServices.startForegroundServiceDelegateLocked(options, connection);
+ }
+ }
+
+ @Override
+ public void stopForegroundServiceDelegate(
+ @NonNull ForegroundServiceDelegationOptions options) {
+ synchronized (ActivityManagerService.this) {
+ mServices.stopForegroundServiceDelegateLocked(options);
+ }
+ }
+
+ @Override
+ public void stopForegroundServiceDelegate(@NonNull ServiceConnection connection) {
+ synchronized (ActivityManagerService.this) {
+ mServices.stopForegroundServiceDelegateLocked(connection);
+ }
+ }
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -18299,6 +18339,59 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
+ * Start/stop foreground service delegate on a app's process.
+ * This interface is intended for the shell command to use.
+ */
+ void setForegroundServiceDelegate(String packageName, int uid, boolean isStart,
+ @ForegroundServiceDelegationOptions.DelegationService int delegateService,
+ String clientInstanceName) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != SYSTEM_UID && callingUid != ROOT_UID && callingUid != SHELL_UID) {
+ throw new SecurityException(
+ "No permission to start/stop foreground service delegate");
+ }
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ boolean foundPid = false;
+ synchronized (this) {
+ ArrayList<ForegroundServiceDelegationOptions> delegates = new ArrayList<>();
+ synchronized (mPidsSelfLocked) {
+ for (int i = 0; i < mPidsSelfLocked.size(); i++) {
+ final ProcessRecord p = mPidsSelfLocked.valueAt(i);
+ final IApplicationThread thread = p.getThread();
+ if (p.uid == uid && thread != null) {
+ foundPid = true;
+ int pid = mPidsSelfLocked.keyAt(i);
+ ForegroundServiceDelegationOptions options =
+ new ForegroundServiceDelegationOptions(pid, uid, packageName,
+ null /* clientAppThread */,
+ false /* isSticky */,
+ clientInstanceName, 0 /* foregroundServiceType */,
+ delegateService);
+ delegates.add(options);
+ }
+ }
+ }
+ for (int i = delegates.size() - 1; i >= 0; i--) {
+ final ForegroundServiceDelegationOptions options = delegates.get(i);
+ if (isStart) {
+ ((ActivityManagerLocal) mInternal).startForegroundServiceDelegate(options,
+ null /* connection */);
+ } else {
+ ((ActivityManagerLocal) mInternal).stopForegroundServiceDelegate(options);
+ }
+ }
+ }
+ if (!foundPid) {
+ Slog.e(TAG, "setForegroundServiceDelegate can not find process for packageName:"
+ + packageName + " uid:" + uid);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
+ }
+
+ /**
* Force the settings cache to be loaded
*/
void refreshSettingsCache() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index e4f947da868e..10f5a369824f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -369,6 +369,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runResetDropboxRateLimiter();
case "list-secondary-displays-for-starting-users":
return runListSecondaryDisplaysForStartingUsers(pw);
+ case "set-foreground-service-delegate":
+ return runSetForegroundServiceDelegate(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -3592,6 +3594,45 @@ final class ActivityManagerShellCommand extends ShellCommand {
return 0;
}
+ int runSetForegroundServiceDelegate(PrintWriter pw) throws RemoteException {
+ int userId = UserHandle.USER_CURRENT;
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if (opt.equals("--user")) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ getErrPrintWriter().println("Error: Unknown option: " + opt);
+ return -1;
+ }
+ }
+ final String packageName = getNextArgRequired();
+ final String action = getNextArgRequired();
+ boolean isStart = true;
+ if ("start".equals(action)) {
+ isStart = true;
+ } else if ("stop".equals(action)) {
+ isStart = false;
+ } else {
+ pw.println("Error: action is either start or stop");
+ return -1;
+ }
+
+ int uid = INVALID_UID;
+ try {
+ final PackageManager pm = mInternal.mContext.getPackageManager();
+ uid = pm.getPackageUidAsUser(packageName,
+ PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), userId);
+ } catch (PackageManager.NameNotFoundException e) {
+ pw.println("Error: userId:" + userId + " package:" + packageName + " is not found");
+ return -1;
+ }
+ mInternal.setForegroundServiceDelegate(packageName, uid, isStart,
+ ForegroundServiceDelegationOptions.DELEGATION_SERVICE_SPECIAL_USE,
+ "FgsDelegate");
+ return 0;
+ }
+
int runResetDropboxRateLimiter() throws RemoteException {
mInternal.resetDropboxRateLimiter();
return 0;
@@ -3968,6 +4009,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" list-secondary-displays-for-starting-users");
pw.println(" Lists the id of displays that can be used to start users on "
+ "background.");
+ pw.println(" set-foreground-service-delegate [--user <USER_ID>] <PACKAGE> start|stop");
+ pw.println(" Start/stop an app's foreground service delegate.");
pw.println();
Intent.printIntentArgsHelp(pw, "");
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 739d2777fe17..7d9b4776bf37 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -596,20 +596,21 @@ class BroadcastProcessQueue {
* barrier timestamp that are still waiting to be delivered.
*/
public boolean isBeyondBarrierLocked(@UptimeMillisLong long barrierTime) {
- if (mActive != null) {
- return mActive.enqueueTime > barrierTime;
- }
final SomeArgs next = mPending.peekFirst();
final SomeArgs nextUrgent = mPendingUrgent.peekFirst();
final SomeArgs nextOffload = mPendingOffload.peekFirst();
- // Empty queue is past any barrier
- final boolean nextLater = (next == null)
+
+ // Empty records are always past any barrier
+ final boolean activeBeyond = (mActive == null)
+ || mActive.enqueueTime > barrierTime;
+ final boolean nextBeyond = (next == null)
|| ((BroadcastRecord) next.arg1).enqueueTime > barrierTime;
- final boolean nextUrgentLater = (nextUrgent == null)
+ final boolean nextUrgentBeyond = (nextUrgent == null)
|| ((BroadcastRecord) nextUrgent.arg1).enqueueTime > barrierTime;
- final boolean nextOffloadLater = (nextOffload == null)
+ final boolean nextOffloadBeyond = (nextOffload == null)
|| ((BroadcastRecord) nextOffload.arg1).enqueueTime > barrierTime;
- return nextLater && nextUrgentLater && nextOffloadLater;
+
+ return activeBeyond && nextBeyond && nextUrgentBeyond && nextOffloadBeyond;
}
public boolean isRunnable() {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 454b28402ced..fb7e0be69d78 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -51,7 +51,6 @@ import android.content.ContentResolver;
import android.content.IIntentReceiver;
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.UserInfo;
@@ -1639,9 +1638,8 @@ public class BroadcastQueueImpl extends BroadcastQueue {
}
Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
logBroadcastReceiverDiscardLocked(r);
- String anrMessage =
- "Broadcast of " + r.intent.toString() + ", waited " + timeoutDurationMs + "ms";
- TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(anrMessage);
+ TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(r.intent,
+ timeoutDurationMs);
if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
BroadcastFilter bf = (BroadcastFilter) curReceiver;
if (bf.receiverList.pid != 0
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 008d95a51cd9..a7d843361762 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -907,8 +907,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
r.anrCount++;
if (app != null && !app.isDebugging()) {
- mService.appNotResponding(queue.app, TimeoutRecord
- .forBroadcastReceiver("Broadcast of " + r.toShortString()));
+ mService.appNotResponding(queue.app, TimeoutRecord.forBroadcastReceiver(r.intent));
}
} else {
mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_SOFT, queue);
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 6ea2dee5b578..84d744270e82 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -397,7 +397,7 @@ final class BroadcastRecord extends Binder {
alarm = options != null && options.isAlarmBroadcast();
pushMessage = options != null && options.isPushMessagingBroadcast();
pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
- interactive = options != null && options.isInteractiveBroadcast();
+ interactive = options != null && options.isInteractive();
this.filterExtrasForReceiver = filterExtrasForReceiver;
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index e34cd12d0ec3..b98639e31d00 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -37,6 +37,7 @@ import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.EventLog;
+import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -2110,15 +2111,28 @@ public final class CachedAppOptimizer {
@GuardedBy({"mAm"})
@Override
- public void onBlockingFileLock(int pid) {
+ public void onBlockingFileLock(IntArray pids) {
if (DEBUG_FREEZER) {
- Slog.d(TAG_AM, "Process (pid=" + pid + ") holds blocking file lock");
+ Slog.d(TAG_AM, "Blocking file lock found: " + pids);
}
synchronized (mProcLock) {
+ int pid = pids.get(0);
ProcessRecord app = mFrozenProcesses.get(pid);
+ ProcessRecord pr;
if (app != null) {
- Slog.i(TAG_AM, app.processName + " (" + pid + ") holds blocking file lock");
- unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ for (int i = 1; i < pids.size(); i++) {
+ int blocked = pids.get(i);
+ synchronized (mAm.mPidsSelfLocked) {
+ pr = mAm.mPidsSelfLocked.get(blocked);
+ }
+ if (pr != null && pr.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
+ Slog.d(TAG_AM, app.processName + " (" + pid + ") blocks "
+ + pr.processName + " (" + blocked + ")");
+ // Found at least one blocked non-cached process
+ unfreezeAppLSP(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ break;
+ }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ForegroundServiceDelegation.java b/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
new file mode 100644
index 000000000000..a051d174e1a5
--- /dev/null
+++ b/services/core/java/com/android/server/am/ForegroundServiceDelegation.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+
+/**
+ * A foreground service delegate which has client options and connection callback.
+ */
+public class ForegroundServiceDelegation {
+ public final IBinder mBinder = new Binder();
+ @NonNull
+ public final ForegroundServiceDelegationOptions mOptions;
+ @Nullable
+ public final ServiceConnection mConnection;
+
+ public ForegroundServiceDelegation(@NonNull ForegroundServiceDelegationOptions options,
+ @Nullable ServiceConnection connection) {
+ mOptions = options;
+ mConnection = connection;
+ }
+}
diff --git a/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java b/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java
new file mode 100644
index 000000000000..5eb5a55d2c77
--- /dev/null
+++ b/services/core/java/com/android/server/am/ForegroundServiceDelegationOptions.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.IApplicationThread;
+import android.content.ComponentName;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A service module such as MediaSessionService, VOIP, Camera, Microphone, Location can ask
+ * ActivityManagerService to start a foreground service delegate on behalf of the actual app,
+ * by which the client app's process state can be promoted to FOREGROUND_SERVICE process state which
+ * is higher than the app's actual process state if the app is in the background. This can help to
+ * keep the app in the memory and extra run-time.
+ * The app does not need to define an actual service component nor add it into manifest file.
+ */
+public class ForegroundServiceDelegationOptions {
+
+ public static final int DELEGATION_SERVICE_DEFAULT = 0;
+ public static final int DELEGATION_SERVICE_DATA_SYNC = 1;
+ public static final int DELEGATION_SERVICE_MEDIA_PLAYBACK = 2;
+ public static final int DELEGATION_SERVICE_PHONE_CALL = 3;
+ public static final int DELEGATION_SERVICE_LOCATION = 4;
+ public static final int DELEGATION_SERVICE_CONNECTED_DEVICE = 5;
+ public static final int DELEGATION_SERVICE_MEDIA_PROJECTION = 6;
+ public static final int DELEGATION_SERVICE_CAMERA = 7;
+ public static final int DELEGATION_SERVICE_MICROPHONE = 8;
+ public static final int DELEGATION_SERVICE_HEALTH = 9;
+ public static final int DELEGATION_SERVICE_REMOTE_MESSAGING = 10;
+ public static final int DELEGATION_SERVICE_SYSTEM_EXEMPTED = 11;
+ public static final int DELEGATION_SERVICE_SPECIAL_USE = 12;
+
+ @IntDef(flag = false, prefix = { "DELEGATION_SERVICE_" }, value = {
+ DELEGATION_SERVICE_DEFAULT,
+ DELEGATION_SERVICE_DATA_SYNC,
+ DELEGATION_SERVICE_MEDIA_PLAYBACK,
+ DELEGATION_SERVICE_PHONE_CALL,
+ DELEGATION_SERVICE_LOCATION,
+ DELEGATION_SERVICE_CONNECTED_DEVICE,
+ DELEGATION_SERVICE_MEDIA_PROJECTION,
+ DELEGATION_SERVICE_CAMERA,
+ DELEGATION_SERVICE_MICROPHONE,
+ DELEGATION_SERVICE_HEALTH,
+ DELEGATION_SERVICE_REMOTE_MESSAGING,
+ DELEGATION_SERVICE_SYSTEM_EXEMPTED,
+ DELEGATION_SERVICE_SPECIAL_USE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DelegationService {}
+
+ // The actual app's PID
+ public final int mClientPid;
+ // The actual app's UID
+ public final int mClientUid;
+ // The actual app's package name
+ @NonNull
+ public final String mClientPackageName;
+ // The actual app's app thread
+ @Nullable
+ public final IApplicationThread mClientAppThread;
+ public final boolean mSticky; // Is it a sticky service
+
+ // The delegation service's instance name which is to identify the delegate.
+ @NonNull
+ public String mClientInstanceName;
+ // The foreground service types it consists of.
+ public final int mForegroundServiceTypes;
+ /**
+ * The service's name such as MediaSessionService, VOIP, Camera, Microphone, Location. This is
+ * the internal module's name which actually starts the FGS delegate on behalf of the client
+ * app.
+ */
+ public final @DelegationService int mDelegationService;
+
+ public ForegroundServiceDelegationOptions(int clientPid,
+ int clientUid,
+ @NonNull String clientPackageName,
+ @NonNull IApplicationThread clientAppThread,
+ boolean isSticky,
+ @NonNull String clientInstanceName,
+ int foregroundServiceTypes,
+ @DelegationService int delegationService) {
+ mClientPid = clientPid;
+ mClientUid = clientUid;
+ mClientPackageName = clientPackageName;
+ mClientAppThread = clientAppThread;
+ mSticky = isSticky;
+ mClientInstanceName = clientInstanceName;
+ mForegroundServiceTypes = foregroundServiceTypes;
+ mDelegationService = delegationService;
+ }
+
+ /**
+ * A service delegates a foreground service state to a clientUID using a instanceName.
+ * This delegation is uniquely identified by
+ * mDelegationService/mClientUid/mClientPid/mClientInstanceName
+ */
+ public boolean isSameDelegate(ForegroundServiceDelegationOptions that) {
+ return this.mDelegationService == that.mDelegationService
+ && this.mClientUid == that.mClientUid
+ && this.mClientPid == that.mClientPid
+ && this.mClientInstanceName.equals(that.mClientInstanceName);
+ }
+
+ /**
+ * Construct a component name for this delegate.
+ */
+ public ComponentName getComponentName() {
+ return new ComponentName(mClientPackageName, serviceCodeToString(mDelegationService)
+ + ":" + mClientInstanceName);
+ }
+
+ /**
+ * Get string description of this delegate options.
+ */
+ public String getDescription() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ForegroundServiceDelegate{")
+ .append("package:")
+ .append(mClientPackageName)
+ .append(",")
+ .append("service:")
+ .append(serviceCodeToString(mDelegationService))
+ .append(",")
+ .append("uid:")
+ .append(mClientUid)
+ .append(",")
+ .append("pid:")
+ .append(mClientPid)
+ .append(",")
+ .append("instance:")
+ .append(mClientInstanceName)
+ .append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Map the integer service code to string name.
+ * @param serviceCode
+ * @return
+ */
+ public static String serviceCodeToString(@DelegationService int serviceCode) {
+ switch (serviceCode) {
+ case DELEGATION_SERVICE_DEFAULT:
+ return "DEFAULT";
+ case DELEGATION_SERVICE_DATA_SYNC:
+ return "DATA_SYNC";
+ case DELEGATION_SERVICE_MEDIA_PLAYBACK:
+ return "MEDIA_PLAYBACK";
+ case DELEGATION_SERVICE_PHONE_CALL:
+ return "PHONE_CALL";
+ case DELEGATION_SERVICE_LOCATION:
+ return "LOCATION";
+ case DELEGATION_SERVICE_CONNECTED_DEVICE:
+ return "CONNECTED_DEVICE";
+ case DELEGATION_SERVICE_MEDIA_PROJECTION:
+ return "MEDIA_PROJECTION";
+ case DELEGATION_SERVICE_CAMERA:
+ return "CAMERA";
+ case DELEGATION_SERVICE_MICROPHONE:
+ return "MICROPHONE";
+ case DELEGATION_SERVICE_HEALTH:
+ return "HEALTH";
+ case DELEGATION_SERVICE_REMOTE_MESSAGING:
+ return "REMOTE_MESSAGING";
+ case DELEGATION_SERVICE_SYSTEM_EXEMPTED:
+ return "SYSTEM_EXEMPTED";
+ case DELEGATION_SERVICE_SPECIAL_USE:
+ return "SPECIAL_USE";
+ default:
+ return "(unknown:" + serviceCode + ")";
+ }
+ }
+
+ public static class Builder {
+ int mClientPid; // The actual app PID
+ int mClientUid; // The actual app UID
+ String mClientPackageName; // The actual app's package name
+ int mClientNotificationId; // The actual app's notification
+ IApplicationThread mClientAppThread; // The actual app's app thread
+ boolean mSticky; // Is it a sticky service
+ String mClientInstanceName; // The delegation service instance name
+ int mForegroundServiceTypes; // The foreground service types it consists of
+ @DelegationService int mDelegationService; // The internal service's name, i.e. VOIP
+
+ public Builder setClientPid(int clientPid) {
+ mClientPid = clientPid;
+ return this;
+ }
+
+ public Builder setClientUid(int clientUid) {
+ mClientUid = clientUid;
+ return this;
+ }
+
+ public Builder setClientPackageName(@NonNull String clientPackageName) {
+ mClientPackageName = clientPackageName;
+ return this;
+ }
+
+ public Builder setClientNotificationId(int clientNotificationId) {
+ mClientNotificationId = clientNotificationId;
+ return this;
+ }
+
+ public Builder setClientAppThread(@NonNull IApplicationThread clientAppThread) {
+ mClientAppThread = clientAppThread;
+ return this;
+ }
+
+ public Builder setClientInstanceName(@NonNull String clientInstanceName) {
+ mClientInstanceName = clientInstanceName;
+ return this;
+ }
+
+ public Builder setSticky(boolean isSticky) {
+ mSticky = isSticky;
+ return this;
+ }
+
+ public Builder setForegroundServiceTypes(int foregroundServiceTypes) {
+ mForegroundServiceTypes = foregroundServiceTypes;
+ return this;
+ }
+
+ public Builder setDelegationService(@DelegationService int delegationService) {
+ mDelegationService = delegationService;
+ return this;
+ }
+
+ public ForegroundServiceDelegationOptions build() {
+ return new ForegroundServiceDelegationOptions(mClientPid,
+ mClientUid,
+ mClientPackageName,
+ mClientAppThread,
+ mSticky,
+ mClientInstanceName,
+ mForegroundServiceTypes,
+ mDelegationService
+ );
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 4b82ad863c8e..c27ed7a46844 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -206,6 +206,10 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
// Last time mAllowWhileInUsePermissionInFgs or mAllowStartForeground is set.
long mLastSetFgsRestrictionTime;
+ // This is a service record of a FGS delegate (not a service record of a real service)
+ boolean mIsFgsDelegate;
+ @Nullable ForegroundServiceDelegation mFgsDelegation;
+
String stringName; // caching of toString
private int lastStartId; // identifier of most recent start request.
@@ -233,6 +237,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
final boolean taskRemoved;
final int id;
final int callingId;
+ final String mCallingProcessName;
final Intent intent;
final NeededUriGrants neededGrants;
long deliveredTime;
@@ -242,14 +247,16 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
String stringName; // caching of toString
- StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id, Intent _intent,
- NeededUriGrants _neededGrants, int _callingId) {
+ StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id,
+ Intent _intent, NeededUriGrants _neededGrants, int _callingId,
+ String callingProcessName) {
sr = _sr;
taskRemoved = _taskRemoved;
id = _id;
intent = _intent;
neededGrants = _neededGrants;
callingId = _callingId;
+ mCallingProcessName = callingProcessName;
}
UriPermissionOwner getUriPermissionsLocked() {
@@ -502,6 +509,9 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
pw.print(" foregroundId="); pw.print(foregroundId);
pw.print(" foregroundNoti="); pw.println(foregroundNoti);
}
+ if (mIsFgsDelegate) {
+ pw.print(prefix); pw.print("isFgsDelegate="); pw.println(mIsFgsDelegate);
+ }
pw.print(prefix); pw.print("createTime=");
TimeUtils.formatDuration(createRealTime, nowReal, pw);
pw.print(" startingBgTimeout=");
@@ -634,7 +644,9 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
serviceInfo.applicationInfo.uid,
serviceInfo.applicationInfo.longVersionCode,
serviceInfo.processName, serviceInfo.name);
- tracker.applyNewOwner(this);
+ if (tracker != null) {
+ tracker.applyNewOwner(this);
+ }
}
return tracker;
}
diff --git a/services/core/java/com/android/server/biometrics/TEST_MAPPING b/services/core/java/com/android/server/biometrics/TEST_MAPPING
index 36acc3c7344d..8b80674070a4 100644
--- a/services/core/java/com/android/server/biometrics/TEST_MAPPING
+++ b/services/core/java/com/android/server/biometrics/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "CtsBiometricsTestCases"
+ },
+ {
+ "name": "CtsBiometricsHostTestCases"
}
]
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index da4361843681..d584c99cea72 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -120,7 +120,7 @@ final class ALSProbe implements Probe {
// if a final consumer is set it will call destroy/disable on the next value if requested
if (!mDestroyed && mNextConsumer == null) {
- disableLightSensorLoggingLocked();
+ disableLightSensorLoggingLocked(false /* destroying */);
}
}
@@ -130,7 +130,7 @@ final class ALSProbe implements Probe {
// if a final consumer is set it will call destroy/disable on the next value if requested
if (!mDestroyed && mNextConsumer == null) {
- disableLightSensorLoggingLocked();
+ disableLightSensorLoggingLocked(true /* destroying */);
mDestroyed = true;
}
}
@@ -177,11 +177,10 @@ final class ALSProbe implements Probe {
final float current = mLastAmbientLux;
if (current > -1f) {
nextConsumer.consume(current);
- } else if (mDestroyed) {
- nextConsumer.consume(-1f);
} else if (mNextConsumer != null) {
mNextConsumer.add(nextConsumer);
} else {
+ mDestroyed = false;
mNextConsumer = nextConsumer;
enableLightSensorLoggingLocked();
}
@@ -199,12 +198,14 @@ final class ALSProbe implements Probe {
resetTimerLocked(true /* start */);
}
- private void disableLightSensorLoggingLocked() {
+ private void disableLightSensorLoggingLocked(boolean destroying) {
resetTimerLocked(false /* start */);
if (mEnabled) {
mEnabled = false;
- mLastAmbientLux = -1;
+ if (!destroying) {
+ mLastAmbientLux = -1;
+ }
mSensorManager.unregisterListener(mLightSensorListener);
Slog.v(TAG, "Disable ALS: " + mLightSensorListener.hashCode());
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index 7a13c91a2cf1..d11f0991a5d4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -22,6 +22,7 @@ import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.face.AuthenticationFrame;
import android.hardware.biometrics.face.BaseFrame;
+import android.hardware.biometrics.face.EnrollmentFrame;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticationFrame;
import android.hardware.face.FaceEnrollFrame;
@@ -33,6 +34,7 @@ import android.util.Slog;
import com.android.server.biometrics.HardwareAuthTokenUtils;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
+import com.android.server.biometrics.sensors.EnrollClient;
import com.android.server.biometrics.sensors.face.FaceUtils;
import java.util.HashSet;
@@ -200,22 +202,24 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
- // TODO(b/178414967): replace with notifyAuthenticationFrame and notifyEnrollmentFrame.
@Override
public void notifyAcquired(int userId, int acquireInfo) {
-
super.notifyAcquired_enforcePermission();
BaseFrame data = new BaseFrame();
data.acquiredInfo = (byte) acquireInfo;
- AuthenticationFrame authenticationFrame = new AuthenticationFrame();
- authenticationFrame.data = data;
-
- // TODO(b/178414967): Currently onAuthenticationFrame and onEnrollmentFrame are the same.
- // This will need to call the correct callback once the onAcquired callback is removed.
- mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFrame(
- authenticationFrame);
+ if (mSensor.getScheduler().getCurrentClient() instanceof EnrollClient) {
+ final EnrollmentFrame frame = new EnrollmentFrame();
+ frame.data = data;
+ mSensor.getSessionForUser(userId).getHalSessionCallback()
+ .onEnrollmentFrame(frame);
+ } else {
+ final AuthenticationFrame frame = new AuthenticationFrame();
+ frame.data = data;
+ mSensor.getSessionForUser(userId).getHalSessionCallback()
+ .onAuthenticationFrame(frame);
+ }
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 81b56a310738..d2e572f2f979 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -18,6 +18,7 @@ package com.android.server.companion.virtual;
import android.annotation.NonNull;
import android.companion.virtual.IVirtualDevice;
+import android.companion.virtual.VirtualDeviceParams;
import java.util.Set;
@@ -109,4 +110,14 @@ public abstract class VirtualDeviceManagerInternal {
* Returns true if the {@code displayId} is owned by any virtual device
*/
public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId);
+
+ /**
+ * Returns the device policy for the given virtual device and policy type.
+ *
+ * <p>In case the virtual device identifier is not valid, or there's no explicitly specified
+ * policy for that device and policy type, then
+ * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
+ */
+ public abstract @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
+ int deviceId, @VirtualDeviceParams.PolicyType int policyType);
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 0741d46b6f29..bc9bc031ca35 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -645,7 +645,8 @@ public class Vpn {
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
- .setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE, null))
+ .setTransportInfo(new VpnTransportInfo(
+ VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
.build();
loadAlwaysOnPackage();
@@ -709,7 +710,8 @@ public class Vpn {
private void resetNetworkCapabilities() {
mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
.setUids(null)
- .setTransportInfo(new VpnTransportInfo(VpnManager.TYPE_VPN_NONE, null))
+ .setTransportInfo(new VpnTransportInfo(
+ VpnManager.TYPE_VPN_NONE, null /* sessionId */, false /* bypassable */))
.build();
}
@@ -1567,7 +1569,8 @@ public class Vpn {
capsBuilder.setUids(createUserAndRestrictedProfilesRanges(mUserId,
mConfig.allowedApplications, mConfig.disallowedApplications));
- capsBuilder.setTransportInfo(new VpnTransportInfo(getActiveVpnType(), mConfig.session));
+ capsBuilder.setTransportInfo(
+ new VpnTransportInfo(getActiveVpnType(), mConfig.session, mConfig.allowBypass));
// Only apps targeting Q and above can explicitly declare themselves as metered.
// These VPNs are assumed metered unless they state otherwise.
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 838bb53f0b86..d6f0fd070f94 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1703,6 +1703,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mTempBrightnessEvent.setRbcStrength(mCdsi != null
? mCdsi.getReduceBrightColorsStrength() : -1);
mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+ mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -1713,12 +1714,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
|| brightnessAdjustmentFlags != 0) {
float lastBrightness = mLastBrightnessEvent.getBrightness();
mTempBrightnessEvent.setInitialBrightness(lastBrightness);
- mTempBrightnessEvent.setFastAmbientLux(
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
- mTempBrightnessEvent.setSlowAmbientLux(
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -2846,9 +2841,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
convertToNits(event.getInitialBrightness()),
convertToNits(event.getBrightness()),
- event.getSlowAmbientLux(),
+ event.getLux(),
event.getPhysicalDisplayId(),
- event.isShortTermModelActive(),
+ event.wasShortTermModelActive(),
appliedLowPowerMode,
appliedRbcStrength,
appliedHbmMaxNits,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index bf0b388fdb56..300b5895ca98 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -1568,6 +1568,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mTempBrightnessEvent.setRbcStrength(mCdsi != null
? mCdsi.getReduceBrightColorsStrength() : -1);
mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
+ mTempBrightnessEvent.setWasShortTermModelActive(hadUserBrightnessPoint);
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -1578,12 +1579,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
|| brightnessAdjustmentFlags != 0) {
float lastBrightness = mLastBrightnessEvent.getBrightness();
mTempBrightnessEvent.setInitialBrightness(lastBrightness);
- mTempBrightnessEvent.setFastAmbientLux(
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getFastAmbientLux());
- mTempBrightnessEvent.setSlowAmbientLux(
- mAutomaticBrightnessController == null
- ? -1f : mAutomaticBrightnessController.getSlowAmbientLux());
mTempBrightnessEvent.setAutomaticBrightnessEnabled(mPowerRequest.useAutoBrightness);
mLastBrightnessEvent.copyFrom(mTempBrightnessEvent);
BrightnessEvent newEvent = new BrightnessEvent(mTempBrightnessEvent);
@@ -2517,9 +2512,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
convertToNits(event.getInitialBrightness()),
convertToNits(event.getBrightness()),
- event.getSlowAmbientLux(),
+ event.getLux(),
event.getPhysicalDisplayId(),
- event.isShortTermModelActive(),
+ event.wasShortTermModelActive(),
appliedLowPowerMode,
appliedRbcStrength,
appliedHbmMaxNits,
@@ -2694,7 +2689,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
int displayId, SensorManager sensorManager) {
return new DisplayPowerProximityStateController(wakelockController, displayDeviceConfig,
looper, nudgeUpdatePowerState,
- displayId, sensorManager);
+ displayId, sensorManager, /* injector= */ null);
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
index 5b64dd5ff319..a3433d9570a4 100644
--- a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
@@ -30,6 +30,7 @@ import android.util.TimeUtils;
import android.view.Display;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.utils.SensorUtils;
import java.io.PrintWriter;
@@ -40,16 +41,22 @@ import java.io.PrintWriter;
* state changes.
*/
public final class DisplayPowerProximityStateController {
- private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1;
+ @VisibleForTesting
+ static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1;
+ @VisibleForTesting
+ static final int PROXIMITY_UNKNOWN = -1;
+ @VisibleForTesting
+ static final int PROXIMITY_POSITIVE = 1;
+ @VisibleForTesting
+ static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
+
private static final int MSG_IGNORE_PROXIMITY = 2;
- private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
- private static final int PROXIMITY_POSITIVE = 1;
private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
// Proximity sensor debounce delay in milliseconds for positive transitions.
- private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
+
// Proximity sensor debounce delay in milliseconds for negative transitions.
private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
// Trigger proximity if distance is less than 5 cm.
@@ -66,12 +73,13 @@ public final class DisplayPowerProximityStateController {
private final DisplayPowerProximityStateHandler mHandler;
// A runnable to execute the utility to update the power state.
private final Runnable mNudgeUpdatePowerState;
+ private Clock mClock;
// A listener which listen's to the events emitted by the proximity sensor.
private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
if (mProximitySensorEnabled) {
- final long time = SystemClock.uptimeMillis();
+ final long time = mClock.uptimeMillis();
final float distance = event.values[0];
boolean positive = distance >= 0.0f && distance < mProximityThreshold;
handleProximitySensorEvent(time, positive);
@@ -147,7 +155,12 @@ public final class DisplayPowerProximityStateController {
public DisplayPowerProximityStateController(
WakelockController wakeLockController, DisplayDeviceConfig displayDeviceConfig,
Looper looper,
- Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager) {
+ Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager,
+ Injector injector) {
+ if (injector == null) {
+ injector = new Injector();
+ }
+ mClock = injector.createClock();
mWakelockController = wakeLockController;
mHandler = new DisplayPowerProximityStateHandler(looper);
mNudgeUpdatePowerState = nudgeUpdatePowerState;
@@ -239,7 +252,6 @@ public final class DisplayPowerProximityStateController {
setProximitySensorEnabled(false);
mWaitingForNegativeProximity = false;
}
-
if (mScreenOffBecauseOfProximity
&& (mProximity != PROXIMITY_POSITIVE || mIgnoreProximityUntilChanged)) {
// The screen *was* off due to prox being near, but now it's "far" so lets turn
@@ -313,7 +325,7 @@ public final class DisplayPowerProximityStateController {
+ mSkipRampBecauseOfProximityChangeToNegative);
}
- private void ignoreProximitySensorUntilChangedInternal() {
+ void ignoreProximitySensorUntilChangedInternal() {
if (!mIgnoreProximityUntilChanged
&& mProximity == PROXIMITY_POSITIVE) {
// Only ignore if it is still reporting positive (near)
@@ -414,7 +426,7 @@ public final class DisplayPowerProximityStateController {
if (mProximitySensorEnabled
&& mPendingProximity != PROXIMITY_UNKNOWN
&& mPendingProximityDebounceTime >= 0) {
- final long now = SystemClock.uptimeMillis();
+ final long now = mClock.uptimeMillis();
if (mPendingProximityDebounceTime <= now) {
if (mProximity != mPendingProximity) {
// if the status of the sensor changed, stop ignoring.
@@ -473,4 +485,66 @@ public final class DisplayPowerProximityStateController {
}
}
+ @VisibleForTesting
+ boolean getPendingWaitForNegativeProximityLocked() {
+ synchronized (mLock) {
+ return mPendingWaitForNegativeProximityLocked;
+ }
+ }
+
+ @VisibleForTesting
+ boolean getWaitingForNegativeProximity() {
+ return mWaitingForNegativeProximity;
+ }
+
+ @VisibleForTesting
+ boolean shouldIgnoreProximityUntilChanged() {
+ return mIgnoreProximityUntilChanged;
+ }
+
+ boolean isProximitySensorEnabled() {
+ return mProximitySensorEnabled;
+ }
+
+ @VisibleForTesting
+ Handler getHandler() {
+ return mHandler;
+ }
+
+ @VisibleForTesting
+ int getPendingProximity() {
+ return mPendingProximity;
+ }
+
+ @VisibleForTesting
+ int getProximity() {
+ return mProximity;
+ }
+
+
+ @VisibleForTesting
+ long getPendingProximityDebounceTime() {
+ return mPendingProximityDebounceTime;
+ }
+
+ @VisibleForTesting
+ SensorEventListener getProximitySensorListener() {
+ return mProximitySensorListener;
+ }
+
+ /** Functional interface for providing time. */
+ @VisibleForTesting
+ interface Clock {
+ /**
+ * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
+ */
+ long uptimeMillis();
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ Clock createClock() {
+ return () -> SystemClock.uptimeMillis();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index e3fa6220edf4..f19852b3eff5 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -39,8 +39,6 @@ public final class BrightnessEvent {
private String mPhysicalDisplayId;
private long mTime;
private float mLux;
- private float mFastAmbientLux;
- private float mSlowAmbientLux;
private float mPreThresholdLux;
private float mInitialBrightness;
private float mBrightness;
@@ -51,6 +49,7 @@ public final class BrightnessEvent {
private int mRbcStrength;
private float mThermalMax;
private float mPowerFactor;
+ private boolean mWasShortTermModelActive;
private int mFlags;
private int mAdjustmentFlags;
private boolean mAutomaticBrightnessEnabled;
@@ -76,8 +75,6 @@ public final class BrightnessEvent {
mTime = that.getTime();
// Lux values
mLux = that.getLux();
- mFastAmbientLux = that.getFastAmbientLux();
- mSlowAmbientLux = that.getSlowAmbientLux();
mPreThresholdLux = that.getPreThresholdLux();
// Brightness values
mInitialBrightness = that.getInitialBrightness();
@@ -90,6 +87,7 @@ public final class BrightnessEvent {
mRbcStrength = that.getRbcStrength();
mThermalMax = that.getThermalMax();
mPowerFactor = that.getPowerFactor();
+ mWasShortTermModelActive = that.wasShortTermModelActive();
mFlags = that.getFlags();
mAdjustmentFlags = that.getAdjustmentFlags();
// Auto-brightness setting
@@ -105,8 +103,6 @@ public final class BrightnessEvent {
mPhysicalDisplayId = "";
// Lux values
mLux = 0;
- mFastAmbientLux = 0;
- mSlowAmbientLux = 0;
mPreThresholdLux = 0;
// Brightness values
mInitialBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -119,6 +115,7 @@ public final class BrightnessEvent {
mRbcStrength = 0;
mThermalMax = PowerManager.BRIGHTNESS_MAX;
mPowerFactor = 1f;
+ mWasShortTermModelActive = false;
mFlags = 0;
mAdjustmentFlags = 0;
// Auto-brightness setting
@@ -140,10 +137,6 @@ public final class BrightnessEvent {
&& mDisplayId == that.mDisplayId
&& mPhysicalDisplayId.equals(that.mPhysicalDisplayId)
&& Float.floatToRawIntBits(mLux) == Float.floatToRawIntBits(that.mLux)
- && Float.floatToRawIntBits(mFastAmbientLux)
- == Float.floatToRawIntBits(that.mFastAmbientLux)
- && Float.floatToRawIntBits(mSlowAmbientLux)
- == Float.floatToRawIntBits(that.mSlowAmbientLux)
&& Float.floatToRawIntBits(mPreThresholdLux)
== Float.floatToRawIntBits(that.mPreThresholdLux)
&& Float.floatToRawIntBits(mInitialBrightness)
@@ -161,6 +154,7 @@ public final class BrightnessEvent {
== Float.floatToRawIntBits(that.mThermalMax)
&& Float.floatToRawIntBits(mPowerFactor)
== Float.floatToRawIntBits(that.mPowerFactor)
+ && mWasShortTermModelActive == that.mWasShortTermModelActive
&& mFlags == that.mFlags
&& mAdjustmentFlags == that.mAdjustmentFlags
&& mAutomaticBrightnessEnabled == that.mAutomaticBrightnessEnabled;
@@ -182,14 +176,13 @@ public final class BrightnessEvent {
+ ", rcmdBrt=" + mRecommendedBrightness
+ ", preBrt=" + mPreThresholdBrightness
+ ", lux=" + mLux
- + ", fastLux=" + mFastAmbientLux
- + ", slowLux=" + mSlowAmbientLux
+ ", preLux=" + mPreThresholdLux
+ ", hbmMax=" + mHbmMax
+ ", hbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
+ ", rbcStrength=" + mRbcStrength
+ ", thrmMax=" + mThermalMax
+ ", powerFactor=" + mPowerFactor
+ + ", wasShortTermModelActive=" + mWasShortTermModelActive
+ ", flags=" + flagsToString()
+ ", reason=" + mReason.toString(mAdjustmentFlags)
+ ", autoBrightness=" + mAutomaticBrightnessEnabled;
@@ -240,22 +233,6 @@ public final class BrightnessEvent {
this.mLux = lux;
}
- public float getFastAmbientLux() {
- return mFastAmbientLux;
- }
-
- public void setFastAmbientLux(float mFastAmbientLux) {
- this.mFastAmbientLux = mFastAmbientLux;
- }
-
- public float getSlowAmbientLux() {
- return mSlowAmbientLux;
- }
-
- public void setSlowAmbientLux(float mSlowAmbientLux) {
- this.mSlowAmbientLux = mSlowAmbientLux;
- }
-
public float getPreThresholdLux() {
return mPreThresholdLux;
}
@@ -344,6 +321,20 @@ public final class BrightnessEvent {
return (mFlags & FLAG_LOW_POWER_MODE) != 0;
}
+ /**
+ * Set whether the short term model was active before the brightness event.
+ */
+ public boolean setWasShortTermModelActive(boolean wasShortTermModelActive) {
+ return this.mWasShortTermModelActive = wasShortTermModelActive;
+ }
+
+ /**
+ * Returns whether the short term model was active before the brightness event.
+ */
+ public boolean wasShortTermModelActive() {
+ return this.mWasShortTermModelActive;
+ }
+
public int getFlags() {
return mFlags;
}
@@ -352,10 +343,6 @@ public final class BrightnessEvent {
this.mFlags = flags;
}
- public boolean isShortTermModelActive() {
- return (mFlags & FLAG_USER_SET) != 0;
- }
-
public int getAdjustmentFlags() {
return mAdjustmentFlags;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 9bce471fd0cb..8a22ab968165 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -837,7 +837,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
void enableAudioReturnChannel(boolean enabled) {
assertRunOnServiceThread();
HdmiDeviceInfo avr = getAvrDeviceInfo();
- if (avr != null) {
+ if (avr != null && avr.getPortId() != Constants.INVALID_PORT_ID) {
mService.enableAudioReturnChannel(avr.getPortId(), enabled);
}
}
@@ -1336,19 +1336,31 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
}
@ServiceThreadOnly
+ private void forceDisableArcOnAllPins() {
+ List<HdmiPortInfo> ports = mService.getPortInfo();
+ for (HdmiPortInfo port : ports) {
+ if (isArcFeatureEnabled(port.getId())) {
+ mService.enableAudioReturnChannel(port.getId(), false);
+ }
+ }
+ }
+
+ @ServiceThreadOnly
private void disableArcIfExist() {
assertRunOnServiceThread();
HdmiDeviceInfo avr = getAvrDeviceInfo();
if (avr == null) {
return;
}
- disableArc();
// Seq #44.
removeAllRunningArcAction();
if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) {
addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress()));
}
+
+ // Disable ARC Pin earlier, prevent the case where AVR doesn't send <Terminate ARC> in time
+ forceDisableArcOnAllPins();
}
@ServiceThreadOnly
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index d02faad1956e..25e71e8ceca1 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -2081,9 +2081,11 @@ public class LockSettingsService extends ILockSettings.Stub {
public VerifyCredentialResponse checkCredential(LockscreenCredential credential, int userId,
ICheckCredentialProgressCallback progressCallback) {
checkPasswordReadPermission();
+ final long identity = Binder.clearCallingIdentity();
try {
return doVerifyCredential(credential, userId, progressCallback, 0 /* flags */);
} finally {
+ Binder.restoreCallingIdentity(identity);
scheduleGc();
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 90135ad7684b..d6b9bd5d3114 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3797,13 +3797,13 @@ public class NotificationManagerService extends SystemService {
}
private void createNotificationChannelsImpl(String pkg, int uid,
- ParceledListSlice channelsList) {
- createNotificationChannelsImpl(pkg, uid, channelsList,
+ ParceledListSlice channelsList, boolean fromTargetApp) {
+ createNotificationChannelsImpl(pkg, uid, channelsList, fromTargetApp,
ActivityTaskManager.INVALID_TASK_ID);
}
private void createNotificationChannelsImpl(String pkg, int uid,
- ParceledListSlice channelsList, int startingTaskId) {
+ ParceledListSlice channelsList, boolean fromTargetApp, int startingTaskId) {
List<NotificationChannel> channels = channelsList.getList();
final int channelsSize = channels.size();
ParceledListSlice<NotificationChannel> oldChannels =
@@ -3815,7 +3815,7 @@ public class NotificationManagerService extends SystemService {
final NotificationChannel channel = channels.get(i);
Objects.requireNonNull(channel, "channel in list is null");
needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(pkg, uid,
- channel, true /* fromTargetApp */,
+ channel, fromTargetApp,
mConditionProviders.isPackageOrComponentAllowed(
pkg, UserHandle.getUserId(uid)));
if (needsPolicyFileChange) {
@@ -3851,6 +3851,7 @@ public class NotificationManagerService extends SystemService {
@Override
public void createNotificationChannels(String pkg, ParceledListSlice channelsList) {
checkCallerIsSystemOrSameApp(pkg);
+ boolean fromTargetApp = !isCallerSystemOrPhone(); // if not system, it's from the app
int taskId = ActivityTaskManager.INVALID_TASK_ID;
try {
int uid = mPackageManager.getPackageUid(pkg, 0,
@@ -3859,14 +3860,15 @@ public class NotificationManagerService extends SystemService {
} catch (RemoteException e) {
// Do nothing
}
- createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, taskId);
+ createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList, fromTargetApp,
+ taskId);
}
@Override
public void createNotificationChannelsForPackage(String pkg, int uid,
ParceledListSlice channelsList) {
enforceSystemOrSystemUI("only system can call this");
- createNotificationChannelsImpl(pkg, uid, channelsList);
+ createNotificationChannelsImpl(pkg, uid, channelsList, false /* fromTargetApp */);
}
@Override
@@ -3881,7 +3883,8 @@ public class NotificationManagerService extends SystemService {
CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
conversationChannel.setConversationId(parentId, conversationId);
createNotificationChannelsImpl(
- pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
+ pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)),
+ false /* fromTargetApp */);
mRankingHandler.requestSort();
handleSavePolicyFile();
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1bbcc839ccd1..444fef634de3 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -918,7 +918,7 @@ public class PreferencesHelper implements RankingConfig {
throw new IllegalArgumentException("Reserved id");
}
NotificationChannel existing = r.channels.get(channel.getId());
- if (existing != null && fromTargetApp) {
+ if (existing != null) {
// Actually modifying an existing channel - keep most of the existing settings
if (existing.isDeleted()) {
// The existing channel was deleted - undelete it.
@@ -1004,9 +1004,7 @@ public class PreferencesHelper implements RankingConfig {
}
if (fromTargetApp) {
channel.setLockscreenVisibility(r.visibility);
- channel.setAllowBubbles(existing != null
- ? existing.getAllowBubbles()
- : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
+ channel.setAllowBubbles(NotificationChannel.DEFAULT_ALLOW_BUBBLE);
}
clearLockedFieldsLocked(channel);
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index c97711b3aa80..5b837f1daa6f 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -81,11 +81,8 @@ import com.android.server.utils.Watcher;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
/**
* Implementation of the methods that update the internal structures of AppsFilter. Because of the
@@ -113,7 +110,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
*/
@GuardedBy("mQueryableViaUsesPermissionLock")
@NonNull
- private HashMap<String, Set<Integer>> mPermissionToUids;
+ private final ArrayMap<String, ArraySet<Integer>> mPermissionToUids;
/**
* A cache that maps parsed {@link android.R.styleable#AndroidManifestUsesPermission
@@ -123,7 +120,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
*/
@GuardedBy("mQueryableViaUsesPermissionLock")
@NonNull
- private HashMap<String, Set<Integer>> mUsesPermissionToUids;
+ private final ArrayMap<String, ArraySet<Integer>> mUsesPermissionToUids;
/**
* Ensures an observer is in the list, exactly once. The observer cannot be null. The
@@ -225,8 +222,8 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
mProtectedBroadcasts = new WatchedArraySet<>();
mProtectedBroadcastsSnapshot = new SnapshotCache.Auto<>(
mProtectedBroadcasts, mProtectedBroadcasts, "AppsFilter.mProtectedBroadcasts");
- mPermissionToUids = new HashMap<>();
- mUsesPermissionToUids = new HashMap<>();
+ mPermissionToUids = new ArrayMap<>();
+ mUsesPermissionToUids = new ArrayMap<>();
mSnapshot = new SnapshotCache<AppsFilterSnapshot>(this, this) {
@Override
@@ -609,7 +606,10 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
// Lookup in the mPermissionToUids cache if installed packages have
// defined this permission.
if (mPermissionToUids.containsKey(usesPermissionName)) {
- for (int targetAppId : mPermissionToUids.get(usesPermissionName)) {
+ final ArraySet<Integer> permissionDefiners =
+ mPermissionToUids.get(usesPermissionName);
+ for (int j = 0; j < permissionDefiners.size(); j++) {
+ final int targetAppId = permissionDefiners.valueAt(j);
if (targetAppId != newPkgSetting.getAppId()) {
mQueryableViaUsesPermission.add(newPkgSetting.getAppId(),
targetAppId);
@@ -619,7 +619,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
// Record in mUsesPermissionToUids that a permission was requested
// by a new package
if (!mUsesPermissionToUids.containsKey(usesPermissionName)) {
- mUsesPermissionToUids.put(usesPermissionName, new HashSet<>());
+ mUsesPermissionToUids.put(usesPermissionName, new ArraySet<>());
}
mUsesPermissionToUids.get(usesPermissionName).add(newPkgSetting.getAppId());
}
@@ -633,7 +633,10 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
// Lookup in the mUsesPermissionToUids cache if installed packages have
// requested this permission.
if (mUsesPermissionToUids.containsKey(permissionName)) {
- for (int queryingAppId : mUsesPermissionToUids.get(permissionName)) {
+ final ArraySet<Integer> permissionUsers = mUsesPermissionToUids.get(
+ permissionName);
+ for (int j = 0; j < permissionUsers.size(); j++) {
+ final int queryingAppId = permissionUsers.valueAt(j);
if (queryingAppId != newPkgSetting.getAppId()) {
mQueryableViaUsesPermission.add(queryingAppId,
newPkgSetting.getAppId());
@@ -642,7 +645,7 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable,
}
// Record in mPermissionToUids that a permission was defined by a new package
if (!mPermissionToUids.containsKey(permissionName)) {
- mPermissionToUids.put(permissionName, new HashSet<>());
+ mPermissionToUids.put(permissionName, new ArraySet<>());
}
mPermissionToUids.get(permissionName).add(newPkgSetting.getAppId());
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 88a3f8e4c8c1..095a7f6066b5 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -261,6 +261,7 @@ final class DeletePackageHelper {
final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
info.sendPackageRemovedBroadcasts(killApp, removedBySystem);
info.sendSystemPackageUpdatedBroadcasts();
+ PackageMetrics.onUninstallSucceeded(info, deleteFlags, mUserManagerInternal);
}
// Force a gc to clear up things.
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2a0b44d43bd7..70bd24ce20a2 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2619,11 +2619,29 @@ final class InstallPackageHelper {
}
}
+ Bundle extras = new Bundle();
+ extras.putInt(Intent.EXTRA_UID, request.getUid());
+ if (update) {
+ extras.putBoolean(Intent.EXTRA_REPLACING, true);
+ }
+ extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+
+ // If a package is a static shared library, then only the installer of the package
+ // should get the broadcast.
+ if (installerPackageName != null
+ && request.getPkg().getStaticSharedLibraryName() != null) {
+ mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+ extras, 0 /*flags*/,
+ installerPackageName, null /*finishedReceiver*/,
+ request.getNewUsers(), null /* instantUserIds*/,
+ null /* broadcastAllowList */, null);
+ }
+
// Send installed broadcasts if the package is not a static shared lib.
if (request.getPkg().getStaticSharedLibraryName() == null) {
mPm.mProcessLoggingHandler.invalidateBaseApkHash(request.getPkg().getBaseApkPath());
- // Send added for users that see the package for the first time
+ // Send PACKAGE_ADDED broadcast for users that see the package for the first time
// sendPackageAddedForNewUsers also deals with system apps
int appId = UserHandle.getAppId(request.getUid());
boolean isSystem = request.getPkg().isSystem();
@@ -2631,13 +2649,9 @@ final class InstallPackageHelper {
isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId,
firstUserIds, firstInstantUserIds, dataLoaderType);
- // Send added for users that don't see the package for the first time
- Bundle extras = new Bundle();
- extras.putInt(Intent.EXTRA_UID, request.getUid());
- if (update) {
- extras.putBoolean(Intent.EXTRA_REPLACING, true);
- }
- extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType);
+ // Send PACKAGE_ADDED broadcast for users that don't see
+ // the package for the first time
+
// Send to all running apps.
final SparseArray<int[]> newBroadcastAllowList;
synchronized (mPm.mLock) {
@@ -2650,8 +2664,8 @@ final class InstallPackageHelper {
extras, 0 /*flags*/,
null /*targetPackage*/, null /*finishedReceiver*/,
updateUserIds, instantUserIds, newBroadcastAllowList, null);
+ // Send to the installer, even if it's not running.
if (installerPackageName != null) {
- // Send to the installer, even if it's not running.
mPm.sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
extras, 0 /*flags*/,
installerPackageName, null /*finishedReceiver*/,
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 01a8bd0a4225..4e5a6f9fa2b5 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED;
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;
@@ -114,6 +115,8 @@ final class InstallRequest {
@Nullable
private final PackageMetrics mPackageMetrics;
+ private final int mSessionId;
+ private final int mRequireUserAction;
// New install
InstallRequest(InstallingSession params) {
@@ -128,6 +131,8 @@ final class InstallRequest {
params.mDataLoaderType, params.mPackageSource);
mPackageMetrics = new PackageMetrics(this);
mIsInstallInherit = params.mIsInherit;
+ mSessionId = params.mSessionId;
+ mRequireUserAction = params.mRequireUserAction;
}
// Install existing package as user
@@ -141,6 +146,8 @@ final class InstallRequest {
mPostInstallRunnable = runnable;
mPackageMetrics = new PackageMetrics(this);
mIsInstallForUsers = true;
+ mSessionId = -1;
+ mRequireUserAction = USER_ACTION_UNSPECIFIED;
}
// addForInit
@@ -158,6 +165,8 @@ final class InstallRequest {
mScanFlags = scanFlags;
mScanResult = scanResult;
mPackageMetrics = null; // No logging from this code path
+ mSessionId = -1;
+ mRequireUserAction = USER_ACTION_UNSPECIFIED;
}
@Nullable
@@ -565,6 +574,14 @@ final class InstallRequest {
}
}
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ public int getRequireUserAction() {
+ return mRequireUserAction;
+ }
+
public void setScanFlags(int scanFlags) {
mScanFlags = scanFlags;
}
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index e4a0a3ab8dfa..d8494dbaa827 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING;
+import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_UNSPECIFIED;
import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_STAGED;
import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
@@ -95,7 +96,10 @@ class InstallingSession {
final InstallPackageHelper mInstallPackageHelper;
final RemovePackageHelper mRemovePackageHelper;
final boolean mIsInherit;
+ final int mSessionId;
+ final int mRequireUserAction;
+ // For move install
InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
int installFlags, InstallSource installSource, String volumeUuid,
UserHandle user, String packageAbiOverride, int packageSource,
@@ -124,9 +128,11 @@ class InstallingSession {
mPackageSource = packageSource;
mPackageLite = packageLite;
mIsInherit = false;
+ mSessionId = -1;
+ mRequireUserAction = USER_ACTION_UNSPECIFIED;
}
- InstallingSession(File stagedDir, IPackageInstallObserver2 observer,
+ InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
PackageInstaller.SessionParams sessionParams, InstallSource installSource,
UserHandle user, SigningDetails signingDetails, int installerUid,
PackageLite packageLite, PackageManagerService pm) {
@@ -155,6 +161,8 @@ class InstallingSession {
mPackageSource = sessionParams.packageSource;
mPackageLite = packageLite;
mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING;
+ mSessionId = sessionId;
+ mRequireUserAction = sessionParams.requireUserAction;
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5d13a45e03e3..a2b462a8d76c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2566,8 +2566,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
synchronized (mLock) {
- return new InstallingSession(stageDir, localObserver, params, mInstallSource, user,
- mSigningDetails, mInstallerUid, mPackageLite, mPm);
+ return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource,
+ user, mSigningDetails, mInstallerUid, mPackageLite, mPm);
}
}
@@ -3711,7 +3711,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private boolean dispatchPendingAbandonCallback() {
final Runnable callback;
synchronized (mLock) {
- Preconditions.checkState(mStageDirInUse);
+ if (!mStageDirInUse) {
+ return false;
+ }
mStageDirInUse = false;
callback = mPendingAbandonCallback;
mPendingAbandonCallback = null;
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 0391163856b2..0574f737a54b 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -19,12 +19,13 @@ package com.android.server.pm;
import static android.os.Process.INVALID_UID;
import android.annotation.IntDef;
+import android.content.pm.PackageManager;
import android.content.pm.parsing.ApkLiteParseUtils;
-import android.os.UserManager;
import android.util.Pair;
import android.util.SparseArray;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
import com.android.server.pm.pkg.PackageStateInternal;
import java.io.File;
@@ -73,6 +74,8 @@ final class PackageMetrics {
}
private void reportInstallationStats(Computer snapshot, boolean success) {
+ UserManagerInternal userManagerInternal =
+ LocalServices.getService(UserManagerInternal.class);
// TODO(b/249294752): do not log if adb
final long installDurationMillis =
System.currentTimeMillis() - mInstallStartTimestampMillis;
@@ -89,13 +92,13 @@ final class PackageMetrics {
final long apksSize = getApksSize(ps.getPath());
FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
- 0 /* session_id */,
+ mInstallRequest.getSessionId() /* session_id */,
success ? null : packageName /* not report package_name on success */,
mInstallRequest.getUid() /* uid */,
newUsers /* user_ids */,
- getUserTypes(newUsers) /* user_types */,
+ userManagerInternal.getUserTypesForStatsd(newUsers) /* user_types */,
originalUsers /* original_user_ids */,
- getUserTypes(originalUsers) /* original_user_types */,
+ userManagerInternal.getUserTypesForStatsd(originalUsers) /* original_user_types */,
mInstallRequest.getReturnCode() /* public_return_code */,
0 /* internal_error_code */,
apksSize /* apks_size_bytes */,
@@ -107,7 +110,7 @@ final class PackageMetrics {
installerUid /* installer_package_uid */,
-1 /* original_installer_package_uid */,
mInstallRequest.getDataLoaderType() /* data_loader_type */,
- 0 /* user_action_required_type */,
+ mInstallRequest.getRequireUserAction() /* user_action_required_type */,
mInstallRequest.isInstantInstall() /* is_instant */,
mInstallRequest.isInstallReplace() /* is_replace */,
mInstallRequest.isInstallSystem() /* is_system */,
@@ -163,18 +166,6 @@ final class PackageMetrics {
return new Pair<>(stepsArray, durationsArray);
}
- private static int[] getUserTypes(int[] userIds) {
- if (userIds == null) {
- return null;
- }
- final int[] userTypes = new int[userIds.length];
- for (int i = 0; i < userTypes.length; i++) {
- String userType = UserManagerService.getInstance().getUserInfo(userIds[i]).userType;
- userTypes[i] = UserManager.getUserTypeForStatsd(userType);
- }
- return userTypes;
- }
-
private static class InstallStep {
private final long mStartTimestampMillis;
private long mDurationMillis = -1;
@@ -191,4 +182,20 @@ final class PackageMetrics {
return mDurationMillis;
}
}
+
+ public static void onUninstallSucceeded(PackageRemovedInfo info, int deleteFlags,
+ UserManagerInternal userManagerInternal) {
+ if (info.mIsUpdate) {
+ // Not logging uninstalls caused by app updates
+ return;
+ }
+ final int[] removedUsers = info.mRemovedUsers;
+ final int[] removedUserTypes = userManagerInternal.getUserTypesForStatsd(removedUsers);
+ final int[] originalUsers = info.mOrigUsers;
+ final int[] originalUserTypes = userManagerInternal.getUserTypesForStatsd(originalUsers);
+ FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_UNINSTALLATION_REPORTED,
+ info.mUid, removedUsers, removedUserTypes, originalUsers, originalUserTypes,
+ deleteFlags, PackageManager.DELETE_SUCCEEDED, info.mIsRemovedPackageSystemUpdate,
+ !info.mRemovedForAllUsers);
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 3c863d080d79..4cac1151136d 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -111,12 +111,6 @@ final class PackageRemovedInfo {
}
private void sendPackageRemovedBroadcastInternal(boolean killApp, boolean removedBySystem) {
- // Don't send static shared library removal broadcasts as these
- // libs are visible only the apps that depend on them an one
- // cannot remove the library if it has a dependency.
- if (mIsStaticSharedLib) {
- return;
- }
Bundle extras = new Bundle();
final int removedUid = mRemovedAppId >= 0 ? mRemovedAppId : mUid;
extras.putInt(Intent.EXTRA_UID, removedUid);
@@ -128,15 +122,22 @@ final class PackageRemovedInfo {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, mRemovedForAllUsers);
+
+ // Send PACKAGE_REMOVED broadcast to the respective installer.
+ if (mRemovedPackage != null && mInstallerPackageName != null) {
+ mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
+ mRemovedPackage, extras, 0 /*flags*/,
+ mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null);
+ }
+ if (mIsStaticSharedLib) {
+ // When uninstalling static shared libraries, only the package's installer needs to be
+ // sent a PACKAGE_REMOVED broadcast. There are no other intended recipients.
+ return;
+ }
if (mRemovedPackage != null) {
mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
mRemovedPackage, extras, 0, null /*targetPackage*/, null,
mBroadcastUsers, mInstantUserIds, mBroadcastAllowList, null);
- if (mInstallerPackageName != null) {
- mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED,
- mRemovedPackage, extras, 0 /*flags*/,
- mInstallerPackageName, null, mBroadcastUsers, mInstantUserIds, null, null);
- }
mPackageSender.sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED_INTERNAL,
mRemovedPackage, extras, 0 /*flags*/, PLATFORM_PACKAGE_NAME,
null /*finishedReceiver*/, mBroadcastUsers, mInstantUserIds,
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 9dafcceefdd0..1420cbf3ae45 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -437,4 +437,8 @@ public abstract class UserManagerInternal {
/** TODO(b/244333150): temporary method until UserVisibilityMediator handles that logic */
public abstract void onUserVisibilityChanged(@UserIdInt int userId, boolean visible);
+
+ /** Return the integer types of the given user IDs. Only used for reporting metrics to statsd.
+ */
+ public abstract int[] getUserTypesForStatsd(@UserIdInt int[] userIds);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4a4a231f4fba..9f84ab030cef 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -6893,6 +6893,24 @@ public class UserManagerService extends IUserManager.Stub {
}
});
}
+
+ @Override
+ public int[] getUserTypesForStatsd(@UserIdInt int[] userIds) {
+ if (userIds == null) {
+ return null;
+ }
+ final int[] userTypes = new int[userIds.length];
+ for (int i = 0; i < userTypes.length; i++) {
+ final UserInfo userInfo = getUserInfo(userIds[i]);
+ if (userInfo == null) {
+ // Not possible because the input user ids should all be valid
+ userTypes[i] = UserManager.getUserTypeForStatsd("");
+ } else {
+ userTypes[i] = UserManager.getUserTypeForStatsd(userInfo.userType);
+ }
+ }
+ return userTypes;
+ }
} // class LocalService
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 916df89d6521..0c5e451a93cc 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -11507,6 +11507,9 @@ public class BatteryStatsImpl extends BatteryStats {
mHistory.reset();
+ // Store the empty state to disk to ensure consistency
+ writeSyncLocked();
+
// Flush external data, gathering snapshots, but don't process it since it is pre-reset data
mIgnoreNextExternalStats = true;
mExternalSync.scheduleSync("reset", ExternalStatsSync.UPDATE_ON_RESET);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 7281a4742b84..50eab256c411 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -272,7 +272,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
mContext = context;
LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
- LocalServices.addService(GlobalActionsProvider.class, mGlobalActionsProvider);
// We always have a default display.
final UiState state = new UiState();
@@ -289,6 +288,17 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
mSessionMonitor = new SessionMonitor(mContext);
}
+ /**
+ * Publish the {@link GlobalActionsProvider}.
+ */
+ // TODO(b/259420401): investigate if we can extract GlobalActionsProvider to its own system
+ // service.
+ public void publishGlobalActionsProvider() {
+ if (LocalServices.getService(GlobalActionsProvider.class) == null) {
+ LocalServices.addService(GlobalActionsProvider.class, mGlobalActionsProvider);
+ }
+ }
+
private IOverlayManager getOverlayManager() {
// No need to synchronize; worst-case scenario it will be fetched twice.
if (mOverlayManager == null) {
diff --git a/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java b/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
index 8218fa5c4643..80d959977b0f 100644
--- a/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
+++ b/services/core/java/com/android/server/timezonedetector/GeolocationTimeZoneSuggestion.java
@@ -19,20 +19,15 @@ package com.android.server.timezonedetector;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.ShellCommand;
-import android.os.SystemClock;
-import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import java.util.StringTokenizer;
/**
- * A time zone suggestion from the location_time_zone_manager service to the time_zone_detector
- * service.
+ * A time zone suggestion from the location_time_zone_manager service (AKA the location-based time
+ * zone detection algorithm).
*
* <p>Geolocation-based suggestions have the following properties:
*
@@ -63,24 +58,16 @@ import java.util.StringTokenizer;
* location_time_zone_manager may become uncertain if components further downstream cannot
* determine the device's location with sufficient accuracy, or if the location is known but no
* time zone can be determined because no time zone mapping information is available.</li>
- * <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is
- * used to record why the suggestion exists and how it was obtained. This information exists
- * only to aid in debugging and therefore is used by {@link #toString()}, but it is not for use
- * in detection logic and is not considered in {@link #hashCode()} or {@link #equals(Object)}.
* </li>
* </ul>
- *
- * @hide
*/
public final class GeolocationTimeZoneSuggestion {
@ElapsedRealtimeLong private final long mEffectiveFromElapsedMillis;
@Nullable private final List<String> mZoneIds;
- @Nullable private ArrayList<String> mDebugInfo;
private GeolocationTimeZoneSuggestion(
- @ElapsedRealtimeLong long effectiveFromElapsedMillis,
- @Nullable List<String> zoneIds) {
+ @ElapsedRealtimeLong long effectiveFromElapsedMillis, @Nullable List<String> zoneIds) {
mEffectiveFromElapsedMillis = effectiveFromElapsedMillis;
if (zoneIds == null) {
// Unopinionated
@@ -104,8 +91,7 @@ public final class GeolocationTimeZoneSuggestion {
*/
@NonNull
public static GeolocationTimeZoneSuggestion createCertainSuggestion(
- @ElapsedRealtimeLong long effectiveFromElapsedMillis,
- @NonNull List<String> zoneIds) {
+ @ElapsedRealtimeLong long effectiveFromElapsedMillis, @NonNull List<String> zoneIds) {
return new GeolocationTimeZoneSuggestion(effectiveFromElapsedMillis, zoneIds);
}
@@ -126,25 +112,6 @@ public final class GeolocationTimeZoneSuggestion {
return mZoneIds;
}
- /** Returns debug information. See {@link GeolocationTimeZoneSuggestion} for details. */
- @NonNull
- public List<String> getDebugInfo() {
- return mDebugInfo == null
- ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
- }
-
- /**
- * Associates information with the instance that can be useful for debugging / logging. The
- * information is present in {@link #toString()} but is not considered for
- * {@link #equals(Object)} and {@link #hashCode()}.
- */
- public void addDebugInfo(String... debugInfos) {
- if (mDebugInfo == null) {
- mDebugInfo = new ArrayList<>();
- }
- mDebugInfo.addAll(Arrays.asList(debugInfos));
- }
-
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -169,59 +136,6 @@ public final class GeolocationTimeZoneSuggestion {
return "GeolocationTimeZoneSuggestion{"
+ "mEffectiveFromElapsedMillis=" + mEffectiveFromElapsedMillis
+ ", mZoneIds=" + mZoneIds
- + ", mDebugInfo=" + mDebugInfo
+ '}';
}
-
- /** @hide */
- public static GeolocationTimeZoneSuggestion parseCommandLineArg(@NonNull ShellCommand cmd) {
- String zoneIdsString = null;
- String opt;
- while ((opt = cmd.getNextArg()) != null) {
- switch (opt) {
- case "--zone_ids": {
- zoneIdsString = cmd.getNextArgRequired();
- break;
- }
- default: {
- throw new IllegalArgumentException("Unknown option: " + opt);
- }
- }
- }
-
- if (zoneIdsString == null) {
- throw new IllegalArgumentException("Missing --zone_ids");
- }
-
- long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
- List<String> zoneIds = parseZoneIdsArg(zoneIdsString);
- GeolocationTimeZoneSuggestion suggestion =
- new GeolocationTimeZoneSuggestion(elapsedRealtimeMillis, zoneIds);
- suggestion.addDebugInfo("Command line injection");
- return suggestion;
- }
-
- private static List<String> parseZoneIdsArg(String zoneIdsString) {
- if ("UNCERTAIN".equals(zoneIdsString)) {
- return null;
- } else if ("EMPTY".equals(zoneIdsString)) {
- return Collections.emptyList();
- } else {
- ArrayList<String> zoneIds = new ArrayList<>();
- StringTokenizer tokenizer = new StringTokenizer(zoneIdsString, ",");
- while (tokenizer.hasMoreTokens()) {
- zoneIds.add(tokenizer.nextToken());
- }
- return zoneIds;
- }
- }
-
- /** @hide */
- public static void printCommandLineOpts(@NonNull PrintWriter pw) {
- pw.println("Geolocation suggestion options:");
- pw.println(" --zone_ids {UNCERTAIN|EMPTY|<Olson ID>+}");
- pw.println();
- pw.println("See " + GeolocationTimeZoneSuggestion.class.getName()
- + " for more information");
- }
}
diff --git a/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java b/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java
new file mode 100644
index 000000000000..1ffd9a11b300
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/LocationAlgorithmEvent.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezonedetector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.StringTokenizer;
+
+/**
+ * An event from the location_time_zone_manager service (AKA the location-based time zone detection
+ * algorithm). An event can represent a new time zone recommendation, an algorithm status change, or
+ * both.
+ *
+ * <p>Events have the following properties:
+ *
+ * <ul>
+ * <li>{@code algorithmStatus}: The current status of the location-based time zone detection
+ * algorithm.</li>
+ * <li>{@code suggestion}: The latest time zone suggestion, if there is one.</li>
+ * <li>{@code debugInfo} contains debugging metadata associated with the suggestion. This is
+ * used to record why the event exists and how information contained within it was obtained.
+ * This information exists only to aid in debugging and therefore is used by
+ * {@link #toString()}, but it is not for use in detection logic and is not considered in
+ * {@link #hashCode()} or {@link #equals(Object)}.
+ * </li>
+ * </ul>
+ */
+public final class LocationAlgorithmEvent {
+
+ @NonNull private final LocationTimeZoneAlgorithmStatus mAlgorithmStatus;
+ @Nullable private final GeolocationTimeZoneSuggestion mSuggestion;
+ @Nullable private ArrayList<String> mDebugInfo;
+
+ /** Creates a new instance. */
+ public LocationAlgorithmEvent(
+ @NonNull LocationTimeZoneAlgorithmStatus algorithmStatus,
+ @Nullable GeolocationTimeZoneSuggestion suggestion) {
+ mAlgorithmStatus = Objects.requireNonNull(algorithmStatus);
+ mSuggestion = suggestion;
+ }
+
+ /**
+ * Returns the status of the location time zone detector algorithm.
+ */
+ @NonNull
+ public LocationTimeZoneAlgorithmStatus getAlgorithmStatus() {
+ return mAlgorithmStatus;
+ }
+
+ /**
+ * Returns the latest location algorithm suggestion. See {@link LocationAlgorithmEvent} for
+ * details.
+ */
+ @Nullable
+ public GeolocationTimeZoneSuggestion getSuggestion() {
+ return mSuggestion;
+ }
+
+ /** Returns debug information. See {@link LocationAlgorithmEvent} for details. */
+ @NonNull
+ public List<String> getDebugInfo() {
+ return mDebugInfo == null
+ ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
+ }
+
+ /**
+ * Associates information with the instance that can be useful for debugging / logging. The
+ * information is present in {@link #toString()} but is not considered for
+ * {@link #equals(Object)} and {@link #hashCode()}.
+ */
+ public void addDebugInfo(String... debugInfos) {
+ if (mDebugInfo == null) {
+ mDebugInfo = new ArrayList<>();
+ }
+ mDebugInfo.addAll(Arrays.asList(debugInfos));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ LocationAlgorithmEvent that = (LocationAlgorithmEvent) o;
+ return mAlgorithmStatus.equals(that.mAlgorithmStatus)
+ && Objects.equals(mSuggestion, that.mSuggestion);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAlgorithmStatus, mSuggestion);
+ }
+
+ @Override
+ public String toString() {
+ return "LocationAlgorithmEvent{"
+ + "mAlgorithmStatus=" + mAlgorithmStatus
+ + ", mSuggestion=" + mSuggestion
+ + ", mDebugInfo=" + mDebugInfo
+ + '}';
+ }
+
+ static LocationAlgorithmEvent parseCommandLineArg(@NonNull ShellCommand cmd) {
+ String suggestionString = null;
+ LocationTimeZoneAlgorithmStatus algorithmStatus = null;
+ String opt;
+ while ((opt = cmd.getNextArg()) != null) {
+ switch (opt) {
+ case "--status": {
+ algorithmStatus = LocationTimeZoneAlgorithmStatus.parseCommandlineArg(
+ cmd.getNextArgRequired());
+ break;
+ }
+ case "--suggestion": {
+ suggestionString = cmd.getNextArgRequired();
+ break;
+ }
+ default: {
+ throw new IllegalArgumentException("Unknown option: " + opt);
+ }
+ }
+ }
+
+ if (algorithmStatus == null) {
+ throw new IllegalArgumentException("Missing --status");
+ }
+
+ GeolocationTimeZoneSuggestion suggestion = null;
+ if (suggestionString != null) {
+ List<String> zoneIds = parseZoneIds(suggestionString);
+ long elapsedRealtimeMillis = SystemClock.elapsedRealtime();
+ if (zoneIds == null) {
+ suggestion = GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+ elapsedRealtimeMillis);
+ } else {
+ suggestion = GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ elapsedRealtimeMillis, zoneIds);
+ }
+ }
+
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
+ event.addDebugInfo("Command line injection");
+ return event;
+ }
+
+ private static List<String> parseZoneIds(String zoneIdsString) {
+ if ("UNCERTAIN".equals(zoneIdsString)) {
+ return null;
+ } else if ("EMPTY".equals(zoneIdsString)) {
+ return Collections.emptyList();
+ } else {
+ ArrayList<String> zoneIds = new ArrayList<>();
+ StringTokenizer tokenizer = new StringTokenizer(zoneIdsString, ",");
+ while (tokenizer.hasMoreTokens()) {
+ zoneIds.add(tokenizer.nextToken());
+ }
+ return zoneIds;
+ }
+ }
+
+ static void printCommandLineOpts(@NonNull PrintWriter pw) {
+ pw.println("Location algorithm event options:");
+ pw.println(" --status {LocationTimeZoneAlgorithmStatus toString() format}");
+ pw.println(" [--suggestion {UNCERTAIN|EMPTY|<Olson ID>+}]");
+ pw.println();
+ pw.println("See " + LocationAlgorithmEvent.class.getName() + " for more information");
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
index 6c36989320e8..aad53596fc19 100644
--- a/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
+++ b/services/core/java/com/android/server/timezonedetector/MetricsTimeZoneDetectorState.java
@@ -89,7 +89,7 @@ public final class MetricsTimeZoneDetectorState {
@NonNull String deviceTimeZoneId,
@Nullable ManualTimeZoneSuggestion latestManualSuggestion,
@Nullable TelephonyTimeZoneSuggestion latestTelephonySuggestion,
- @Nullable GeolocationTimeZoneSuggestion latestGeolocationSuggestion) {
+ @Nullable LocationAlgorithmEvent latestLocationAlgorithmEvent) {
boolean includeZoneIds = configurationInternal.isEnhancedMetricsCollectionEnabled();
String metricDeviceTimeZoneId = includeZoneIds ? deviceTimeZoneId : null;
@@ -101,9 +101,13 @@ public final class MetricsTimeZoneDetectorState {
MetricsTimeZoneSuggestion latestCanonicalTelephonySuggestion =
createMetricsTimeZoneSuggestion(
tzIdOrdinalGenerator, latestTelephonySuggestion, includeZoneIds);
- MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion =
- createMetricsTimeZoneSuggestion(
- tzIdOrdinalGenerator, latestGeolocationSuggestion, includeZoneIds);
+
+ MetricsTimeZoneSuggestion latestCanonicalGeolocationSuggestion = null;
+ if (latestLocationAlgorithmEvent != null) {
+ GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion();
+ latestCanonicalGeolocationSuggestion = createMetricsTimeZoneSuggestion(
+ tzIdOrdinalGenerator, suggestion, includeZoneIds);
+ }
return new MetricsTimeZoneDetectorState(
configurationInternal, deviceTimeZoneIdOrdinal, metricDeviceTimeZoneId,
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
index 80cf1d6b9031..74a518bf8382 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternal.java
@@ -59,11 +59,11 @@ public interface TimeZoneDetectorInternal {
boolean setManualTimeZoneForDpm(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion);
/**
- * Suggests the current time zone, determined using geolocation, to the detector. The
- * detector may ignore the signal based on system settings, whether better information is
- * available, and so on. This method may be implemented asynchronously.
+ * Handles the supplied {@link LocationAlgorithmEvent}. The detector may ignore the event based
+ * on system settings, whether better information is available, and so on. This method may be
+ * implemented asynchronously.
*/
- void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion);
+ void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent locationAlgorithmEvent);
/** Generates a state snapshot for metrics. */
@NonNull
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index dfb44df7b993..07d04737c3e2 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -76,13 +76,14 @@ public final class TimeZoneDetectorInternalImpl implements TimeZoneDetectorInter
}
@Override
- public void suggestGeolocationTimeZone(
- @NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
- Objects.requireNonNull(timeZoneSuggestion);
+ public void handleLocationAlgorithmEvent(
+ @NonNull LocationAlgorithmEvent locationAlgorithmEvent) {
+ Objects.requireNonNull(locationAlgorithmEvent);
// This call can take place on the mHandler thread because there is no return value.
mHandler.post(
- () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
+ () -> mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(
+ locationAlgorithmEvent));
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index f415cf03fdec..f8c1c9269ff3 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -300,12 +300,13 @@ public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub
}
/** Provided for command-line access. This is not exposed as a binder API. */
- void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion timeZoneSuggestion) {
+ void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent locationAlgorithmEvent) {
enforceSuggestGeolocationTimeZonePermission();
- Objects.requireNonNull(timeZoneSuggestion);
+ Objects.requireNonNull(locationAlgorithmEvent);
mHandler.post(
- () -> mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(timeZoneSuggestion));
+ () -> mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(
+ locationAlgorithmEvent));
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
index 1b9f8e6cd66f..69274dba7825 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorShellCommand.java
@@ -19,6 +19,7 @@ import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_CONFIR
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_DUMP_METRICS;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_GET_TIME_ZONE_STATE;
+import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_IS_GEO_DETECTION_SUPPORTED;
@@ -27,7 +28,6 @@ import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SERVIC
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_AUTO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_GEO_DETECTION_ENABLED;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SET_TIME_ZONE_STATE;
-import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE;
import static android.app.timezonedetector.TimeZoneDetector.SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEM_TIME;
@@ -79,8 +79,8 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
return runIsGeoDetectionEnabled();
case SHELL_COMMAND_SET_GEO_DETECTION_ENABLED:
return runSetGeoDetectionEnabled();
- case SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE:
- return runSuggestGeolocationTimeZone();
+ case SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT:
+ return runHandleLocationEvent();
case SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE:
return runSuggestManualTimeZone();
case SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE:
@@ -153,34 +153,34 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
return mInterface.updateConfiguration(userId, configuration) ? 0 : 1;
}
- private int runSuggestGeolocationTimeZone() {
- return runSuggestTimeZone(
- () -> GeolocationTimeZoneSuggestion.parseCommandLineArg(this),
- mInterface::suggestGeolocationTimeZone);
+ private int runHandleLocationEvent() {
+ return runSingleArgMethod(
+ () -> LocationAlgorithmEvent.parseCommandLineArg(this),
+ mInterface::handleLocationAlgorithmEvent);
}
private int runSuggestManualTimeZone() {
- return runSuggestTimeZone(
+ return runSingleArgMethod(
() -> ManualTimeZoneSuggestion.parseCommandLineArg(this),
mInterface::suggestManualTimeZone);
}
private int runSuggestTelephonyTimeZone() {
- return runSuggestTimeZone(
+ return runSingleArgMethod(
() -> TelephonyTimeZoneSuggestion.parseCommandLineArg(this),
mInterface::suggestTelephonyTimeZone);
}
- private <T> int runSuggestTimeZone(Supplier<T> suggestionParser, Consumer<T> invoker) {
+ private <T> int runSingleArgMethod(Supplier<T> argParser, Consumer<T> invoker) {
final PrintWriter pw = getOutPrintWriter();
try {
- T suggestion = suggestionParser.get();
- if (suggestion == null) {
- pw.println("Error: suggestion not specified");
+ T arg = argParser.get();
+ if (arg == null) {
+ pw.println("Error: arg not specified");
return 1;
}
- invoker.accept(suggestion);
- pw.println("Suggestion " + suggestion + " injected.");
+ invoker.accept(arg);
+ pw.println("Arg " + arg + " injected.");
return 0;
} catch (RuntimeException e) {
pw.println(e);
@@ -263,18 +263,18 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
pw.printf(" Sets the geolocation time zone detection enabled setting.\n");
pw.printf(" %s\n", SHELL_COMMAND_ENABLE_TELEPHONY_FALLBACK);
pw.printf(" Signals that telephony time zone detection fall back can be used if"
- + " geolocation detection is supported and enabled. This is a temporary state until"
- + " geolocation detection becomes \"certain\". To have an effect this requires that"
- + " the telephony fallback feature is supported on the device, see below for"
- + " for device_config flags.\n");
- pw.println();
- pw.printf(" %s <geolocation suggestion opts>\n",
- SHELL_COMMAND_SUGGEST_GEO_LOCATION_TIME_ZONE);
- pw.printf(" Suggests a time zone as if via the \"location\" origin.\n");
+ + " geolocation detection is supported and enabled.\n)");
+ pw.printf(" This is a temporary state until geolocation detection becomes \"certain\"."
+ + "\n");
+ pw.printf(" To have an effect this requires that the telephony fallback feature is"
+ + " supported on the device, see below for device_config flags.\n");
+ pw.printf(" %s <location event opts>\n", SHELL_COMMAND_HANDLE_LOCATION_ALGORITHM_EVENT);
+ pw.printf(" Simulates an event from the location time zone detection algorithm.\n");
pw.printf(" %s <manual suggestion opts>\n", SHELL_COMMAND_SUGGEST_MANUAL_TIME_ZONE);
- pw.printf(" Suggests a time zone as if via the \"manual\" origin.\n");
+ pw.printf(" Suggests a time zone as if supplied by a user manually.\n");
pw.printf(" %s <telephony suggestion opts>\n", SHELL_COMMAND_SUGGEST_TELEPHONY_TIME_ZONE);
- pw.printf(" Suggests a time zone as if via the \"telephony\" origin.\n");
+ pw.printf(" Simulates a time zone suggestion from the telephony time zone detection"
+ + " algorithm.\n");
pw.printf(" %s\n", SHELL_COMMAND_GET_TIME_ZONE_STATE);
pw.printf(" Returns the current time zone setting state.\n");
pw.printf(" %s <time zone state options>\n", SHELL_COMMAND_SET_TIME_ZONE_STATE);
@@ -284,7 +284,7 @@ class TimeZoneDetectorShellCommand extends ShellCommand {
pw.printf(" %s\n", SHELL_COMMAND_DUMP_METRICS);
pw.printf(" Dumps the service metrics to stdout for inspection.\n");
pw.println();
- GeolocationTimeZoneSuggestion.printCommandLineOpts(pw);
+ LocationAlgorithmEvent.printCommandLineOpts(pw);
pw.println();
ManualTimeZoneSuggestion.printCommandLineOpts(pw);
pw.println();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 328cf72b262e..5768a6bfa0dc 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -157,10 +157,9 @@ public interface TimeZoneDetectorStrategy extends Dumpable {
boolean confirmTimeZone(@NonNull String timeZoneId);
/**
- * Suggests zero, one or more time zones for the device, or withdraws a previous suggestion if
- * {@link GeolocationTimeZoneSuggestion#getZoneIds()} is {@code null}.
+ * Handles an event from the location-based time zone detection algorithm.
*/
- void suggestGeolocationTimeZone(@NonNull GeolocationTimeZoneSuggestion suggestion);
+ void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent event);
/**
* Suggests a time zone for the device using manually-entered (i.e. user sourced) information.
@@ -183,7 +182,7 @@ public interface TimeZoneDetectorStrategy extends Dumpable {
/**
* Tells the strategy that it can fall back to telephony detection while geolocation detection
- * remains uncertain. {@link #suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion)} can
+ * remains uncertain. {@link #handleLocationAlgorithmEvent(LocationAlgorithmEvent)} can
* disable it again. See {@link TimeZoneDetectorStrategy} for details.
*/
void enableTelephonyTimeZoneFallback();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index ecf25e9c157c..eecf0f74bff6 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -29,9 +29,13 @@ import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.time.DetectorStatusTypes;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
import android.app.time.TimeZoneCapabilities;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -183,11 +187,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
/**
- * The latest geolocation suggestion received. If the user disabled geolocation time zone
- * detection then the latest suggestion is cleared.
+ * The latest location algorithm event received.
*/
@GuardedBy("this")
- private final ReferenceWithHistory<GeolocationTimeZoneSuggestion> mLatestGeoLocationSuggestion =
+ private final ReferenceWithHistory<LocationAlgorithmEvent> mLatestLocationAlgorithmEvent =
new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
/**
@@ -208,6 +211,14 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
@NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
/**
+ * A snapshot of the current detector status. A local copy is cached because it is relatively
+ * heavyweight to obtain and is used more often than it is expected to change.
+ */
+ @GuardedBy("this")
+ @NonNull
+ private TimeZoneDetectorStatus mDetectorStatus;
+
+ /**
* A snapshot of the current user's {@link ConfigurationInternal}. A local copy is cached
* because it is relatively heavyweight to obtain and is used more often than it is expected to
* change. Because many operations are asynchronous, this value may be out of date but should
@@ -258,8 +269,10 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
// Listen for config and user changes and get an initial snapshot of configuration.
StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged;
mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
- mCurrentConfigurationInternal =
- mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+
+ // Initialize mCurrentConfigurationInternal and mDetectorStatus with their starting
+ // values.
+ updateCurrentConfigurationInternalIfRequired("TimeZoneDetectorStrategyImpl:");
}
}
@@ -278,6 +291,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId);
}
return new TimeZoneCapabilitiesAndConfig(
+ mDetectorStatus,
configurationInternal.asCapabilities(bypassUserPolicyChecks),
configurationInternal.asConfiguration());
}
@@ -295,28 +309,52 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
// but that could mean an immediate call to getCapabilitiesAndConfig() for the current user
// wouldn't see the update. So, handle the cache update and notifications here. When the
// async update listener triggers it will find everything already up to date and do nothing.
- if (updateSuccessful && mCurrentConfigurationInternal.getUserId() == userId) {
- ConfigurationInternal configurationInternal =
- mServiceConfigAccessor.getConfigurationInternal(userId);
-
- // If the configuration actually changed, update the cached copy synchronously and do
- // other necessary house-keeping / (async) listener notifications.
- if (!configurationInternal.equals(mCurrentConfigurationInternal)) {
- mCurrentConfigurationInternal = configurationInternal;
-
- String logMsg = "updateConfiguration:"
- + " userId=" + userId
- + ", configuration=" + configuration
- + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks
- + ", mCurrentConfigurationInternal=" + mCurrentConfigurationInternal;
- logTimeZoneDebugInfo(logMsg);
-
- handleConfigurationInternalChanged(logMsg);
- }
+ if (updateSuccessful) {
+ String logMsg = "updateConfiguration:"
+ + " userId=" + userId
+ + ", configuration=" + configuration
+ + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks;
+ updateCurrentConfigurationInternalIfRequired(logMsg);
}
return updateSuccessful;
}
+ @GuardedBy("this")
+ private void updateCurrentConfigurationInternalIfRequired(@NonNull String logMsg) {
+ ConfigurationInternal newCurrentConfigurationInternal =
+ mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+ // mCurrentConfigurationInternal is null the first time this method is called.
+ ConfigurationInternal oldCurrentConfigurationInternal = mCurrentConfigurationInternal;
+
+ // If the configuration actually changed, update the cached copy synchronously and do
+ // other necessary house-keeping / (async) listener notifications.
+ if (!newCurrentConfigurationInternal.equals(oldCurrentConfigurationInternal)) {
+ mCurrentConfigurationInternal = newCurrentConfigurationInternal;
+
+ logMsg += " [oldConfiguration=" + oldCurrentConfigurationInternal
+ + ", newConfiguration=" + newCurrentConfigurationInternal
+ + "]";
+ logTimeZoneDebugInfo(logMsg);
+
+ // ConfigurationInternal changes can affect the detector's status.
+ updateDetectorStatus();
+
+ // The configuration and maybe the status changed so notify listeners.
+ notifyStateChangeListenersAsynchronously();
+
+ // The configuration change may have changed available suggestions or the way
+ // suggestions are used, so re-run detection.
+ doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
+ }
+ }
+
+ @GuardedBy("this")
+ private void notifyStateChangeListenersAsynchronously() {
+ for (StateChangeListener listener : mStateChangeListeners) {
+ mStateChangeHandler.post(listener::onChange);
+ }
+ }
+
@Override
public synchronized void addChangeListener(StateChangeListener listener) {
mStateChangeListeners.add(listener);
@@ -356,33 +394,39 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
}
@Override
- public synchronized void suggestGeolocationTimeZone(
- @NonNull GeolocationTimeZoneSuggestion suggestion) {
-
+ public synchronized void handleLocationAlgorithmEvent(@NonNull LocationAlgorithmEvent event) {
ConfigurationInternal currentUserConfig = mCurrentConfigurationInternal;
if (DBG) {
- Slog.d(LOG_TAG, "Geolocation suggestion received."
+ Slog.d(LOG_TAG, "Location algorithm event received."
+ " currentUserConfig=" + currentUserConfig
- + " newSuggestion=" + suggestion);
+ + " event=" + event);
}
- Objects.requireNonNull(suggestion);
+ Objects.requireNonNull(event);
- // Geolocation suggestions may be stored but not used during time zone detection if the
+ // Location algorithm events may be stored but not used during time zone detection if the
// configuration doesn't have geo time zone detection enabled. The caller is expected to
- // withdraw a previous suggestion (i.e. submit an "uncertain" suggestion, when geo time zone
- // detection is disabled.
-
- // The suggestion's "effective from" time is ignored: we currently assume suggestions
- // are made in a sensible order and the most recent is always the best one to use.
- mLatestGeoLocationSuggestion.set(suggestion);
+ // withdraw a previous suggestion, i.e. submit an event containing an "uncertain"
+ // suggestion, when geo time zone detection is disabled.
+
+ // We currently assume events are made in a sensible order and the most recent is always the
+ // best one to use.
+ mLatestLocationAlgorithmEvent.set(event);
+
+ // The latest location algorithm event can affect the cached detector status, so update it
+ // and notify state change listeners as needed.
+ boolean statusChanged = updateDetectorStatus();
+ if (statusChanged) {
+ notifyStateChangeListenersAsynchronously();
+ }
// Update the mTelephonyTimeZoneFallbackEnabled state if needed: a certain suggestion
// will usually disable telephony fallback mode if it is currently enabled.
+ // TODO(b/236624675)Some provider status codes can be used to enable telephony fallback.
disableTelephonyFallbackIfNeeded();
- // Now perform auto time zone detection. The new suggestion may be used to modify the
- // time zone setting.
- String reason = "New geolocation time zone suggested. suggestion=" + suggestion;
+ // Now perform auto time zone detection. The new event may be used to modify the time zone
+ // setting.
+ String reason = "New location algorithm event received. event=" + event;
doAutoTimeZoneDetection(currentUserConfig, reason);
}
@@ -465,9 +509,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
+ mTelephonyTimeZoneFallbackEnabled;
logTimeZoneDebugInfo(logMsg);
- // mTelephonyTimeZoneFallbackEnabled and mLatestGeoLocationSuggestion interact.
- // If there is currently a certain geolocation suggestion, then the telephony fallback
- // value needs to be considered after changing it.
+ // mTelephonyTimeZoneFallbackEnabled and mLatestLocationAlgorithmEvent interact.
+ // If the latest event contains a "certain" geolocation suggestion, then the telephony
+ // fallback value needs to be considered after changing it.
// With the way that the mTelephonyTimeZoneFallbackEnabled time is currently chosen
// above, and the fact that geolocation suggestions should never have a time in the
// future, the following call will be a no-op, and telephony fallback will remain
@@ -507,7 +551,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
mEnvironment.getDeviceTimeZone(),
getLatestManualSuggestion(),
telephonySuggestion,
- getLatestGeolocationSuggestion());
+ getLatestLocationAlgorithmEvent());
}
@Override
@@ -606,13 +650,15 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
*/
@GuardedBy("this")
private boolean doGeolocationTimeZoneDetection(@NonNull String detectionReason) {
- GeolocationTimeZoneSuggestion latestGeolocationSuggestion =
- mLatestGeoLocationSuggestion.get();
- if (latestGeolocationSuggestion == null) {
+ // Terminate early if there's nothing to do.
+ LocationAlgorithmEvent latestLocationAlgorithmEvent = mLatestLocationAlgorithmEvent.get();
+ if (latestLocationAlgorithmEvent == null
+ || latestLocationAlgorithmEvent.getSuggestion() == null) {
return false;
}
- List<String> zoneIds = latestGeolocationSuggestion.getZoneIds();
+ GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion();
+ List<String> zoneIds = suggestion.getZoneIds();
if (zoneIds == null) {
// This means the originator of the suggestion is uncertain about the time zone. The
// existing time zone setting must be left as it is but detection can go on looking for
@@ -645,13 +691,18 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
}
/**
- * Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest geo
- * suggestion is a "certain" suggestion that comes after the time when telephony fallback was
- * enabled.
+ * Sets the mTelephonyTimeZoneFallbackEnabled state to {@code false} if the latest location
+ * algorithm event contains a "certain" suggestion that comes after the time when telephony
+ * fallback was enabled.
*/
@GuardedBy("this")
private void disableTelephonyFallbackIfNeeded() {
- GeolocationTimeZoneSuggestion suggestion = mLatestGeoLocationSuggestion.get();
+ LocationAlgorithmEvent latestLocationAlgorithmEvent = mLatestLocationAlgorithmEvent.get();
+ if (latestLocationAlgorithmEvent == null) {
+ return;
+ }
+
+ GeolocationTimeZoneSuggestion suggestion = latestLocationAlgorithmEvent.getSuggestion();
boolean isLatestSuggestionCertain = suggestion != null && suggestion.getZoneIds() != null;
if (isLatestSuggestionCertain && mTelephonyTimeZoneFallbackEnabled.getValue()) {
// This transition ONLY changes mTelephonyTimeZoneFallbackEnabled from
@@ -809,33 +860,27 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* Handles a configuration change notification.
*/
private synchronized void handleConfigurationInternalMaybeChanged() {
- ConfigurationInternal currentUserConfig =
- mServiceConfigAccessor.getCurrentUserConfigurationInternal();
-
- // The configuration may not actually have changed so check before doing anything.
- if (!currentUserConfig.equals(mCurrentConfigurationInternal)) {
- String logMsg = "handleConfigurationInternalMaybeChanged:"
- + " oldConfiguration=" + mCurrentConfigurationInternal
- + ", newConfiguration=" + currentUserConfig;
- logTimeZoneDebugInfo(logMsg);
-
- mCurrentConfigurationInternal = currentUserConfig;
-
- handleConfigurationInternalChanged(logMsg);
- }
+ String logMsg = "handleConfigurationInternalMaybeChanged:";
+ updateCurrentConfigurationInternalIfRequired(logMsg);
}
- /** House-keeping that needs to be done when the mCurrentConfigurationInternal has changed. */
+ /**
+ * Called whenever the information that contributes to {@link #mDetectorStatus} could have
+ * changed. Updates the cached status snapshot if required.
+ *
+ * @return true if the status had changed and has been updated
+ */
@GuardedBy("this")
- private void handleConfigurationInternalChanged(@NonNull String logMsg) {
- // Notify change listeners asynchronously.
- for (StateChangeListener listener : mStateChangeListeners) {
- mStateChangeHandler.post(listener::onChange);
+ private boolean updateDetectorStatus() {
+ TimeZoneDetectorStatus newDetectorStatus = createTimeZoneDetectorStatus(
+ mCurrentConfigurationInternal, mLatestLocationAlgorithmEvent.get());
+ // mDetectorStatus is null the first time this method is called.
+ TimeZoneDetectorStatus oldDetectorStatus = mDetectorStatus;
+ boolean statusChanged = !newDetectorStatus.equals(oldDetectorStatus);
+ if (statusChanged) {
+ mDetectorStatus = newDetectorStatus;
}
-
- // The configuration change may have changed available suggestions or the way
- // suggestions are used, so re-run detection.
- doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
+ return statusChanged;
}
/**
@@ -847,6 +892,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
ipw.increaseIndent(); // level 1
ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal);
+ ipw.println("mDetectorStatus=" + mDetectorStatus);
final boolean bypassUserPolicyChecks = false;
ipw.println("[Capabilities="
+ mCurrentConfigurationInternal.asCapabilities(bypassUserPolicyChecks) + "]");
@@ -870,9 +916,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
mLatestManualSuggestion.dump(ipw);
ipw.decreaseIndent(); // level 2
- ipw.println("Geolocation suggestion history:");
+ ipw.println("Location algorithm event history:");
ipw.increaseIndent(); // level 2
- mLatestGeoLocationSuggestion.dump(ipw);
+ mLatestLocationAlgorithmEvent.dump(ipw);
ipw.decreaseIndent(); // level 2
ipw.println("Telephony suggestion history:");
@@ -886,6 +932,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* A method used to inspect strategy state during tests. Not intended for general use.
*/
@VisibleForTesting
+ @Nullable
public synchronized ManualTimeZoneSuggestion getLatestManualSuggestion() {
return mLatestManualSuggestion.get();
}
@@ -894,6 +941,7 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* A method used to inspect strategy state during tests. Not intended for general use.
*/
@VisibleForTesting
+ @Nullable
public synchronized QualifiedTelephonyTimeZoneSuggestion getLatestTelephonySuggestion(
int slotIndex) {
return mTelephonySuggestionsBySlotIndex.get(slotIndex);
@@ -903,8 +951,9 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
* A method used to inspect strategy state during tests. Not intended for general use.
*/
@VisibleForTesting
- public synchronized GeolocationTimeZoneSuggestion getLatestGeolocationSuggestion() {
- return mLatestGeoLocationSuggestion.get();
+ @Nullable
+ public synchronized LocationAlgorithmEvent getLatestLocationAlgorithmEvent() {
+ return mLatestLocationAlgorithmEvent.get();
}
@VisibleForTesting
@@ -917,6 +966,11 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
return mCurrentConfigurationInternal;
}
+ @VisibleForTesting
+ public synchronized TimeZoneDetectorStatus getCachedDetectorStatusForTests() {
+ return mDetectorStatus;
+ }
+
/**
* A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
*/
@@ -970,4 +1024,42 @@ public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrat
private static String formatDebugString(TimestampedValue<?> value) {
return value.getValue() + " @ " + Duration.ofMillis(value.getReferenceTimeMillis());
}
+
+ @NonNull
+ private static TimeZoneDetectorStatus createTimeZoneDetectorStatus(
+ @NonNull ConfigurationInternal currentConfigurationInternal,
+ @Nullable LocationAlgorithmEvent latestLocationAlgorithmEvent) {
+
+ int detectorStatus;
+ if (!currentConfigurationInternal.isAutoDetectionSupported()) {
+ detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_NOT_SUPPORTED;
+ } else if (currentConfigurationInternal.getAutoDetectionEnabledBehavior()) {
+ detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+ } else {
+ detectorStatus = DetectorStatusTypes.DETECTOR_STATUS_NOT_RUNNING;
+ }
+
+ TelephonyTimeZoneAlgorithmStatus telephonyAlgorithmStatus =
+ createTelephonyAlgorithmStatus(currentConfigurationInternal);
+
+ LocationTimeZoneAlgorithmStatus locationAlgorithmStatus =
+ latestLocationAlgorithmEvent == null ? LocationTimeZoneAlgorithmStatus.UNKNOWN
+ : latestLocationAlgorithmEvent.getAlgorithmStatus();
+
+ return new TimeZoneDetectorStatus(
+ detectorStatus, telephonyAlgorithmStatus, locationAlgorithmStatus);
+ }
+
+ @NonNull
+ private static TelephonyTimeZoneAlgorithmStatus createTelephonyAlgorithmStatus(
+ @NonNull ConfigurationInternal currentConfigurationInternal) {
+ int algorithmStatus;
+ if (!currentConfigurationInternal.isTelephonyDetectionSupported()) {
+ algorithmStatus = DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+ } else {
+ // The telephony detector is passive, so we treat it as "running".
+ algorithmStatus = DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+ }
+ return new TelephonyTimeZoneAlgorithmStatus(algorithmStatus);
+ }
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
index a1de2941808e..71aa10d8614a 100644
--- a/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/BinderLocationTimeZoneProvider.java
@@ -53,7 +53,7 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
}
@Override
- void onInitialize() {
+ boolean onInitialize() {
mProxy.initialize(new LocationTimeZoneProviderProxy.Listener() {
@Override
public void onReportTimeZoneProviderEvent(
@@ -71,6 +71,7 @@ class BinderLocationTimeZoneProvider extends LocationTimeZoneProvider {
handleTemporaryFailure("onProviderUnbound()");
}
});
+ return true;
}
@Override
diff --git a/services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java
new file mode 100644
index 000000000000..5d6184ec66d6
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/location/DisabledLocationTimeZoneProvider.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezonedetector.location;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+
+import java.time.Duration;
+
+/**
+ * A {@link LocationTimeZoneProvider} that provides minimal responses needed to operate correctly
+ * when there is no "real" provider configured / enabled. This is used when the platform supports
+ * more providers than are needed for an Android deployment.
+ *
+ * <p>That is, the {@link LocationTimeZoneProviderController} supports a primary and a secondary
+ * {@link LocationTimeZoneProvider}, but if only a primary is configured, the secondary provider
+ * config will marked as "disabled" and the {@link LocationTimeZoneProvider} implementation will use
+ * {@link DisabledLocationTimeZoneProvider}. The {@link DisabledLocationTimeZoneProvider} fails
+ * initialization and immediately moves to a "permanent failure" state, which ensures the {@link
+ * LocationTimeZoneProviderController} correctly categorizes it and won't attempt to use it.
+ */
+class DisabledLocationTimeZoneProvider extends LocationTimeZoneProvider {
+
+ DisabledLocationTimeZoneProvider(
+ @NonNull ProviderMetricsLogger providerMetricsLogger,
+ @NonNull ThreadingDomain threadingDomain,
+ @NonNull String providerName,
+ boolean recordStateChanges) {
+ super(providerMetricsLogger, threadingDomain, providerName, x -> x, recordStateChanges);
+ }
+
+ @Override
+ boolean onInitialize() {
+ // Fail initialization, preventing further use.
+ return false;
+ }
+
+ @Override
+ void onDestroy() {
+ }
+
+ @Override
+ void onStartUpdates(@NonNull Duration initializationTimeout,
+ @NonNull Duration eventFilteringAgeThreshold) {
+ throw new UnsupportedOperationException("Provider is disabled");
+ }
+
+ @Override
+ void onStopUpdates() {
+ throw new UnsupportedOperationException("Provider is disabled");
+ }
+
+ @Override
+ public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
+ synchronized (mSharedLock) {
+ ipw.println("{DisabledLocationTimeZoneProvider}");
+ ipw.println("mProviderName=" + mProviderName);
+ ipw.println("mCurrentState=" + mCurrentState);
+ }
+ }
+
+ @Override
+ public String toString() {
+ synchronized (mSharedLock) {
+ return "DisabledLocationTimeZoneProvider{"
+ + "mProviderName=" + mProviderName
+ + ", mCurrentState=" + mCurrentState
+ + '}';
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
index 36ab111dfccb..8d9854436e29 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerService.java
@@ -447,11 +447,18 @@ public class LocationTimeZoneManagerService extends Binder {
@NonNull
LocationTimeZoneProvider createProvider() {
- LocationTimeZoneProviderProxy proxy = createProxy();
ProviderMetricsLogger providerMetricsLogger = new RealProviderMetricsLogger(mIndex);
- return new BinderLocationTimeZoneProvider(
- providerMetricsLogger, mThreadingDomain, mName, proxy,
- mServiceConfigAccessor.getRecordStateChangesForTests());
+
+ String mode = getMode();
+ if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
+ return new DisabledLocationTimeZoneProvider(providerMetricsLogger, mThreadingDomain,
+ mName, mServiceConfigAccessor.getRecordStateChangesForTests());
+ } else {
+ LocationTimeZoneProviderProxy proxy = createBinderProxy();
+ return new BinderLocationTimeZoneProvider(
+ providerMetricsLogger, mThreadingDomain, mName, proxy,
+ mServiceConfigAccessor.getRecordStateChangesForTests());
+ }
}
@Override
@@ -460,17 +467,6 @@ public class LocationTimeZoneManagerService extends Binder {
ipw.printf("getPackageName()=%s\n", getPackageName());
}
- @NonNull
- private LocationTimeZoneProviderProxy createProxy() {
- String mode = getMode();
- if (Objects.equals(mode, PROVIDER_MODE_DISABLED)) {
- return new NullLocationTimeZoneProviderProxy(mContext, mThreadingDomain);
- } else {
- // mode == PROVIDER_MODE_OVERRIDE_ENABLED (or unknown).
- return createRealProxy();
- }
- }
-
/** Returns the mode of the provider (enabled/disabled). */
@NonNull
private String getMode() {
@@ -482,7 +478,7 @@ public class LocationTimeZoneManagerService extends Binder {
}
@NonNull
- private RealLocationTimeZoneProviderProxy createRealProxy() {
+ private RealLocationTimeZoneProviderProxy createBinderProxy() {
String providerServiceAction = mServiceAction;
boolean isTestProvider = isTestProvider();
String providerPackageName = getPackageName();
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
index 1f752f45fd51..e90a1fe34798 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerServiceState.java
@@ -19,7 +19,7 @@ package com.android.server.timezonedetector.location;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState;
import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State;
@@ -32,14 +32,14 @@ import java.util.Objects;
final class LocationTimeZoneManagerServiceState {
private final @State String mControllerState;
- @Nullable private final GeolocationTimeZoneSuggestion mLastSuggestion;
+ @Nullable private final LocationAlgorithmEvent mLastEvent;
@NonNull private final List<@State String> mControllerStates;
@NonNull private final List<ProviderState> mPrimaryProviderStates;
@NonNull private final List<ProviderState> mSecondaryProviderStates;
LocationTimeZoneManagerServiceState(@NonNull Builder builder) {
mControllerState = builder.mControllerState;
- mLastSuggestion = builder.mLastSuggestion;
+ mLastEvent = builder.mLastEvent;
mControllerStates = Objects.requireNonNull(builder.mControllerStates);
mPrimaryProviderStates = Objects.requireNonNull(builder.mPrimaryProviderStates);
mSecondaryProviderStates = Objects.requireNonNull(builder.mSecondaryProviderStates);
@@ -50,8 +50,8 @@ final class LocationTimeZoneManagerServiceState {
}
@Nullable
- public GeolocationTimeZoneSuggestion getLastSuggestion() {
- return mLastSuggestion;
+ public LocationAlgorithmEvent getLastEvent() {
+ return mLastEvent;
}
@NonNull
@@ -73,7 +73,7 @@ final class LocationTimeZoneManagerServiceState {
public String toString() {
return "LocationTimeZoneManagerServiceState{"
+ "mControllerState=" + mControllerState
- + ", mLastSuggestion=" + mLastSuggestion
+ + ", mLastEvent=" + mLastEvent
+ ", mControllerStates=" + mControllerStates
+ ", mPrimaryProviderStates=" + mPrimaryProviderStates
+ ", mSecondaryProviderStates=" + mSecondaryProviderStates
@@ -83,7 +83,7 @@ final class LocationTimeZoneManagerServiceState {
static final class Builder {
private @State String mControllerState;
- private GeolocationTimeZoneSuggestion mLastSuggestion;
+ private LocationAlgorithmEvent mLastEvent;
private List<@State String> mControllerStates;
private List<ProviderState> mPrimaryProviderStates;
private List<ProviderState> mSecondaryProviderStates;
@@ -95,8 +95,8 @@ final class LocationTimeZoneManagerServiceState {
}
@NonNull
- Builder setLastSuggestion(@NonNull GeolocationTimeZoneSuggestion lastSuggestion) {
- mLastSuggestion = Objects.requireNonNull(lastSuggestion);
+ Builder setLastEvent(@NonNull LocationAlgorithmEvent lastEvent) {
+ mLastEvent = Objects.requireNonNull(lastEvent);
return this;
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
index 60bbea77b636..cefd0b578df8 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneManagerShellCommand.java
@@ -15,6 +15,10 @@
*/
package com.android.server.timezonedetector.location;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_UNKNOWN;
import static android.app.time.LocationTimeZoneManager.DUMP_STATE_OPTION_PROTO;
import static android.app.time.LocationTimeZoneManager.NULL_PACKAGE_NAME_TOKEN;
import static android.app.time.LocationTimeZoneManager.SERVICE_NAME;
@@ -51,9 +55,14 @@ import static com.android.server.timezonedetector.location.LocationTimeZoneProvi
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
import android.app.time.GeolocationTimeZoneSuggestionProto;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.LocationTimeZoneAlgorithmStatusProto;
import android.app.time.LocationTimeZoneManagerProto;
import android.app.time.LocationTimeZoneManagerServiceStateProto;
+import android.app.time.LocationTimeZoneProviderEventProto;
+import android.app.time.TimeZoneDetectorProto;
import android.app.time.TimeZoneProviderStateProto;
import android.app.timezonedetector.TimeZoneDetector;
import android.os.ShellCommand;
@@ -62,6 +71,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
import com.android.server.timezonedetector.location.LocationTimeZoneProviderController.State;
@@ -239,19 +249,39 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
outputStream = new DualDumpOutputStream(
new IndentingPrintWriter(getOutPrintWriter(), " "));
}
- if (state.getLastSuggestion() != null) {
- GeolocationTimeZoneSuggestion lastSuggestion = state.getLastSuggestion();
- long lastSuggestionToken = outputStream.start(
- "last_suggestion", LocationTimeZoneManagerServiceStateProto.LAST_SUGGESTION);
- for (String zoneId : lastSuggestion.getZoneIds()) {
- outputStream.write(
- "zone_ids" , GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId);
+
+ if (state.getLastEvent() != null) {
+ LocationAlgorithmEvent lastEvent = state.getLastEvent();
+ long lastEventToken = outputStream.start(
+ "last_event", LocationTimeZoneManagerServiceStateProto.LAST_EVENT);
+
+ // lastEvent.algorithmStatus
+ LocationTimeZoneAlgorithmStatus algorithmStatus = lastEvent.getAlgorithmStatus();
+ long algorithmStatusToken = outputStream.start(
+ "algorithm_status", LocationTimeZoneProviderEventProto.ALGORITHM_STATUS);
+ outputStream.write("status", LocationTimeZoneAlgorithmStatusProto.STATUS,
+ convertDetectionAlgorithmStatusToEnumToProtoEnum(algorithmStatus.getStatus()));
+ outputStream.end(algorithmStatusToken);
+
+ // lastEvent.suggestion
+ if (lastEvent.getSuggestion() != null) {
+ long suggestionToken = outputStream.start(
+ "suggestion", LocationTimeZoneProviderEventProto.SUGGESTION);
+ GeolocationTimeZoneSuggestion lastSuggestion = lastEvent.getSuggestion();
+ for (String zoneId : lastSuggestion.getZoneIds()) {
+ outputStream.write(
+ "zone_ids", GeolocationTimeZoneSuggestionProto.ZONE_IDS, zoneId);
+ }
+ outputStream.end(suggestionToken);
}
- for (String debugInfo : lastSuggestion.getDebugInfo()) {
+
+ // lastEvent.debugInfo
+ for (String debugInfo : lastEvent.getDebugInfo()) {
outputStream.write(
- "debug_info", GeolocationTimeZoneSuggestionProto.DEBUG_INFO, debugInfo);
+ "debug_info", LocationTimeZoneProviderEventProto.DEBUG_INFO, debugInfo);
}
- outputStream.end(lastSuggestionToken);
+
+ outputStream.end(lastEventToken);
}
writeControllerStates(outputStream, state.getControllerStates());
@@ -330,6 +360,22 @@ class LocationTimeZoneManagerShellCommand extends ShellCommand {
}
}
+ private static int convertDetectionAlgorithmStatusToEnumToProtoEnum(
+ @DetectionAlgorithmStatus int statusEnum) {
+ switch (statusEnum) {
+ case DETECTION_ALGORITHM_STATUS_UNKNOWN:
+ return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_UNKNOWN;
+ case DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED:
+ return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_NOT_SUPPORTED;
+ case DETECTION_ALGORITHM_STATUS_NOT_RUNNING:
+ return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+ case DETECTION_ALGORITHM_STATUS_RUNNING:
+ return TimeZoneDetectorProto.DETECTION_ALGORITHM_STATUS_RUNNING;
+ default:
+ throw new IllegalArgumentException("Unknown statusEnum=" + statusEnum);
+ }
+ }
+
private void reportError(@NonNull Throwable e) {
PrintWriter errPrintWriter = getErrPrintWriter();
errPrintWriter.println("Error: ");
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
index b1fc4f561033..ba7c328f1e80 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProvider.java
@@ -16,6 +16,10 @@
package com.android.server.timezonedetector.location;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_PERMANENT_FAILURE;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION;
import static android.service.timezone.TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN;
@@ -33,6 +37,7 @@ import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
import android.os.Handler;
import android.os.SystemClock;
import android.service.timezone.TimeZoneProviderEvent;
@@ -295,6 +300,34 @@ abstract class LocationTimeZoneProvider implements Dumpable {
|| stateEnum == PROVIDER_STATE_DESTROYED;
}
+ /**
+ * Maps the internal state enum value to one of the status values exposed to the layers
+ * above.
+ */
+ public @ProviderStatus int getProviderStatus() {
+ switch (stateEnum) {
+ case PROVIDER_STATE_STARTED_INITIALIZING:
+ return PROVIDER_STATUS_NOT_READY;
+ case PROVIDER_STATE_STARTED_CERTAIN:
+ return PROVIDER_STATUS_IS_CERTAIN;
+ case PROVIDER_STATE_STARTED_UNCERTAIN:
+ return PROVIDER_STATUS_IS_UNCERTAIN;
+ case PROVIDER_STATE_PERM_FAILED:
+ // Perm failed means the providers wasn't configured, configured properly,
+ // or has removed itself for other reasons, e.g. turned-down server.
+ return PROVIDER_STATUS_NOT_PRESENT;
+ case PROVIDER_STATE_STOPPED:
+ case PROVIDER_STATE_DESTROYED:
+ // This is a "safe" default that best describes a provider that isn't in one of
+ // the more obviously mapped states.
+ return PROVIDER_STATUS_NOT_READY;
+ case PROVIDER_STATE_UNKNOWN:
+ default:
+ throw new IllegalStateException(
+ "Unknown state enum:" + prettyPrintStateEnum(stateEnum));
+ }
+ }
+
/** Returns the status reported by the provider, if available. */
@Nullable
TimeZoneProviderStatus getReportedStatus() {
@@ -415,13 +448,21 @@ abstract class LocationTimeZoneProvider implements Dumpable {
currentState = currentState.newState(PROVIDER_STATE_STOPPED, null, null, "initialize");
setCurrentState(currentState, false);
+ boolean initializationSuccess;
+ String initializationFailureReason;
// Guard against uncaught exceptions due to initialization problems.
try {
- onInitialize();
+ initializationSuccess = onInitialize();
+ initializationFailureReason = "onInitialize() returned false";
} catch (RuntimeException e) {
- warnLog("Unable to initialize the provider", e);
+ warnLog("Unable to initialize the provider due to exception", e);
+ initializationSuccess = false;
+ initializationFailureReason = "onInitialize() threw exception:" + e.getMessage();
+ }
+
+ if (!initializationSuccess) {
currentState = currentState.newState(PROVIDER_STATE_PERM_FAILED, null, null,
- "Failed to initialize: " + e.getMessage());
+ "Failed to initialize: " + initializationFailureReason);
setCurrentState(currentState, true);
}
}
@@ -429,9 +470,12 @@ abstract class LocationTimeZoneProvider implements Dumpable {
/**
* Implemented by subclasses to do work during {@link #initialize}.
+ *
+ * @return returns {@code true} on success, {@code false} if the provider should be considered
+ * "permanently failed" / disabled
*/
@GuardedBy("mSharedLock")
- abstract void onInitialize();
+ abstract boolean onInitialize();
/**
* Destroys the provider. Called after the provider is stopped. This instance will not be called
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
index a9b9884e0074..ed7ea00ec8f5 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderController.java
@@ -35,6 +35,10 @@ import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
+import android.app.time.DetectorStatusTypes;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.LocationTimeZoneAlgorithmStatus.ProviderStatus;
import android.service.timezone.TimeZoneProviderEvent;
import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
@@ -44,6 +48,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.Dumpable;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.ReferenceWithHistory;
import com.android.server.timezonedetector.location.ThreadingDomain.SingleRunnableQueue;
@@ -83,8 +88,7 @@ import java.util.Objects;
* <p>All incoming calls except for {@link
* LocationTimeZoneProviderController#dump(android.util.IndentingPrintWriter, String[])} must be
* made on the {@link android.os.Handler} thread of the {@link ThreadingDomain} passed to {@link
- * #LocationTimeZoneProviderController(ThreadingDomain, LocationTimeZoneProvider,
- * LocationTimeZoneProvider)}.
+ * #LocationTimeZoneProviderController}.
*
* <p>Provider / controller integration notes:
*
@@ -172,10 +176,10 @@ class LocationTimeZoneProviderController implements Dumpable {
@GuardedBy("mSharedLock")
private final ReferenceWithHistory<@State String> mState = new ReferenceWithHistory<>(10);
- /** Contains the last suggestion actually made, if there is one. */
+ /** Contains the last event reported, if there is one. */
@GuardedBy("mSharedLock")
@Nullable
- private GeolocationTimeZoneSuggestion mLastSuggestion;
+ private LocationAlgorithmEvent mLastEvent;
LocationTimeZoneProviderController(@NonNull ThreadingDomain threadingDomain,
@NonNull MetricsLogger metricsLogger,
@@ -213,7 +217,7 @@ class LocationTimeZoneProviderController implements Dumpable {
setState(STATE_PROVIDERS_INITIALIZING);
mPrimaryProvider.initialize(providerListener);
mSecondaryProvider.initialize(providerListener);
- setState(STATE_STOPPED);
+ setStateAndReportStatusOnlyEvent(STATE_STOPPED, "initialize()");
alterProvidersStartedStateIfRequired(
null /* oldConfiguration */, mCurrentUserConfiguration);
@@ -273,13 +277,51 @@ class LocationTimeZoneProviderController implements Dumpable {
// Enter destroyed state.
mPrimaryProvider.destroy();
mSecondaryProvider.destroy();
- setState(STATE_DESTROYED);
+ setStateAndReportStatusOnlyEvent(STATE_DESTROYED, "destroy()");
}
}
/**
- * Updates {@link #mState} if needed, and performs all the record-keeping / callbacks associated
- * with state changes.
+ * Sets the state and reports an event containing the algorithm status and a {@code null}
+ * suggestion.
+ */
+ @GuardedBy("mSharedLock")
+ private void setStateAndReportStatusOnlyEvent(@State String state, @NonNull String reason) {
+ setState(state);
+
+ final GeolocationTimeZoneSuggestion suggestion = null;
+ LocationAlgorithmEvent event =
+ new LocationAlgorithmEvent(generateCurrentAlgorithmStatus(), suggestion);
+ event.addDebugInfo(reason);
+ reportEvent(event);
+ }
+
+ /**
+ * Reports an event containing the algorithm status and the supplied suggestion.
+ */
+ @GuardedBy("mSharedLock")
+ private void reportSuggestionEvent(
+ @NonNull GeolocationTimeZoneSuggestion suggestion, @NonNull String reason) {
+ LocationTimeZoneAlgorithmStatus algorithmStatus = generateCurrentAlgorithmStatus();
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ algorithmStatus, suggestion);
+ event.addDebugInfo(reason);
+ reportEvent(event);
+ }
+
+ /**
+ * Sends an event immediately. This method updates {@link #mLastEvent}.
+ */
+ @GuardedBy("mSharedLock")
+ private void reportEvent(@NonNull LocationAlgorithmEvent event) {
+ debugLog("makeSuggestion: suggestion=" + event);
+ mCallback.sendEvent(event);
+ mLastEvent = event;
+ }
+
+ /**
+ * Updates the state if needed. This includes setting {@link #mState} and performing all the
+ * record-keeping / callbacks associated with state changes.
*/
@GuardedBy("mSharedLock")
private void setState(@State String state) {
@@ -300,17 +342,7 @@ class LocationTimeZoneProviderController implements Dumpable {
// By definition, if both providers are stopped, the controller is uncertain.
cancelUncertaintyTimeout();
- // If a previous "certain" suggestion has been made, then a new "uncertain"
- // suggestion must now be made to indicate the controller {does not / no longer has}
- // an opinion and will not be sending further updates (until at least the providers are
- // re-started).
- if (Objects.equals(mState.get(), STATE_CERTAIN)) {
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- mEnvironment.elapsedRealtimeMillis(),
- "Withdraw previous suggestion, providers are stopping: " + reason);
- makeSuggestion(suggestion, STATE_UNCERTAIN);
- }
- setState(STATE_STOPPED);
+ setStateAndReportStatusOnlyEvent(STATE_STOPPED, "Providers stopped: " + reason);
}
@GuardedBy("mSharedLock")
@@ -381,7 +413,7 @@ class LocationTimeZoneProviderController implements Dumpable {
// timeout started when the primary entered {started uncertain} should be cancelled.
if (newIsGeoDetectionExecutionEnabled) {
- setState(STATE_INITIALIZING);
+ setStateAndReportStatusOnlyEvent(STATE_INITIALIZING, "initializing()");
// Try to start the primary provider.
tryStartProvider(mPrimaryProvider, newConfiguration);
@@ -397,13 +429,11 @@ class LocationTimeZoneProviderController implements Dumpable {
ProviderState newSecondaryState = mSecondaryProvider.getCurrentState();
if (!newSecondaryState.isStarted()) {
// If both providers are {perm failed} then the controller immediately
- // reports uncertain.
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- mEnvironment.elapsedRealtimeMillis(),
- "Providers are failed:"
- + " primary=" + mPrimaryProvider.getCurrentState()
- + " secondary=" + mPrimaryProvider.getCurrentState());
- makeSuggestion(suggestion, STATE_FAILED);
+ // reports the failure.
+ String reason = "Providers are failed:"
+ + " primary=" + mPrimaryProvider.getCurrentState()
+ + " secondary=" + mPrimaryProvider.getCurrentState();
+ setStateAndReportStatusOnlyEvent(STATE_FAILED, reason);
}
}
} else {
@@ -537,12 +567,10 @@ class LocationTimeZoneProviderController implements Dumpable {
// If both providers are now terminated, then a suggestion must be sent informing the
// time zone detector that there are no further updates coming in the future.
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- mEnvironment.elapsedRealtimeMillis(),
- "Both providers are terminated:"
- + " primary=" + primaryCurrentState.provider
- + ", secondary=" + secondaryCurrentState.provider);
- makeSuggestion(suggestion, STATE_FAILED);
+ String reason = "Both providers are terminated:"
+ + " primary=" + primaryCurrentState.provider
+ + ", secondary=" + secondaryCurrentState.provider;
+ setStateAndReportStatusOnlyEvent(STATE_FAILED, reason);
}
}
@@ -615,6 +643,9 @@ class LocationTimeZoneProviderController implements Dumpable {
TimeZoneProviderSuggestion providerSuggestion = providerEvent.getSuggestion();
+ // Set the current state so it is correct when the suggestion event is created.
+ setState(STATE_CERTAIN);
+
// For the suggestion's effectiveFromElapsedMillis, use the time embedded in the provider's
// suggestion (which indicates the time when the provider detected the location used to
// establish the time zone).
@@ -623,15 +654,13 @@ class LocationTimeZoneProviderController implements Dumpable {
// this would hinder the ability for the time_zone_detector to judge which suggestions are
// based on newer information when comparing suggestions between different sources.
long effectiveFromElapsedMillis = providerSuggestion.getElapsedRealtimeMillis();
- GeolocationTimeZoneSuggestion geoSuggestion =
+ GeolocationTimeZoneSuggestion suggestion =
GeolocationTimeZoneSuggestion.createCertainSuggestion(
effectiveFromElapsedMillis, providerSuggestion.getTimeZoneIds());
-
- String debugInfo = "Event received provider=" + provider
+ String debugInfo = "Provider event received: provider=" + provider
+ ", providerEvent=" + providerEvent
+ ", suggestionCreationTime=" + mEnvironment.elapsedRealtimeMillis();
- geoSuggestion.addDebugInfo(debugInfo);
- makeSuggestion(geoSuggestion, STATE_CERTAIN);
+ reportSuggestionEvent(suggestion, debugInfo);
}
@Override
@@ -647,7 +676,7 @@ class LocationTimeZoneProviderController implements Dumpable {
+ mEnvironment.getProviderInitializationTimeoutFuzz());
ipw.println("uncertaintyDelay=" + mEnvironment.getUncertaintyDelay());
ipw.println("mState=" + mState.get());
- ipw.println("mLastSuggestion=" + mLastSuggestion);
+ ipw.println("mLastEvent=" + mLastEvent);
ipw.println("State history:");
ipw.increaseIndent(); // level 2
@@ -668,19 +697,6 @@ class LocationTimeZoneProviderController implements Dumpable {
}
}
- /**
- * Sends an immediate suggestion and enters a new state if needed. This method updates
- * mLastSuggestion and changes mStateEnum / reports the new state for metrics.
- */
- @GuardedBy("mSharedLock")
- private void makeSuggestion(@NonNull GeolocationTimeZoneSuggestion suggestion,
- @State String newState) {
- debugLog("makeSuggestion: suggestion=" + suggestion);
- mCallback.suggest(suggestion);
- mLastSuggestion = suggestion;
- setState(newState);
- }
-
/** Clears the uncertainty timeout. */
@GuardedBy("mSharedLock")
private void cancelUncertaintyTimeout() {
@@ -688,18 +704,16 @@ class LocationTimeZoneProviderController implements Dumpable {
}
/**
- * Called when a provider has become "uncertain" about the time zone.
+ * Called when a provider has reported it is "uncertain" about the time zone.
*
* <p>A provider is expected to report its uncertainty as soon as it becomes uncertain, as
* this enables the most flexibility for the controller to start other providers when there are
- * multiple ones available. The controller is therefore responsible for deciding when to make a
- * "uncertain" suggestion to the downstream time zone detector.
+ * multiple ones available. The controller is therefore responsible for deciding when to pass
+ * the "uncertain" suggestion to the downstream time zone detector.
*
* <p>This method schedules an "uncertainty" timeout (if one isn't already scheduled) to be
* triggered later if nothing else preempts it. It can be preempted if the provider becomes
- * certain (or does anything else that calls {@link
- * #makeSuggestion(GeolocationTimeZoneSuggestion, String)}) within {@link
- * Environment#getUncertaintyDelay()}. Preemption causes the scheduled
+ * certain within {@link Environment#getUncertaintyDelay()}. Preemption causes the scheduled
* "uncertainty" timeout to be cancelled. If the provider repeatedly sends uncertainty events
* within the uncertainty delay period, those events are effectively ignored (i.e. the timeout
* is not reset each time).
@@ -741,6 +755,8 @@ class LocationTimeZoneProviderController implements Dumpable {
synchronized (mSharedLock) {
long afterUncertaintyTimeoutElapsedMillis = mEnvironment.elapsedRealtimeMillis();
+ setState(STATE_UNCERTAIN);
+
// For the effectiveFromElapsedMillis suggestion property, use the
// uncertaintyStartedElapsedMillis. This is the time when the provider first reported
// uncertainty, i.e. before the uncertainty timeout.
@@ -749,30 +765,65 @@ class LocationTimeZoneProviderController implements Dumpable {
// the location_time_zone_manager finally confirms that the time zone was uncertain,
// but the suggestion property allows the information to be back-dated, which should
// help when comparing suggestions from different sources.
- GeolocationTimeZoneSuggestion suggestion = createUncertainSuggestion(
- uncertaintyStartedElapsedMillis,
- "Uncertainty timeout triggered for " + provider.getName() + ":"
- + " primary=" + mPrimaryProvider
- + ", secondary=" + mSecondaryProvider
- + ", uncertaintyStarted="
- + Duration.ofMillis(uncertaintyStartedElapsedMillis)
- + ", afterUncertaintyTimeout="
- + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis)
- + ", uncertaintyDelay=" + uncertaintyDelay
- );
- makeSuggestion(suggestion, STATE_UNCERTAIN);
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+ uncertaintyStartedElapsedMillis);
+ String debugInfo = "Uncertainty timeout triggered for " + provider.getName() + ":"
+ + " primary=" + mPrimaryProvider
+ + ", secondary=" + mSecondaryProvider
+ + ", uncertaintyStarted="
+ + Duration.ofMillis(uncertaintyStartedElapsedMillis)
+ + ", afterUncertaintyTimeout="
+ + Duration.ofMillis(afterUncertaintyTimeoutElapsedMillis)
+ + ", uncertaintyDelay=" + uncertaintyDelay;
+ reportSuggestionEvent(suggestion, debugInfo);
}
}
+ @GuardedBy("mSharedLock")
@NonNull
- private static GeolocationTimeZoneSuggestion createUncertainSuggestion(
- @ElapsedRealtimeLong long effectiveFromElapsedMillis,
- @NonNull String reason) {
- GeolocationTimeZoneSuggestion suggestion =
- GeolocationTimeZoneSuggestion.createUncertainSuggestion(
- effectiveFromElapsedMillis);
- suggestion.addDebugInfo(reason);
- return suggestion;
+ private LocationTimeZoneAlgorithmStatus generateCurrentAlgorithmStatus() {
+ @State String controllerState = mState.get();
+ ProviderState primaryProviderState = mPrimaryProvider.getCurrentState();
+ ProviderState secondaryProviderState = mSecondaryProvider.getCurrentState();
+ return createAlgorithmStatus(controllerState, primaryProviderState, secondaryProviderState);
+ }
+
+ @NonNull
+ private static LocationTimeZoneAlgorithmStatus createAlgorithmStatus(
+ @NonNull @State String controllerState,
+ @NonNull ProviderState primaryProviderState,
+ @NonNull ProviderState secondaryProviderState) {
+
+ @DetectionAlgorithmStatus int algorithmStatus =
+ mapControllerStateToDetectionAlgorithmStatus(controllerState);
+ @ProviderStatus int primaryProviderStatus = primaryProviderState.getProviderStatus();
+ @ProviderStatus int secondaryProviderStatus = secondaryProviderState.getProviderStatus();
+
+ // Neither provider is running. The algorithm is not running.
+ return new LocationTimeZoneAlgorithmStatus(algorithmStatus,
+ primaryProviderStatus, primaryProviderState.getReportedStatus(),
+ secondaryProviderStatus, secondaryProviderState.getReportedStatus());
+ }
+
+ /**
+ * Maps the internal state enum value to one of the status values exposed to the layers above.
+ */
+ private static @DetectionAlgorithmStatus int mapControllerStateToDetectionAlgorithmStatus(
+ @NonNull @State String controllerState) {
+ switch (controllerState) {
+ case STATE_INITIALIZING:
+ case STATE_PROVIDERS_INITIALIZING:
+ case STATE_CERTAIN:
+ case STATE_UNCERTAIN:
+ return DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+ case STATE_STOPPED:
+ case STATE_DESTROYED:
+ case STATE_FAILED:
+ case STATE_UNKNOWN:
+ default:
+ return DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+ }
}
/**
@@ -798,8 +849,8 @@ class LocationTimeZoneProviderController implements Dumpable {
synchronized (mSharedLock) {
LocationTimeZoneManagerServiceState.Builder builder =
new LocationTimeZoneManagerServiceState.Builder();
- if (mLastSuggestion != null) {
- builder.setLastSuggestion(mLastSuggestion);
+ if (mLastEvent != null) {
+ builder.setLastEvent(mLastEvent);
}
builder.setControllerState(mState.get())
.setStateChanges(mRecordedStates)
@@ -867,17 +918,15 @@ class LocationTimeZoneProviderController implements Dumpable {
abstract static class Callback {
@NonNull protected final ThreadingDomain mThreadingDomain;
- @NonNull protected final Object mSharedLock;
Callback(@NonNull ThreadingDomain threadingDomain) {
mThreadingDomain = Objects.requireNonNull(threadingDomain);
- mSharedLock = threadingDomain.getLockObject();
}
/**
* Suggests the latest time zone state for the device.
*/
- abstract void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion);
+ abstract void sendEvent(@NonNull LocationAlgorithmEvent event);
}
/**
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
index 0c751aaa62c7..7eb7e01b539a 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerCallbackImpl.java
@@ -19,7 +19,7 @@ package com.android.server.timezonedetector.location;
import android.annotation.NonNull;
import com.android.server.LocalServices;
-import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.TimeZoneDetectorInternal;
/**
@@ -34,11 +34,11 @@ class LocationTimeZoneProviderControllerCallbackImpl
}
@Override
- void suggest(@NonNull GeolocationTimeZoneSuggestion suggestion) {
+ void sendEvent(@NonNull LocationAlgorithmEvent event) {
mThreadingDomain.assertCurrentThread();
TimeZoneDetectorInternal timeZoneDetector =
LocalServices.getService(TimeZoneDetectorInternal.class);
- timeZoneDetector.suggestGeolocationTimeZone(suggestion);
+ timeZoneDetector.handleLocationAlgorithmEvent(event);
}
}
diff --git a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
deleted file mode 100644
index 9cb1813df6db..000000000000
--- a/services/core/java/com/android/server/timezonedetector/location/NullLocationTimeZoneProviderProxy.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.timezonedetector.location;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.os.SystemClock;
-import android.service.timezone.TimeZoneProviderEvent;
-import android.util.IndentingPrintWriter;
-
-/**
- * A {@link LocationTimeZoneProviderProxy} that provides minimal responses needed for the {@link
- * BinderLocationTimeZoneProvider} to operate correctly when there is no "real" provider
- * configured / enabled. This can be used during development / testing, or in a production build
- * when the platform supports more providers than are needed for an Android deployment.
- *
- * <p>For example, if the {@link LocationTimeZoneProviderController} supports a primary
- * and a secondary {@link LocationTimeZoneProvider}, but only a primary is configured, the secondary
- * config will be left null and the {@link LocationTimeZoneProviderProxy} implementation will be
- * defaulted to a {@link NullLocationTimeZoneProviderProxy}. The {@link
- * NullLocationTimeZoneProviderProxy} sends a "permanent failure" event immediately after being
- * started for the first time, which ensures the {@link LocationTimeZoneProviderController} won't
- * expect any further {@link TimeZoneProviderEvent}s to come from it, and won't attempt to use it
- * again.
- */
-class NullLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
-
- /** Creates the instance. */
- NullLocationTimeZoneProviderProxy(
- @NonNull Context context, @NonNull ThreadingDomain threadingDomain) {
- super(context, threadingDomain);
- }
-
- @Override
- void onInitialize() {
- // No-op
- }
-
- @Override
- void onDestroy() {
- // No-op
- }
-
- @Override
- void setRequest(@NonNull TimeZoneProviderRequest request) {
- if (request.sendUpdates()) {
- TimeZoneProviderEvent event = TimeZoneProviderEvent.createPermanentFailureEvent(
- SystemClock.elapsedRealtime(), "Provider is disabled");
- handleTimeZoneProviderEvent(event);
- }
- }
-
- @Override
- public void dump(@NonNull IndentingPrintWriter ipw, @Nullable String[] args) {
- synchronized (mSharedLock) {
- ipw.println("{NullLocationTimeZoneProviderProxy}");
- }
- }
-}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 3b7bf4880faa..715729310a06 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1386,8 +1386,38 @@ class ActivityClientController extends IActivityClientController.Stub {
}
}
+ /**
+ * Return {@code true} when the given Activity is a relative Task root. That is, the rest of
+ * the Activities in the Task should be finished when it finishes. Otherwise, return {@code
+ * false}.
+ */
+ private boolean isRelativeTaskRootActivity(ActivityRecord r, ActivityRecord taskRoot) {
+ // Not a relative root if the given Activity is not the root Activity of its TaskFragment.
+ final TaskFragment taskFragment = r.getTaskFragment();
+ if (r != taskFragment.getActivity(ar -> !ar.finishing || ar == r,
+ false /* traverseTopToBottom */)) {
+ return false;
+ }
+
+ // The given Activity is the relative Task root if its TaskFragment is a companion
+ // TaskFragment to the taskRoot (i.e. the taskRoot TF will be finished together).
+ return taskRoot.getTaskFragment().getCompanionTaskFragment() == taskFragment;
+ }
+
+ private boolean isTopActivityInTaskFragment(ActivityRecord activity) {
+ return activity.getTaskFragment().topRunningActivity() == activity;
+ }
+
+ private void requestCallbackFinish(IRequestFinishCallback callback) {
+ try {
+ callback.requestFinish();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to invoke request finish callback", e);
+ }
+ }
+
@Override
- public void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) {
+ public void onBackPressed(IBinder token, IRequestFinishCallback callback) {
final long origId = Binder.clearCallingIdentity();
try {
final Intent baseActivityIntent;
@@ -1397,20 +1427,29 @@ class ActivityClientController extends IActivityClientController.Stub {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r == null) return;
- if (mService.mWindowOrganizerController.mTaskOrganizerController
+ final Task task = r.getTask();
+ final ActivityRecord root = task.getRootActivity(false /*ignoreRelinquishIdentity*/,
+ true /*setToBottomIfNone*/);
+ final boolean isTaskRoot = r == root;
+ if (isTaskRoot) {
+ if (mService.mWindowOrganizerController.mTaskOrganizerController
.handleInterceptBackPressedOnTaskRoot(r.getRootTask())) {
- // This task is handled by a task organizer that has requested the back pressed
- // callback.
+ // This task is handled by a task organizer that has requested the back
+ // pressed callback.
+ return;
+ }
+ } else if (!isRelativeTaskRootActivity(r, root)) {
+ // Finish the Activity if the activity is not the task root or relative root.
+ requestCallbackFinish(callback);
return;
}
- final Task task = r.getTask();
- isLastRunningActivity = task.topRunningActivity() == r;
+ isLastRunningActivity = isTopActivityInTaskFragment(isTaskRoot ? root : r);
- final boolean isBaseActivity = r.mActivityComponent.equals(task.realActivity);
- baseActivityIntent = isBaseActivity ? r.intent : null;
+ final boolean isBaseActivity = root.mActivityComponent.equals(task.realActivity);
+ baseActivityIntent = isBaseActivity ? root.intent : null;
- launchedFromHome = r.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
+ launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
}
// If the activity is one of the main entry points for the application, then we should
@@ -1425,16 +1464,12 @@ class ActivityClientController extends IActivityClientController.Stub {
if (baseActivityIntent != null && isLastRunningActivity
&& ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent))
|| isLauncherActivity(baseActivityIntent.getComponent()))) {
- moveActivityTaskToBack(token, false /* nonRoot */);
+ moveActivityTaskToBack(token, true /* nonRoot */);
return;
}
// The default option for handling the back button is to finish the Activity.
- try {
- callback.requestFinish();
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to invoke request finish callback", e);
- }
+ requestCallbackFinish(callback);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8f8b57fb8cf5..23ed188bca85 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6867,6 +6867,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (r == null || r.getParent() == null) {
return INVALID_TASK_ID;
}
+ return getTaskForActivityLocked(r, onlyRoot);
+ }
+
+ static int getTaskForActivityLocked(ActivityRecord r, boolean onlyRoot) {
final Task task = r.task;
if (onlyRoot && r.compareTo(task.getRootActivity(
false /*ignoreRelinquishIdentity*/, true /*setToBottomIfNone*/)) > 0) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index a857d900d771..3ec24d5d4d7d 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2136,7 +2136,7 @@ class ActivityStarter {
mStartActivity.mUserId);
if (act != null) {
final Task task = act.getTask();
- boolean actuallyMoved = task.moveActivityToFrontLocked(act);
+ boolean actuallyMoved = task.moveActivityToFront(act);
if (actuallyMoved) {
// Only record if the activity actually moved.
mMovedToTopActivity = act;
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index 4c18d0b8a0dc..56edde09f747 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -57,7 +57,6 @@ import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.Button;
@@ -109,18 +108,13 @@ public class ImmersiveModeConfirmation {
mContext = display.getDisplayId() == DEFAULT_DISPLAY
? uiContext : uiContext.createDisplayContext(display);
mHandler = new H(looper);
- mShowDelayMs = getNavBarExitDuration() * 3;
+ mShowDelayMs = context.getResources().getInteger(R.integer.dock_enter_exit_duration) * 3L;
mPanicThresholdMs = context.getResources()
.getInteger(R.integer.config_immersive_mode_confirmation_panic);
mVrModeEnabled = vrModeEnabled;
mCanSystemBarsBeShownByUser = canSystemBarsBeShownByUser;
}
- private long getNavBarExitDuration() {
- Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit);
- return exit != null ? exit.getDuration() : 0;
- }
-
static boolean loadSetting(int currentUserId, Context context) {
final boolean wasConfirmed = sConfirmed;
sConfirmed = false;
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index ccc71bb8c537..de42c55be4b3 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -16,15 +16,21 @@
package com.android.server.wm;
+import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
+import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
+
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
+import android.hardware.display.DisplayManager;
import android.view.Display;
import android.view.Display.Mode;
import android.view.DisplayInfo;
+import android.view.Surface;
import android.view.SurfaceControl.RefreshRateRange;
import java.util.HashMap;
+import java.util.Objects;
/**
* Policy to select a lower refresh rate for the display if applicable.
@@ -154,39 +160,109 @@ class RefreshRatePolicy {
return LAYER_PRIORITY_UNSET;
}
- float getPreferredRefreshRate(WindowState w) {
+ public static class FrameRateVote {
+ float mRefreshRate;
+ @Surface.FrameRateCompatibility int mCompatibility;
+
+ FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
+ update(refreshRate, compatibility);
+ }
+
+ FrameRateVote() {
+ reset();
+ }
+
+ boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility) {
+ if (!refreshRateEquals(refreshRate) || mCompatibility != compatibility) {
+ mRefreshRate = refreshRate;
+ mCompatibility = compatibility;
+ return true;
+ }
+ return false;
+ }
+
+ boolean reset() {
+ return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof FrameRateVote)) {
+ return false;
+ }
+
+ FrameRateVote other = (FrameRateVote) o;
+ return refreshRateEquals(other.mRefreshRate)
+ && mCompatibility == other.mCompatibility;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRefreshRate, mCompatibility);
+ }
+
+ @Override
+ public String toString() {
+ return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility;
+ }
+
+ private boolean refreshRateEquals(float refreshRate) {
+ return mRefreshRate <= refreshRate + RefreshRateRange.FLOAT_TOLERANCE
+ && mRefreshRate >= refreshRate - RefreshRateRange.FLOAT_TOLERANCE;
+ }
+ }
+
+ boolean updateFrameRateVote(WindowState w) {
+ @DisplayManager.SwitchingType int refreshRateSwitchingType =
+ mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType();
+
+ // If refresh rate switching is disabled there is no point to set the frame rate on the
+ // surface as the refresh rate will be limited by display manager to a single value
+ // and SurfaceFlinger wouldn't be able to change it anyways.
+ if (refreshRateSwitchingType == SWITCHING_TYPE_NONE) {
+ return w.mFrameRateVote.reset();
+ }
+
// If app is animating, it's not able to control refresh rate because we want the animation
// to run in default refresh rate.
if (w.isAnimating(TRANSITION | PARENTS)) {
- return 0;
+ return w.mFrameRateVote.reset();
}
// If the app set a preferredDisplayModeId, the preferred refresh rate is the refresh rate
// of that mode id.
- final int preferredModeId = w.mAttrs.preferredDisplayModeId;
- if (preferredModeId > 0) {
- DisplayInfo info = w.getDisplayInfo();
- if (info != null) {
- for (Display.Mode mode : info.supportedModes) {
- if (preferredModeId == mode.getModeId()) {
- return mode.getRefreshRate();
+ if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
+ final int preferredModeId = w.mAttrs.preferredDisplayModeId;
+ if (preferredModeId > 0) {
+ DisplayInfo info = w.getDisplayInfo();
+ if (info != null) {
+ for (Display.Mode mode : info.supportedModes) {
+ if (preferredModeId == mode.getModeId()) {
+ return w.mFrameRateVote.update(mode.getRefreshRate(),
+ Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+
+ }
}
}
}
}
if (w.mAttrs.preferredRefreshRate > 0) {
- return w.mAttrs.preferredRefreshRate;
+ return w.mFrameRateVote.update(w.mAttrs.preferredRefreshRate,
+ Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
}
// If the app didn't set a preferred mode id or refresh rate, but it is part of the deny
// list, we return the low refresh rate as the preferred one.
- final String packageName = w.getOwningPackage();
- if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
- return mLowRefreshRateMode.getRefreshRate();
+ if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
+ final String packageName = w.getOwningPackage();
+ if (mHighRefreshRateDenylist.isDenylisted(packageName)) {
+ return w.mFrameRateVote.update(mLowRefreshRateMode.getRefreshRate(),
+ Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ }
}
- return 0;
+ return w.mFrameRateVote.reset();
}
float getPreferredMinRefreshRate(WindowState w) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index cdb332123fe2..b290bec8c4e0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1401,13 +1401,26 @@ class Task extends TaskFragment {
* Reorder the history task so that the passed activity is brought to the front.
* @return whether it was actually moved (vs already being top).
*/
- final boolean moveActivityToFrontLocked(ActivityRecord newTop) {
+ final boolean moveActivityToFront(ActivityRecord newTop) {
ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Removing and adding activity %s to root task at top "
+ "callers=%s", newTop, Debug.getCallers(4));
- int origDist = getDistanceFromTop(newTop);
- positionChildAtTop(newTop);
+ final TaskFragment taskFragment = newTop.getTaskFragment();
+ boolean moved;
+ if (taskFragment != this) {
+ if (taskFragment.isEmbedded() && taskFragment.getNonFinishingActivityCount() == 1) {
+ taskFragment.mClearedForReorderActivityToFront = true;
+ }
+ newTop.reparent(this, POSITION_TOP);
+ moved = true;
+ if (taskFragment.isEmbedded()) {
+ mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
+ .onActivityReparentedToTask(newTop);
+ }
+ } else {
+ moved = moveChildToFront(newTop);
+ }
updateEffectiveIntent();
- return getDistanceFromTop(newTop) != origDist;
+ return moved;
}
@Override
@@ -1575,6 +1588,11 @@ class Task extends TaskFragment {
removeChild(r, reason);
});
} else {
+ // Finish or destroy apps from the bottom to ensure that all the other activity have
+ // been finished and the top task in another task gets resumed when a top activity is
+ // removed. Otherwise, shell transitions wouldn't run because there would be no event
+ // that sets the transition ready.
+ final boolean traverseTopToBottom = !mTransitionController.isShellTransitionsEnabled();
forAllActivities((r) -> {
if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
return;
@@ -1588,7 +1606,7 @@ class Task extends TaskFragment {
} else {
r.destroyIfPossible(reason);
}
- });
+ }, traverseTopToBottom);
}
}
@@ -3075,20 +3093,6 @@ class Task extends TaskFragment {
});
}
- void positionChildAtTop(ActivityRecord child) {
- positionChildAt(child, POSITION_TOP);
- }
-
- void positionChildAt(ActivityRecord child, int position) {
- if (child == null) {
- Slog.w(TAG_WM,
- "Attempted to position of non-existing app");
- return;
- }
-
- positionChildAt(position, child, false /* includeParents */);
- }
-
void setTaskDescription(TaskDescription taskDescription) {
mTaskDescription = taskDescription;
}
@@ -4291,13 +4295,14 @@ class Task extends TaskFragment {
}
/**
- * @return true if the task is currently focused.
+ * @return {@code true} if the task is currently focused or one of its children is focused.
*/
boolean isFocused() {
if (mDisplayContent == null || mDisplayContent.mFocusedApp == null) {
return false;
}
- return mDisplayContent.mFocusedApp.getTask() == this;
+ final Task focusedTask = mDisplayContent.mFocusedApp.getTask();
+ return focusedTask == this || (focusedTask != null && focusedTask.getParent() == this);
}
/**
@@ -4317,6 +4322,8 @@ class Task extends TaskFragment {
*/
void onAppFocusChanged(boolean hasFocus) {
dispatchTaskInfoChangedIfNeeded(false /* force */);
+ final Task parentTask = getParent().asTask();
+ if (parentTask != null) parentTask.dispatchTaskInfoChangedIfNeeded(false /* force */);
}
void onPictureInPictureParamsChanged() {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index dfcad48046b0..911a8da1db99 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -224,6 +224,14 @@ class TaskFragment extends WindowContainer<WindowContainer> {
private TaskFragment mAdjacentTaskFragment;
/**
+ * Unlike the {@link mAdjacentTaskFragment}, the companion TaskFragment is not always visually
+ * adjacent to this one, but this TaskFragment will be removed by the organizer if the
+ * companion TaskFragment is removed.
+ */
+ @Nullable
+ private TaskFragment mCompanionTaskFragment;
+
+ /**
* Prevents duplicate calls to onTaskAppeared.
*/
boolean mTaskFragmentAppearedSent;
@@ -241,6 +249,12 @@ class TaskFragment extends WindowContainer<WindowContainer> {
boolean mClearedTaskFragmentForPip;
/**
+ * The last running activity of the TaskFragment was removed and added to the top-most of the
+ * Task because it was launched with FLAG_ACTIVITY_REORDER_TO_FRONT.
+ */
+ boolean mClearedForReorderActivityToFront;
+
+ /**
* When we are in the process of pausing an activity, before starting the
* next one, this variable holds the activity that is currently being paused.
*
@@ -396,6 +410,14 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
}
+ void setCompanionTaskFragment(@Nullable TaskFragment companionTaskFragment) {
+ mCompanionTaskFragment = companionTaskFragment;
+ }
+
+ TaskFragment getCompanionTaskFragment() {
+ return mCompanionTaskFragment;
+ }
+
void resetAdjacentTaskFragment() {
// Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment.
if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
@@ -1852,6 +1874,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
ActivityRecord r = topRunningActivity();
mClearedTaskForReuse = false;
mClearedTaskFragmentForPip = false;
+ mClearedForReorderActivityToFront = false;
final ActivityRecord addingActivity = child.asActivityRecord();
final boolean isAddingActivity = addingActivity != null;
@@ -2440,6 +2463,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
positionInParent,
mClearedTaskForReuse,
mClearedTaskFragmentForPip,
+ mClearedForReorderActivityToFront,
calculateMinDimension());
}
@@ -2726,6 +2750,16 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return callback.test(this) ? this : null;
}
+ /**
+ * Moves the passed child to front
+ * @return whether it was actually moved (vs already being top).
+ */
+ boolean moveChildToFront(WindowContainer newTop) {
+ int origDist = getDistanceFromTop(newTop);
+ positionChildAt(POSITION_TOP, newTop, false /* includeParents */);
+ return getDistanceFromTop(newTop) != origDist;
+ }
+
String toFullString() {
final StringBuilder sb = new StringBuilder(128);
sb.append(this);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index de12a4ef7739..a15fc1201596 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -39,6 +39,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
@@ -996,6 +997,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
tf1.setAdjacentTaskFragment(tf2);
effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ // Clear the focused app if the focused app is no longer visible after reset the
+ // adjacent TaskFragments.
+ if (tf2 == null && tf1.getDisplayContent().mFocusedApp != null
+ && tf1.hasChild(tf1.getDisplayContent().mFocusedApp)
+ && !tf1.shouldBeVisible(null /* starting */)) {
+ tf1.getDisplayContent().setFocusedApp(null);
+ }
+
final Bundle bundle = hop.getLaunchOptions();
final WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams =
bundle != null ? new WindowContainerTransaction.TaskFragmentAdjacentParams(
@@ -1109,6 +1118,22 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
effects |= sanitizeAndApplyHierarchyOp(wc, hop);
break;
}
+ case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT: {
+ final IBinder fragmentToken = hop.getContainer();
+ final IBinder companionToken = hop.getCompanionContainer();
+ final TaskFragment fragment = mLaunchTaskFragments.get(fragmentToken);
+ final TaskFragment companion = companionToken != null ? mLaunchTaskFragments.get(
+ companionToken) : null;
+ if (fragment == null || !fragment.isAttached()) {
+ final Throwable exception = new IllegalArgumentException(
+ "Not allowed to set companion on invalid fragment tokens");
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, fragment, type,
+ exception);
+ break;
+ }
+ fragment.setCompanionTaskFragment(companion);
+ break;
+ }
default: {
// The other operations may change task order so they are skipped while in lock
// task mode. The above operations are still allowed because they don't move
@@ -1652,6 +1677,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT:
enforceTaskFragmentOrganized(func, hop.getNewParent(), organizer);
break;
+ case HIERARCHY_OP_TYPE_SET_COMPANION_TASK_FRAGMENT:
+ enforceTaskFragmentOrganized(func, hop.getContainer(), organizer);
+ if (hop.getCompanionContainer() != null) {
+ enforceTaskFragmentOrganized(func, hop.getCompanionContainer(), organizer);
+ }
+ break;
case HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS:
enforceTaskFragmentOrganized(func, hop.getContainer(), organizer);
if (hop.getAdjacentRoot() != null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 86dd0b5452b5..19409b1f3636 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -24,8 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.graphics.GraphicsProtos.dumpPointProto;
-import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
-import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
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;
@@ -203,7 +201,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.gui.TouchOcclusionMode;
-import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Build;
import android.os.Debug;
@@ -261,6 +258,7 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.ToBooleanFunction;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
+import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
import com.android.server.wm.SurfaceAnimator.AnimationType;
import dalvik.annotation.optimization.NeverCompile;
@@ -792,7 +790,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* preferredDisplayModeId or is part of the high refresh rate deny list.
* The variable is cached, so we do not send too many updates to SF.
*/
- float mAppPreferredFrameRate = 0f;
+ FrameRateVote mFrameRateVote = new FrameRateVote();
static final int BLAST_TIMEOUT_DURATION = 5000; /* milliseconds */
@@ -5507,20 +5505,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mFrameRateSelectionPriority);
}
- // If refresh rate switching is disabled there is no point to set the frame rate on the
- // surface as the refresh rate will be limited by display manager to a single value
- // and SurfaceFlinger wouldn't be able to change it anyways.
- @DisplayManager.SwitchingType int refreshRateSwitchingType =
- mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType();
- if (refreshRateSwitchingType != SWITCHING_TYPE_NONE
- && refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
- final float refreshRate = refreshRatePolicy.getPreferredRefreshRate(this);
- if (mAppPreferredFrameRate != refreshRate) {
- mAppPreferredFrameRate = refreshRate;
- getPendingTransaction().setFrameRate(
- mSurfaceControl, mAppPreferredFrameRate,
- Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
- }
+ boolean voteChanged = refreshRatePolicy.updateFrameRateVote(this);
+ if (voteChanged) {
+ getPendingTransaction().setFrameRate(
+ mSurfaceControl, mFrameRateVote.mRefreshRate,
+ mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
+
}
}
@@ -6136,9 +6126,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mRedrawForSyncReported) {
return false;
}
- if (mInRelayout && mPrepareSyncSeqId > 0) {
- // The last sync seq id will return to the client, so there is no need to request the
- // client to redraw.
+ if (mInRelayout && (mPrepareSyncSeqId > 0 || (mViewVisibility == View.VISIBLE
+ && mWinAnimator.mDrawState == DRAW_PENDING))) {
+ // The client will report draw if it gets the sync seq id from relayout or it is
+ // drawing for being visible, then no need to request redraw.
return false;
}
return useBLASTSync();
diff --git a/services/core/jni/com_android_server_am_BatteryStatsService.cpp b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
index 16eaa77e0822..3678cedeb87f 100644
--- a/services/core/jni/com_android_server_am_BatteryStatsService.cpp
+++ b/services/core/jni/com_android_server_am_BatteryStatsService.cpp
@@ -98,7 +98,7 @@ class WakeupCallback : public BnSuspendCallback {
public:
binder::Status notifyWakeup(bool success,
const std::vector<std::string>& wakeupReasons) override {
- ALOGI("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
+ ALOGV("In wakeup_callback: %s", success ? "resumed from suspend" : "suspend aborted");
bool reasonsCaptured = false;
{
std::unique_lock<std::mutex> reasonsLock(mReasonsMutex, std::defer_lock);
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 20303472e8db..5d0551b4b54c 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1191,8 +1191,9 @@ bool NativeInputManager::filterInputEvent(const InputEvent* inputEvent, uint32_t
static_cast<const KeyEvent*>(inputEvent));
break;
case AINPUT_EVENT_TYPE_MOTION:
- inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
- static_cast<const MotionEvent*>(inputEvent));
+ inputEventObj =
+ android_view_MotionEvent_obtainAsCopy(env,
+ static_cast<const MotionEvent&>(*inputEvent));
break;
default:
return true; // dispatch the event normally
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index e07bc7761156..06d8e6280bef 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -18,12 +18,15 @@ package com.android.server.credentials;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.Context;
import android.credentials.CreateCredentialRequest;
+import android.credentials.CreateCredentialResponse;
import android.credentials.CredentialManager;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
+import android.os.RemoteException;
import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
@@ -35,7 +38,8 @@ import java.util.ArrayList;
* provider(s) state maintained in {@link ProviderCreateSession}.
*/
public final class CreateRequestSession extends RequestSession<CreateCredentialRequest,
- ICreateCredentialCallback> {
+ ICreateCredentialCallback>
+ implements ProviderSession.ProviderInternalCallback<CreateCredentialResponse> {
private static final String TAG = "CreateRequestSession";
CreateRequestSession(@NonNull Context context, int userId,
@@ -72,4 +76,29 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
mRequestId, mClientRequest, mIsFirstUiTurn, mClientCallingPackage),
providerDataList));
}
+
+ private void respondToClientAndFinish(CreateCredentialResponse response) {
+ Log.i(TAG, "respondToClientAndFinish");
+ try {
+ mClientCallback.onResponse(response);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ finishSession();
+ }
+
+ @Override
+ public void onProviderStatusChanged(ProviderSession.Status status,
+ ComponentName componentName) {
+ super.onProviderStatusChanged(status, componentName);
+ }
+
+ @Override
+ public void onFinalResponseReceived(ComponentName componentName,
+ CreateCredentialResponse response) {
+ Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+ if (response != null) {
+ respondToClientAndFinish(response);
+ }
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 8238632fc6c2..8a698cab92a1 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -17,16 +17,14 @@
package com.android.server.credentials;
import android.annotation.Nullable;
+import android.content.ComponentName;
import android.content.Context;
-import android.credentials.Credential;
import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
import android.credentials.IGetCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
-import android.credentials.ui.UserSelectionDialogResult;
import android.os.RemoteException;
-import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
@@ -37,7 +35,8 @@ import java.util.ArrayList;
* responses from providers, and the UX app, and updates the provider(S) state.
*/
public final class GetRequestSession extends RequestSession<GetCredentialRequest,
- IGetCredentialCallback> {
+ IGetCredentialCallback>
+ implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
private static final String TAG = "GetRequestSession";
public GetRequestSession(Context context, int userId,
@@ -67,23 +66,6 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
return providerGetSession;
}
- // TODO: Override for this method not needed once get selection logic is
- // moved to ProviderGetSession
- @Override
- public void onUiSelection(UserSelectionDialogResult selection) {
- String providerId = selection.getProviderId();
- ProviderGetSession providerSession = (ProviderGetSession) mProviders.get(providerId);
- if (providerSession != null) {
- CredentialEntry credentialEntry = providerSession.getCredentialEntry(
- selection.getEntrySubkey());
- if (credentialEntry != null && credentialEntry.getCredential() != null) {
- respondToClientAndFinish(credentialEntry.getCredential());
- }
- // TODO : Handle action chips and authentication selection
- }
- // TODO : finish session and respond to client if provider not found
- }
-
@Override
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newGetRequestInfo(
@@ -91,9 +73,26 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
providerDataList));
}
- private void respondToClientAndFinish(Credential credential) {
+ @Override // from provider session
+ public void onProviderStatusChanged(ProviderSession.Status status,
+ ComponentName componentName) {
+ super.onProviderStatusChanged(status, componentName);
+ }
+
+
+ @Override
+ public void onFinalResponseReceived(ComponentName componentName,
+ GetCredentialResponse response) {
+ Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+ if (response != null) {
+ respondToClientAndFinish(response);
+ }
+ }
+
+ private void respondToClientAndFinish(GetCredentialResponse response) {
+ Log.i(TAG, "respondToClientAndFinish");
try {
- mClientCallback.onResponse(new GetCredentialResponse(credential));
+ mClientCallback.onResponse(response);
} catch (RemoteException e) {
e.printStackTrace();
}
diff --git a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
new file mode 100644
index 000000000000..4cdc4570ba90
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.credentials.CreateCredentialResponse;
+import android.credentials.Credential;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.service.credentials.CredentialProviderService;
+import android.service.credentials.CredentialsDisplayContent;
+
+/**
+ * Helper class for setting up pending intent, and extracting objects from it.
+ *
+ * @hide
+ */
+public class PendingIntentResultHandler {
+ /** Returns true if the result is successful and may contain result extras. */
+ public static boolean isSuccessfulResponse(
+ ProviderPendingIntentResponse pendingIntentResponse) {
+ //TODO: Differentiate based on extra_error in the resultData
+ return pendingIntentResponse.getResultCode() == Activity.RESULT_OK;
+ }
+
+ /** Extracts the {@link CredentialsDisplayContent} object added to the result data. */
+ public static CredentialsDisplayContent extractCredentialsDisplayContent(Intent resultData) {
+ if (resultData == null) {
+ return null;
+ }
+ return resultData.getParcelableExtra(
+ CredentialProviderService.EXTRA_GET_CREDENTIALS_DISPLAY_CONTENT,
+ CredentialsDisplayContent.class);
+ }
+
+ /** Extracts the {@link CreateCredentialResponse} object added to the result data. */
+ public static CreateCredentialResponse extractCreateCredentialResponse(Intent resultData) {
+ if (resultData == null) {
+ return null;
+ }
+ return resultData.getParcelableExtra(
+ CredentialProviderService.EXTRA_CREATE_CREDENTIAL_RESPONSE,
+ CreateCredentialResponse.class);
+ }
+
+ /** Extracts the {@link Credential} object added to the result data. */
+ public static Credential extractCredential(Intent resultData) {
+ if (resultData == null) {
+ return null;
+ }
+ return resultData.getParcelableExtra(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL,
+ Credential.class);
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 49c416f943c1..bf37bd2097e2 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -19,10 +19,12 @@ package com.android.server.credentials;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.PendingIntent;
import android.content.Context;
-import android.credentials.Credential;
+import android.content.Intent;
import android.credentials.ui.CreateCredentialProviderData;
import android.credentials.ui.Entry;
+import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.Bundle;
import android.service.credentials.CreateCredentialRequest;
import android.service.credentials.CreateCredentialResponse;
@@ -166,35 +168,30 @@ public final class ProviderCreateSession extends ProviderSession<
}
@Override
- public void onProviderIntentResult(Bundle resultData) {
- Credential credential = resultData.getParcelable(
- CredentialProviderService.EXTRA_SAVE_CREDENTIAL,
- Credential.class);
- if (credential == null) {
- Log.i(TAG, "Credential returned from intent is null");
- return;
- }
- updateFinalCredentialResponse(credential);
- }
-
- @Override
- public void onUiEntrySelected(String entryType, String entryKey) {
- if (entryType.equals(SAVE_ENTRY_KEY)) {
- SaveEntry saveEntry = mUiSaveEntries.get(entryKey);
- if (saveEntry == null) {
- Log.i(TAG, "Save entry not found");
- return;
- }
- // TODO: Uncomment when pending intent works
- // onSaveEntrySelected(saveEntry);
+ public void onUiEntrySelected(String entryType, String entryKey,
+ ProviderPendingIntentResponse providerPendingIntentResponse) {
+ switch (entryType) {
+ case SAVE_ENTRY_KEY:
+ if (mUiSaveEntries.containsKey(entryKey)) {
+ onSaveEntrySelected(providerPendingIntentResponse);
+ } else {
+ //TODO: Handle properly
+ Log.i(TAG, "Unexpected save entry key");
+ }
+ break;
+ case REMOTE_ENTRY_KEY:
+ if (mUiRemoteEntry.first.equals(entryKey)) {
+ onRemoteEntrySelected(providerPendingIntentResponse);
+ } else {
+ //TODO: Handle properly
+ Log.i(TAG, "Unexpected remote entry key");
+ }
+ break;
+ default:
+ Log.i(TAG, "Unsupported entry type selected");
}
}
- @Override
- public void onProviderIntentCancelled() {
- //TODO (Implement)
- }
-
private List<Entry> prepareUiSaveEntries(@NonNull List<SaveEntry> saveEntries) {
Log.i(TAG, "in populateUiSaveEntries");
List<Entry> uiSaveEntries = new ArrayList<>();
@@ -204,14 +201,17 @@ public final class ProviderCreateSession extends ProviderSession<
String entryId = generateEntryId();
mUiSaveEntries.put(entryId, saveEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
- uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, saveEntry.getSlice()));
+ uiSaveEntries.add(new Entry(SAVE_ENTRY_KEY, entryId, saveEntry.getSlice(),
+ saveEntry.getPendingIntent(), setUpFillInIntent(saveEntry.getPendingIntent())));
}
return uiSaveEntries;
}
- private void updateFinalCredentialResponse(@NonNull Credential credential) {
- mFinalCredentialResponse = credential;
- updateStatusAndInvokeCallback(Status.CREDENTIAL_RECEIVED_FROM_INTENT);
+ private Intent setUpFillInIntent(PendingIntent pendingIntent) {
+ Intent intent = pendingIntent.getIntent();
+ intent.putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS,
+ mCompleteRequest.getData());
+ return intent;
}
private CreateCredentialProviderData prepareUiProviderData(List<Entry> saveEntries,
@@ -223,9 +223,20 @@ public final class ProviderCreateSession extends ProviderSession<
.build();
}
- private void onSaveEntrySelected(SaveEntry saveEntry) {
- mProviderIntentController.setupAndInvokePendingIntent(saveEntry.getPendingIntent(),
- mProviderRequest);
- setStatus(Status.PENDING_INTENT_INVOKED);
+ private void onSaveEntrySelected(ProviderPendingIntentResponse pendingIntentResponse) {
+ if (pendingIntentResponse == null) {
+ return;
+ //TODO: Handle failure if pending intent is null
+ }
+ if (PendingIntentResultHandler.isSuccessfulResponse(pendingIntentResponse)) {
+ android.credentials.CreateCredentialResponse credentialResponse =
+ PendingIntentResultHandler.extractCreateCredentialResponse(
+ pendingIntentResponse.getResultData());
+ if (credentialResponse != null) {
+ mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
+ return;
+ }
+ }
+ //TODO: Handle failure case is pending intent response does not have a credential
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 362d98167462..d63cdebe0e1b 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -20,16 +20,20 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.credentials.Credential;
import android.credentials.GetCredentialOption;
+import android.credentials.GetCredentialResponse;
import android.credentials.ui.Entry;
import android.credentials.ui.GetCredentialProviderData;
-import android.os.Bundle;
+import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.Action;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialsDisplayContent;
import android.service.credentials.GetCredentialsRequest;
import android.service.credentials.GetCredentialsResponse;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import java.util.ArrayList;
@@ -53,11 +57,17 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsRequ
// Key to be used as an entry key for a credential entry
private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
+ // Key to be used as the entry key for an action entry
+ private static final String ACTION_ENTRY_KEY = "action_key";
+ // Key to be used as the entry key for the authentication entry
+ private static final String AUTHENTICATION_ACTION_ENTRY_KEY = "authentication_action_key";
+
@NonNull
private final Map<String, CredentialEntry> mUiCredentialEntries = new HashMap<>();
@NonNull
private final Map<String, Action> mUiActionsEntries = new HashMap<>();
- private Action mAuthenticationAction = null;
+ @Nullable
+ private Pair<String, Action> mUiAuthenticationAction = null;
/** Creates a new provider session to be used by the request session. */
@Nullable public static ProviderGetSession createNewSession(
@@ -85,7 +95,8 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsRequ
List<GetCredentialOption> filteredOptions = new ArrayList<>();
for (GetCredentialOption option : clientRequest.getGetCredentialOptions()) {
if (providerCapabilities.contains(option.getType())) {
- Log.i(TAG, "In createProviderRequest - capability found : " + option.getType());
+ Log.i(TAG, "In createProviderRequest - capability found : "
+ + option.getType());
filteredOptions.add(option);
} else {
Log.i(TAG, "In createProviderRequest - capability not "
@@ -139,19 +150,47 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsRequ
}
}
- @Override // Callback from the provider intent controller class
- public void onProviderIntentResult(Bundle resultData) {
- // TODO : Implement
- }
-
- @Override
- public void onProviderIntentCancelled() {
- // TODO : Implement
- }
-
@Override // Selection call from the request provider
- protected void onUiEntrySelected(String entryType, String entryId) {
- // TODO: Implement
+ protected void onUiEntrySelected(String entryType, String entryKey,
+ ProviderPendingIntentResponse providerPendingIntentResponse) {
+ switch (entryType) {
+ case CREDENTIAL_ENTRY_KEY:
+ CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
+ if (credentialEntry == null) {
+ Log.i(TAG, "Credential entry not found");
+ //TODO: Handle properly
+ return;
+ }
+ onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
+ break;
+ case ACTION_ENTRY_KEY:
+ Action actionEntry = mUiActionsEntries.get(entryKey);
+ if (actionEntry == null) {
+ Log.i(TAG, "Action entry not found");
+ //TODO: Handle properly
+ return;
+ }
+ onActionEntrySelected(providerPendingIntentResponse);
+ break;
+ case AUTHENTICATION_ACTION_ENTRY_KEY:
+ if (mUiAuthenticationAction.first.equals(entryKey)) {
+ onAuthenticationEntrySelected(providerPendingIntentResponse);
+ } else {
+ //TODO: Handle properly
+ Log.i(TAG, "Authentication entry not found");
+ }
+ break;
+ case REMOTE_ENTRY_KEY:
+ if (mUiRemoteEntry.first.equals(entryKey)) {
+ onRemoteEntrySelected(providerPendingIntentResponse);
+ } else {
+ //TODO: Handle properly
+ Log.i(TAG, "Remote entry not found");
+ }
+ break;
+ default:
+ Log.i(TAG, "Unsupported entry type selected");
+ }
}
@Override // Call from request session to data to be shown on the UI
@@ -162,32 +201,46 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsRequ
+ mComponentName.flattenToString());
return null;
}
- GetCredentialsResponse response = getProviderResponse();
- if (response == null) {
+ if (mProviderResponse == null) {
Log.i(TAG, "In prepareUiData response null");
throw new IllegalStateException("Response must be in completion mode");
}
- if (response.getAuthenticationAction() != null) {
+ if (mProviderResponse.getAuthenticationAction() != null) {
Log.i(TAG, "In prepareUiData - top level authentication mode");
return prepareUiProviderData(null, null,
- prepareUiAuthenticationActionEntry(response.getAuthenticationAction()),
+ prepareUiAuthenticationAction(mProviderResponse.getAuthenticationAction()),
/*remoteEntry=*/null);
}
- if (response.getCredentialsDisplayContent() != null){
+ if (mProviderResponse.getCredentialsDisplayContent() != null) {
Log.i(TAG, "In prepareUiData displayContent not null");
- return prepareUiProviderData(populateUiActionEntries(
- response.getCredentialsDisplayContent().getActions()),
- prepareUiCredentialEntries(response.getCredentialsDisplayContent()
+ return prepareUiProviderData(prepareUiActionEntries(
+ mProviderResponse.getCredentialsDisplayContent().getActions()),
+ prepareUiCredentialEntries(mProviderResponse.getCredentialsDisplayContent()
.getCredentialEntries()),
- /*authenticationActionEntry=*/null, /*remoteEntry=*/null);
+ /*authenticationAction=*/null,
+ prepareUiRemoteEntry(mProviderResponse
+ .getCredentialsDisplayContent().getRemoteCredentialEntry()));
}
return null;
}
- private Entry prepareUiAuthenticationActionEntry(@NonNull Action authenticationAction) {
+ private Entry prepareUiRemoteEntry(Action remoteCredentialEntry) {
+ if (remoteCredentialEntry == null) {
+ return null;
+ }
+ String entryId = generateEntryId();
+ Entry remoteEntry = new Entry(REMOTE_ENTRY_KEY, entryId, remoteCredentialEntry.getSlice());
+ mUiRemoteEntry = new Pair<>(entryId, remoteCredentialEntry);
+ return remoteEntry;
+ }
+
+ private Entry prepareUiAuthenticationAction(@NonNull Action authenticationAction) {
String entryId = generateEntryId();
- mUiActionsEntries.put(entryId, authenticationAction);
- return new Entry(ACTION_ENTRY_KEY, entryId, authenticationAction.getSlice());
+ Entry authEntry = new Entry(
+ AUTHENTICATION_ACTION_ENTRY_KEY, entryId, authenticationAction.getSlice(),
+ authenticationAction.getPendingIntent(), /*fillInIntent=*/null);
+ mUiAuthenticationAction = new Pair<>(entryId, authenticationAction);
+ return authEntry;
}
private List<Entry> prepareUiCredentialEntries(@NonNull
@@ -200,19 +253,28 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsRequ
String entryId = generateEntryId();
mUiCredentialEntries.put(entryId, credentialEntry);
Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
- credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
- credentialEntry.getSlice()));
+ if (credentialEntry.getPendingIntent() != null) {
+ credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
+ credentialEntry.getSlice(), credentialEntry.getPendingIntent(),
+ /*fillInIntent=*/null));
+ } else if (credentialEntry.getCredential() != null) {
+ credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
+ credentialEntry.getSlice()));
+ } else {
+ Log.i(TAG, "No credential or pending intent. Should not happen.");
+ }
}
return credentialUiEntries;
}
- private List<Entry> populateUiActionEntries(@Nullable List<Action> actions) {
+ private List<Entry> prepareUiActionEntries(@Nullable List<Action> actions) {
List<Entry> actionEntries = new ArrayList<>();
for (Action action : actions) {
String entryId = UUID.randomUUID().toString();
mUiActionsEntries.put(entryId, action);
// TODO : Remove conversion of string to int after change in Entry class
- actionEntries.add(new Entry(ACTION_ENTRY_KEY, entryId, action.getSlice()));
+ actionEntries.add(new Entry(ACTION_ENTRY_KEY, entryId, action.getSlice(),
+ action.getPendingIntent(), /*fillInIntent=*/null));
}
return actionEntries;
}
@@ -224,16 +286,61 @@ public final class ProviderGetSession extends ProviderSession<GetCredentialsRequ
mComponentName.flattenToString()).setActionChips(actionEntries)
.setCredentialEntries(credentialEntries)
.setAuthenticationEntry(authenticationActionEntry)
+ .setRemoteEntry(remoteEntry)
.build();
}
+ private void onCredentialEntrySelected(CredentialEntry credentialEntry,
+ ProviderPendingIntentResponse providerPendingIntentResponse) {
+ if (credentialEntry.getCredential() != null) {
+ mCallbacks.onFinalResponseReceived(mComponentName, new GetCredentialResponse(
+ credentialEntry.getCredential()));
+ return;
+ } else if (providerPendingIntentResponse != null) {
+ if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
+ Credential credential = PendingIntentResultHandler.extractCredential(
+ providerPendingIntentResponse.getResultData());
+ if (credential != null) {
+ mCallbacks.onFinalResponseReceived(mComponentName,
+ new GetCredentialResponse(credential));
+ return;
+ }
+ }
+ // TODO: Handle other pending intent statuses
+ }
+ Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
+ // TODO: Propagate failure to client
+ }
+
+ private void onAuthenticationEntrySelected(
+ @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
+ if (providerPendingIntentResponse != null) {
+ if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
+ CredentialsDisplayContent content = PendingIntentResultHandler
+ .extractCredentialsDisplayContent(providerPendingIntentResponse
+ .getResultData());
+ if (content != null) {
+ onUpdateResponse(GetCredentialsResponse.createWithDisplayContent(content));
+ return;
+ }
+ }
+ //TODO: Other provider intent statuses
+ }
+ Log.i(TAG, "Display content not present in pending intent result");
+ // TODO: Propagate error to client
+ }
+
+ private void onActionEntrySelected(ProviderPendingIntentResponse
+ providerPendingIntentResponse) {
+ //TODO: Implement if any result expected after an action
+ }
+
+
/** Updates the response being maintained in state by this provider session. */
private void onUpdateResponse(GetCredentialsResponse response) {
mProviderResponse = response;
if (response.getAuthenticationAction() != null) {
Log.i(TAG , "updateResponse with authentication entry");
- // TODO validate authentication action
- mAuthenticationAction = response.getAuthenticationAction();
updateStatusAndInvokeCallback(Status.REQUIRES_AUTHENTICATION);
} else if (response.getCredentialsDisplayContent() != null) {
Log.i(TAG , "updateResponse with credentialEntries");
diff --git a/services/credentials/java/com/android/server/credentials/ProviderIntentController.java b/services/credentials/java/com/android/server/credentials/ProviderIntentController.java
deleted file mode 100644
index 0f2e8ecdbbc6..000000000000
--- a/services/credentials/java/com/android/server/credentials/ProviderIntentController.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.credentials;
-
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.annotation.UserIdInt;
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.ResultReceiver;
-import android.service.credentials.CreateCredentialRequest;
-import android.service.credentials.CredentialProviderService;
-import android.util.Log;
-
-/**
- * Class that invokes providers' pending intents and listens to the responses.
- */
-@SuppressLint("LongLogTag")
-public class ProviderIntentController {
- private static final String TAG = "ProviderIntentController";
- /**
- * Interface to be implemented by any class that wishes to get callbacks from the UI.
- */
- public interface ProviderIntentControllerCallback {
- /** Called when the user makes a selection. */
- void onProviderIntentResult(Bundle resultData);
- /** Called when the user cancels the UI. */
- void onProviderIntentCancelled();
- }
-
- private final int mUserId;
- private final Context mContext;
- private final ProviderIntentControllerCallback mCallback;
- private final ResultReceiver mResultReceiver = new ResultReceiver(
- new Handler(Looper.getMainLooper())) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- Log.i(TAG, "onReceiveResult in providerIntentController");
-
- if (resultCode == Activity.RESULT_OK) {
- Log.i(TAG, "onReceiveResult - ACTIVITYOK");
- mCallback.onProviderIntentResult(resultData);
- } else if (resultCode == Activity.RESULT_CANCELED) {
- Log.i(TAG, "onReceiveResult - RESULTCANCELED");
- mCallback.onProviderIntentCancelled();
- }
- // Drop unknown result
- }
- };
-
- public ProviderIntentController(@UserIdInt int userId,
- Context context,
- ProviderIntentControllerCallback callback) {
- mUserId = userId;
- mContext = context;
- mCallback = callback;
- }
-
- /** Sets up the request data and invokes the given pending intent. */
- public void setupAndInvokePendingIntent(@NonNull PendingIntent pendingIntent,
- CreateCredentialRequest request) {
- Log.i(TAG, "in invokePendingIntent");
- setupIntent(pendingIntent, request);
- Log.i(TAG, "in invokePendingIntent receiver set up");
- Log.i(TAG, "creator package: " + pendingIntent.getIntentSender()
- .getCreatorPackage());
-
- try {
- mContext.startIntentSender(pendingIntent.getIntentSender(),
- null, 0, 0, 0);
- } catch (IntentSender.SendIntentException e) {
- Log.i(TAG, "Error while invoking pending intent");
- }
-
- }
-
- private void setupIntent(PendingIntent pendingIntent, CreateCredentialRequest request) {
- pendingIntent.getIntent().putExtra(Intent.EXTRA_RESULT_RECEIVER,
- toIpcFriendlyResultReceiver(mResultReceiver));
- pendingIntent.getIntent().putExtra(
- CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST_PARAMS,
- request.getData());
- }
-
- private <T extends ResultReceiver> ResultReceiver toIpcFriendlyResultReceiver(
- T resultReceiver) {
- final Parcel parcel = Parcel.obtain();
- resultReceiver.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
-
- final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
- parcel.recycle();
-
- return ipcFriendly;
- }
-}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 14a915754863..4a07f0a4e305 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -22,9 +22,11 @@ import android.content.ComponentName;
import android.content.Context;
import android.credentials.Credential;
import android.credentials.ui.ProviderData;
-import android.os.Bundle;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.service.credentials.Action;
import android.service.credentials.CredentialProviderException;
import android.service.credentials.CredentialProviderInfo;
+import android.util.Pair;
import java.util.UUID;
@@ -33,10 +35,10 @@ import java.util.UUID;
* @param <T> The request to be sent to the provider
* @param <R> The response to be expected from the provider
*/
-public abstract class ProviderSession<T, R> implements RemoteCredentialService.ProviderCallbacks<R>,
- ProviderIntentController.ProviderIntentControllerCallback {
- // Key to be used as the entry key for an action entry
- protected static final String ACTION_ENTRY_KEY = "action_key";
+public abstract class ProviderSession<T, R>
+ implements RemoteCredentialService.ProviderCallbacks<R> {
+ // Key to be used as an entry key for a remote entry
+ protected static final String REMOTE_ENTRY_KEY = "remote_entry_key";
@NonNull protected final Context mContext;
@NonNull protected final ComponentName mComponentName;
@@ -45,17 +47,18 @@ public abstract class ProviderSession<T, R> implements RemoteCredentialService.P
@NonNull protected final int mUserId;
@NonNull protected Status mStatus = Status.NOT_STARTED;
@NonNull protected final ProviderInternalCallback mCallbacks;
- @NonNull protected final ProviderIntentController mProviderIntentController;
@Nullable protected Credential mFinalCredentialResponse;
@NonNull protected final T mProviderRequest;
@Nullable protected R mProviderResponse;
+ @Nullable protected Pair<String, Action> mUiRemoteEntry;
/**
* Returns true if the given status reflects that the provider state is ready to be shown
* on the credMan UI.
*/
public static boolean isUiInvokingStatus(Status status) {
- return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED;
+ return status == Status.CREDENTIALS_RECEIVED || status == Status.SAVE_ENTRIES_RECEIVED
+ || status == Status.REQUIRES_AUTHENTICATION;
}
/**
@@ -86,12 +89,14 @@ public abstract class ProviderSession<T, R> implements RemoteCredentialService.P
* Interface to be implemented by any class that wishes to get a callback when a particular
* provider session's status changes. Typically, implemented by the {@link RequestSession}
* class.
+ * @param <V> the type of the final response expected
*/
- public interface ProviderInternalCallback {
- /**
- * Called when status changes.
- */
+ public interface ProviderInternalCallback<V> {
+ /** Called when status changes. */
void onProviderStatusChanged(Status status, ComponentName componentName);
+
+ /** Called when the final credential to be returned to the client has been received. */
+ void onFinalResponseReceived(ComponentName componentName, V response);
}
protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
@@ -106,7 +111,6 @@ public abstract class ProviderSession<T, R> implements RemoteCredentialService.P
mUserId = userId;
mComponentName = info.getServiceInfo().getComponentName();
mRemoteCredentialService = remoteCredentialService;
- mProviderIntentController = new ProviderIntentController(userId, context, this);
}
/** Provider status at various states of the request session. */
@@ -164,6 +168,11 @@ public abstract class ProviderSession<T, R> implements RemoteCredentialService.P
mCallbacks.onProviderStatusChanged(status, mComponentName);
}
+ protected void onRemoteEntrySelected(
+ ProviderPendingIntentResponse providerPendingIntentResponse) {
+ //TODO: Implement
+ }
+
/** Get the request to be sent to the provider. */
protected T getProviderRequest() {
return mProviderRequest;
@@ -179,12 +188,6 @@ public abstract class ProviderSession<T, R> implements RemoteCredentialService.P
@Nullable protected abstract ProviderData prepareUiData();
/** Should be overridden to handle the selected entry from the UI. */
- protected abstract void onUiEntrySelected(String entryType, String entryId);
-
- @Override
- public abstract void onProviderIntentResult(Bundle resultData);
-
- @Override
- public abstract void onProviderIntentCancelled();
-
+ protected abstract void onUiEntrySelected(String entryType, String entryId,
+ ProviderPendingIntentResponse providerPendingIntentResponse);
}
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 056d0e8718be..71fc67ce5afd 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -37,8 +37,7 @@ import java.util.Map;
* Base class of a request session, that listens to UI events. This class must be extended
* every time a new response type is expected from the providers.
*/
-abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback,
- ProviderSession.ProviderInternalCallback {
+abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialManagerUiCallback{
private static final String TAG = "RequestSession";
// TODO: Revise access levels of attributes
@@ -89,7 +88,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
}
Log.i(TAG, "Provider session found");
providerSession.onUiEntrySelected(selection.getEntryKey(),
- selection.getEntrySubkey());
+ selection.getEntrySubkey(), selection.getPendingIntentProviderResponse());
}
@Override // from CredentialManagerUiCallbacks
@@ -98,8 +97,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
finishSession();
}
- @Override // from provider session
- public void onProviderStatusChanged(ProviderSession.Status status,
+ protected void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName) {
Log.i(TAG, "in onStatusChanged with status: " + status);
if (ProviderSession.isTerminatingStatus(status)) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 89cbf5324ed4..c58e8d500eb2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23,6 +23,7 @@ import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY;
import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE;
@@ -46,6 +47,7 @@ import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
+import static android.app.admin.DevicePolicyManager.EXEMPT_FROM_APP_STANDBY;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_IDS;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -330,6 +332,7 @@ import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -671,6 +674,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
private @interface CopyAccountStatus {}
/**
+ * Mapping of {@link android.app.admin.DevicePolicyManager.ApplicationExemptionConstants} to
+ * corresponding app-ops.
+ */
+ private static final Map<Integer, String> APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS =
+ new ArrayMap<>();
+ static {
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.put(
+ EXEMPT_FROM_APP_STANDBY, OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY);
+ }
+
+ /**
* Admin apps targeting Android S+ may not use
* {@link android.app.admin.DevicePolicyManager#setPasswordQuality} to set password quality
* on the {@code DevicePolicyManager} instance obtained by calling
@@ -17016,6 +17030,88 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
});
}
+ @Override
+ public void setApplicationExemptions(String packageName, int[] exemptions) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkStringNotEmpty(packageName, "Package name cannot be empty.");
+ Objects.requireNonNull(exemptions, "Application exemptions must not be null.");
+ Preconditions.checkArgument(areApplicationExemptionsValid(exemptions),
+ "Invalid application exemption constant found in application exemptions set.");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
+
+ final CallerIdentity caller = getCallerIdentity();
+ final ApplicationInfo packageInfo;
+ packageInfo = getPackageInfoWithNullCheck(packageName, caller);
+
+ for (Map.Entry<Integer, String> entry :
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) {
+ int currentMode = mInjector.getAppOpsManager().unsafeCheckOpNoThrow(
+ entry.getValue(), packageInfo.uid, packageInfo.packageName);
+ int newMode = ArrayUtils.contains(exemptions, entry.getKey())
+ ? MODE_ALLOWED : MODE_DEFAULT;
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ if (currentMode != newMode) {
+ mInjector.getAppOpsManager()
+ .setMode(entry.getValue(),
+ packageInfo.uid,
+ packageName,
+ newMode);
+ }
+ });
+ }
+ }
+
+ @Override
+ public int[] getApplicationExemptions(String packageName) {
+ if (!mHasFeature) {
+ return new int[0];
+ }
+ Preconditions.checkStringNotEmpty(packageName, "Package name cannot be empty.");
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS));
+
+ final CallerIdentity caller = getCallerIdentity();
+ final ApplicationInfo packageInfo;
+ packageInfo = getPackageInfoWithNullCheck(packageName, caller);
+
+ IntArray appliedExemptions = new IntArray(0);
+ for (Map.Entry<Integer, String> entry :
+ APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.entrySet()) {
+ if (mInjector.getAppOpsManager().unsafeCheckOpNoThrow(
+ entry.getValue(), packageInfo.uid, packageInfo.packageName) == MODE_ALLOWED) {
+ appliedExemptions.add(entry.getKey());
+ }
+ }
+ return appliedExemptions.toArray();
+ }
+
+ private ApplicationInfo getPackageInfoWithNullCheck(String packageName, CallerIdentity caller) {
+ final ApplicationInfo packageInfo =
+ mInjector.getPackageManagerInternal().getApplicationInfo(
+ packageName,
+ /* flags= */ 0,
+ caller.getUid(),
+ caller.getUserId());
+ if (packageInfo == null) {
+ throw new ServiceSpecificException(
+ DevicePolicyManager.ERROR_PACKAGE_NAME_NOT_FOUND,
+ "Package name not found.");
+ }
+ return packageInfo;
+ }
+
+ private boolean areApplicationExemptionsValid(int[] exemptions) {
+ for (int exemption : exemptions) {
+ if (!APPLICATION_EXEMPTION_CONSTANTS_TO_APP_OPS.containsKey(exemption)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private boolean isCallingFromPackage(String packageName, int callingUid) {
return mInjector.binderWithCleanCallingIdentity(() -> {
try {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index d406e300a0eb..433c170cfc92 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1798,17 +1798,18 @@ public final class SystemServer implements Dumpable {
dpms = mSystemServiceManager.startService(DevicePolicyManagerService.Lifecycle.class);
t.traceEnd();
- if (!isWatch) {
- t.traceBegin("StartStatusBarManagerService");
- try {
- statusBar = new StatusBarManagerService(context);
- ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false,
- DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
- } catch (Throwable e) {
- reportWtf("starting StatusBarManagerService", e);
+ t.traceBegin("StartStatusBarManagerService");
+ try {
+ statusBar = new StatusBarManagerService(context);
+ if (!isWatch) {
+ statusBar.publishGlobalActionsProvider();
}
- t.traceEnd();
+ ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar, false,
+ DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
+ } catch (Throwable e) {
+ reportWtf("starting StatusBarManagerService", e);
}
+ t.traceEnd();
if (deviceHasConfigString(context,
R.string.config_defaultMusicRecognitionService)) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 0f7c0d73ad08..2f6b07bfb6f7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -466,7 +466,7 @@ public class BroadcastQueueModernImplTest {
@Test
public void testRunnableAt_Cached_Interactive() {
final BroadcastOptions options = BroadcastOptions.makeBasic();
- options.setInteractiveBroadcast(true);
+ options.setInteractive(true);
doRunnableAt_Cached(makeBroadcastRecord(makeMockIntent(), options,
List.of(makeMockRegisteredReceiver()), null, false), REASON_CONTAINS_INTERACTIVE);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS b/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS
new file mode 100644
index 000000000000..d99779e3d9da
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/OWNERS
@@ -0,0 +1 @@
+include /services/backup/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index dc49a94eb5c5..4c28c51f7e62 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -144,7 +144,7 @@ public final class DisplayPowerController2Test {
SensorManager sensorManager) {
return new DisplayPowerProximityStateController(wakelockController,
displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
- sensorManager);
+ sensorManager, /* injector= */ null);
}
};
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
new file mode 100644
index 000000000000..6e91b249b490
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.test.TestLooper;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.testutils.OffsettableClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class DisplayPowerProximityStateControllerTest {
+ @Mock
+ WakelockController mWakelockController;
+
+ @Mock
+ DisplayDeviceConfig mDisplayDeviceConfig;
+
+ @Mock
+ Runnable mNudgeUpdatePowerState;
+
+ @Mock
+ SensorManager mSensorManager;
+
+ private Sensor mProximitySensor;
+ private OffsettableClock mClock;
+ private TestLooper mTestLooper;
+ private SensorEventListener mSensorEventListener;
+ private DisplayPowerProximityStateController mDisplayPowerProximityStateController;
+
+ @Before
+ public void before() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mClock = new OffsettableClock.Stopped();
+ mTestLooper = new TestLooper(mClock::now);
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_PROXIMITY;
+ // This is kept null because currently there is no way to define a sensor
+ // name in TestUtils
+ name = null;
+ }
+ });
+ setUpProxSensor();
+ DisplayPowerProximityStateController.Injector injector =
+ new DisplayPowerProximityStateController.Injector() {
+ @Override
+ DisplayPowerProximityStateController.Clock createClock() {
+ return new DisplayPowerProximityStateController.Clock() {
+ @Override
+ public long uptimeMillis() {
+ return mClock.now();
+ }
+ };
+ }
+ };
+ mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+ mNudgeUpdatePowerState, 0,
+ mSensorManager, injector);
+ mSensorEventListener = mDisplayPowerProximityStateController.getProximitySensorListener();
+ }
+
+ @Test
+ public void updatePendingProximityRequestsWorksAsExpectedWhenPending() {
+ // Set the system to pending wait for proximity
+ assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ assertTrue(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Update the pending proximity wait request
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertTrue(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+ }
+
+ @Test
+ public void updatePendingProximityRequestsWorksAsExpectedWhenNotPending() {
+ // Will not wait or be in the pending wait state of not already pending
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+ }
+
+ @Test
+ public void updatePendingProximityRequestsWorksAsExpectedWhenPendingAndProximityIgnored()
+ throws Exception {
+ // Set the system to the state where it will ignore proximity unless changed
+ enableProximitySensor();
+ emitAndValidatePositiveProximityEvent();
+ mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+ advanceTime(1);
+ assertTrue(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ verify(mNudgeUpdatePowerState, times(2)).run();
+
+ // Do not set the system to pending wait for proximity
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Set the system to pending wait for proximity. But because the proximity is being
+ // ignored, it will not wait or not set the pending wait
+ assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+ }
+
+ @Test
+ public void cleanupDisablesTheProximitySensor() {
+ enableProximitySensor();
+ mDisplayPowerProximityStateController.cleanup();
+ verify(mSensorManager).unregisterListener(
+ mSensorEventListener);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ @Test
+ public void isProximitySensorAvailableReturnsTrueWhenAvailable() {
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void isProximitySensorAvailableReturnsFalseWhenNotAvailable() {
+ when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = null;
+ name = null;
+ }
+ });
+ mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
+ mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
+ mNudgeUpdatePowerState, 1,
+ mSensorManager, null);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
+ DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
+ when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
+ new DisplayDeviceConfig.SensorData() {
+ {
+ type = Sensor.STRING_TYPE_PROXIMITY;
+ name = null;
+ }
+ });
+ Sensor newProxSensor = TestUtils.createSensor(
+ Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
+ when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(List.of(newProxSensor));
+ mDisplayPowerProximityStateController.notifyDisplayDeviceChanged(
+ updatedDisplayDeviceConfig);
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorAvailable());
+ }
+
+ @Test
+ public void setPendingWaitForNegativeProximityLockedWorksAsExpected() {
+ // Doesn't do anything not asked to wait
+ assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ false));
+ assertFalse(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Sets pending wait negative proximity if not already waiting
+ assertTrue(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ assertTrue(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ // Will not set pending wait negative proximity if already waiting
+ assertFalse(mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(
+ true));
+ assertTrue(
+ mDisplayPowerProximityStateController.getPendingWaitForNegativeProximityLocked());
+
+ }
+
+ @Test
+ public void evaluateProximityStateWhenRequestedUseOfProximitySensor() throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Again evaluate the proximity state, with system having positive proximity
+ setScreenOffBecauseOfPositiveProximityState();
+ }
+
+ @Test
+ public void evaluateProximityStateWhenScreenOffBecauseOfPositiveProximity() throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Again evaluate the proximity state, with system having positive proximity
+ setScreenOffBecauseOfPositiveProximityState();
+
+ // Set the system to pending wait for proximity
+ mDisplayPowerProximityStateController.setPendingWaitForNegativeProximityLocked(true);
+ // Update the pending proximity wait request
+ mDisplayPowerProximityStateController.updatePendingProximityRequestsLocked();
+
+ // Start ignoring proximity sensor
+ mDisplayPowerProximityStateController.ignoreProximitySensorUntilChangedInternal();
+ // Re-evaluate the proximity state, such that the system is detecting the positive
+ // proximity, and screen is off because of that
+ when(mWakelockController.getOnProximityNegativeRunnable()).thenReturn(mock(Runnable.class));
+ mDisplayPowerProximityStateController.updateProximityState(mock(
+ DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+ assertTrue(
+ mDisplayPowerProximityStateController
+ .shouldSkipRampBecauseOfProximityChangeToNegative());
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_NEGATIVE);
+ }
+
+ @Test
+ public void evaluateProximityStateWhenDisplayIsTurningOff() throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Again evaluate the proximity state, with system having positive proximity
+ setScreenOffBecauseOfPositiveProximityState();
+
+ // Re-evaluate the proximity state, such that the system is detecting the positive
+ // proximity, and screen is off because of that
+ mDisplayPowerProximityStateController.updateProximityState(mock(
+ DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_OFF);
+ verify(mSensorManager).unregisterListener(
+ mSensorEventListener);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ @Test
+ public void evaluateProximityStateNotWaitingForNegativeProximityAndNotUsingProxSensor()
+ throws Exception {
+ // Enable the proximity sensor
+ enableProximitySensor();
+
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ emitAndValidatePositiveProximityEvent();
+
+ // Re-evaluate the proximity state, such that the system is detecting the positive
+ // proximity, and screen is off because of that
+ mDisplayPowerProximityStateController.updateProximityState(mock(
+ DisplayManagerInternal.DisplayPowerRequest.class), Display.STATE_ON);
+ verify(mSensorManager).unregisterListener(
+ mSensorEventListener);
+ assertFalse(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.getWaitingForNegativeProximity());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_UNKNOWN);
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ private void advanceTime(long timeMs) {
+ mClock.fastForward(timeMs);
+ mTestLooper.dispatchAll();
+ }
+
+ private void setUpProxSensor() throws Exception {
+ mProximitySensor = TestUtils.createSensor(
+ Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 5.0f);
+ when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
+ .thenReturn(List.of(mProximitySensor));
+ }
+
+ private void emitAndValidatePositiveProximityEvent() throws Exception {
+ // Emit a positive proximity event to move the system to a state to mimic a scenario
+ // where the system is in positive proximity
+ when(mWakelockController.releaseWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE)).thenReturn(true);
+ mSensorEventListener.onSensorChanged(TestUtils.createSensorEvent(mProximitySensor, 4));
+ verify(mSensorManager).registerListener(mSensorEventListener,
+ mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+ mDisplayPowerProximityStateController.getHandler());
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_DEBOUNCE);
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertEquals(mDisplayPowerProximityStateController.getProximity(),
+ DisplayPowerProximityStateController.PROXIMITY_POSITIVE);
+ verify(mNudgeUpdatePowerState).run();
+ assertEquals(mDisplayPowerProximityStateController.getPendingProximityDebounceTime(), -1);
+ }
+
+ // Call evaluateProximityState with the request for using the proximity sensor. This will
+ // register the proximity sensor listener, which will be needed for mocking positive
+ // proximity scenarios.
+ private void enableProximitySensor() {
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.useProximitySensor = true;
+ mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ verify(mSensorManager).registerListener(
+ mSensorEventListener,
+ mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+ mDisplayPowerProximityStateController.getHandler());
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertFalse(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+ verifyZeroInteractions(mWakelockController);
+ }
+
+ private void setScreenOffBecauseOfPositiveProximityState() {
+ // Prepare a request to indicate that the proximity sensor is to be used
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ displayPowerRequest.useProximitySensor = true;
+
+ Runnable onProximityPositiveRunnable = mock(Runnable.class);
+ when(mWakelockController.getOnProximityPositiveRunnable()).thenReturn(
+ onProximityPositiveRunnable);
+
+ mDisplayPowerProximityStateController.updateProximityState(displayPowerRequest,
+ Display.STATE_ON);
+ verify(mSensorManager).registerListener(
+ mSensorEventListener,
+ mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL,
+ mDisplayPowerProximityStateController.getHandler());
+ assertTrue(mDisplayPowerProximityStateController.isProximitySensorEnabled());
+ assertFalse(mDisplayPowerProximityStateController.shouldIgnoreProximityUntilChanged());
+ assertTrue(mDisplayPowerProximityStateController.isScreenOffBecauseOfProximity());
+ verify(mWakelockController).acquireWakelock(
+ WakelockController.WAKE_LOCK_PROXIMITY_POSITIVE);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index 6279b87eb603..6e4d21456cd0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -17,6 +17,7 @@
package com.android.server.job;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -34,16 +35,20 @@ import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
+import android.app.IActivityManager;
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -51,6 +56,8 @@ import android.content.pm.IPackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArrayMap;
@@ -149,12 +156,14 @@ public final class JobConcurrencyManagerTest {
R.bool.config_jobSchedulerRestrictBackgroundUser);
when(mContext.getResources()).thenReturn(mResources);
doReturn(mContext).when(jobSchedulerService).getTestableContext();
+ doReturn(jobSchedulerService).when(jobSchedulerService).getLock();
mConfigBuilder = new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
doAnswer((Answer<DeviceConfig.Properties>) invocationOnMock -> mConfigBuilder.build())
.when(() -> DeviceConfig.getProperties(eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER)));
mPendingJobQueue = new PendingJobQueue();
doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue();
doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
+ doReturn(mock(PowerManager.class)).when(mContext).getSystemService(PowerManager.class);
mInjector = new InjectorForTest();
doAnswer((Answer<Long>) invocationOnMock -> {
Object[] args = invocationOnMock.getArguments();
@@ -171,6 +180,16 @@ public final class JobConcurrencyManagerTest {
createCurrentUser(true);
mNextUserId = 10;
mJobConcurrencyManager.mGracePeriodObserver = mGracePeriodObserver;
+
+ IActivityManager activityManager = ActivityManager.getService();
+ spyOn(activityManager);
+ try {
+ doNothing().when(activityManager).registerUserSwitchObserver(any(), anyString());
+ } catch (RemoteException e) {
+ fail("registerUserSwitchObserver threw exception: " + e.getMessage());
+ }
+
+ mJobConcurrencyManager.onSystemReady();
}
@After
@@ -188,13 +207,16 @@ public final class JobConcurrencyManagerTest {
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
- final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
- .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
assertEquals(0, preferredUidOnly.size());
assertEquals(0, stoppable.size());
- assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(0, assignmentInfo.numRunningTopEj);
}
@Test
@@ -207,13 +229,16 @@ public final class JobConcurrencyManagerTest {
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
- final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
- .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, idle.size());
assertEquals(0, preferredUidOnly.size());
assertEquals(0, stoppable.size());
- assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(0, assignmentInfo.numRunningTopEj);
}
@Test
@@ -230,13 +255,45 @@ public final class JobConcurrencyManagerTest {
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
- final long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
- .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
assertEquals(0, idle.size());
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
assertEquals(0, stoppable.size());
- assertEquals(0, minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(0, assignmentInfo.numRunningTopEj);
+ }
+
+ @Test
+ public void testPrepareForAssignmentDetermination_onlyRunningTopEjs() {
+ for (int i = 0; i < JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT; ++i) {
+ JobStatus job = createJob(mDefaultUserId * UserHandle.PER_USER_RANGE + i);
+ job.startedAsExpeditedJob = true;
+ job.lastEvaluatedBias = JobInfo.BIAS_TOP_APP;
+ mJobConcurrencyManager.addRunningJobForTesting(job);
+ }
+
+ for (int i = 0; i < mInjector.contexts.size(); ++i) {
+ doReturn(i % 2 == 0).when(mInjector.contexts.keyAt(i)).isWithinExecutionGuaranteeTime();
+ }
+
+ final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
+ final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
+ final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
+
+ assertEquals(0, idle.size());
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, preferredUidOnly.size());
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT / 2, stoppable.size());
+ assertEquals(0, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
+ assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT,
+ assignmentInfo.numRunningTopEj);
}
@Test
@@ -257,11 +314,13 @@ public final class JobConcurrencyManagerTest {
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
- mJobConcurrencyManager
- .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
mJobConcurrencyManager
.determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
- Long.MAX_VALUE);
+ assignmentInfo);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, changed.size());
for (int i = changed.size() - 1; i >= 0; --i) {
@@ -301,15 +360,17 @@ public final class JobConcurrencyManagerTest {
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
- long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
- .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
- assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
+ assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
mJobConcurrencyManager
.determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
- minPreferredUidOnlyWaitingTimeMs);
+ assignmentInfo);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
assertEquals(0, changed.size());
@@ -350,15 +411,17 @@ public final class JobConcurrencyManagerTest {
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
- long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
- .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
- assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
+ assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
mJobConcurrencyManager
.determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
- minPreferredUidOnlyWaitingTimeMs);
+ assignmentInfo);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
for (int i = changed.size() - 1; i >= 0; --i) {
@@ -404,15 +467,17 @@ public final class JobConcurrencyManagerTest {
final ArraySet<JobConcurrencyManager.ContextAssignment> idle = new ArraySet<>();
final List<JobConcurrencyManager.ContextAssignment> preferredUidOnly = new ArrayList<>();
final List<JobConcurrencyManager.ContextAssignment> stoppable = new ArrayList<>();
+ final JobConcurrencyManager.AssignmentInfo assignmentInfo =
+ new JobConcurrencyManager.AssignmentInfo();
- long minPreferredUidOnlyWaitingTimeMs = mJobConcurrencyManager
- .prepareForAssignmentDeterminationLocked(idle, preferredUidOnly, stoppable);
- assertEquals(remainingTimeMs, minPreferredUidOnlyWaitingTimeMs);
+ mJobConcurrencyManager.prepareForAssignmentDeterminationLocked(
+ idle, preferredUidOnly, stoppable, assignmentInfo);
+ assertEquals(remainingTimeMs, assignmentInfo.minPreferredUidOnlyWaitingTimeMs);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
mJobConcurrencyManager
.determineAssignmentsLocked(changed, idle, preferredUidOnly, stoppable,
- minPreferredUidOnlyWaitingTimeMs);
+ assignmentInfo);
assertEquals(JobConcurrencyManager.STANDARD_CONCURRENCY_LIMIT, preferredUidOnly.size());
// Depending on iteration order, we may create 1 or 2 contexts.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
index 7ccd6d993f00..e0662c44b972 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
@@ -51,6 +51,7 @@ class DeletePackageHelperTest {
mUserManagerInternal = rule.mocks().injector.userManagerInternal
whenever(mUserManagerInternal.getUserIds()).thenReturn(intArrayOf(0, 1))
+ whenever(mUserManagerInternal.getUserTypesForStatsd(any())).thenReturn(intArrayOf(1, 1))
mPms = createPackageManagerService()
doAnswer { false }.`when`(mPms).isPackageDeviceAdmin(any(), any())
diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
index 42c4129513d0..653ed1a144c0 100644
--- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
@@ -23,6 +23,7 @@ import android.app.job.JobScheduler;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemProperties;
@@ -36,8 +37,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.FileDescriptor;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
public class BinaryTransparencyServiceTest {
@@ -96,7 +96,7 @@ public class BinaryTransparencyServiceTest {
@Test
public void getApexInfo_postInitialize_returnsValidEntries() throws RemoteException {
prepApexInfo();
- Map result = mTestInterface.getApexInfo();
+ List result = mTestInterface.getApexInfo();
Assert.assertNotNull("Apex info map should not be null", result);
Assert.assertFalse("Apex info map should not be empty", result.isEmpty());
}
@@ -105,13 +105,18 @@ public class BinaryTransparencyServiceTest {
public void getApexInfo_postInitialize_returnsActualApexs()
throws RemoteException, PackageManager.NameNotFoundException {
prepApexInfo();
- Map result = mTestInterface.getApexInfo();
+ List resultList = mTestInterface.getApexInfo();
PackageManager pm = mContext.getPackageManager();
Assert.assertNotNull(pm);
- HashMap<PackageInfo, String> castedResult = (HashMap<PackageInfo, String>) result;
- for (PackageInfo packageInfo : castedResult.keySet()) {
- Assert.assertTrue(packageInfo.packageName + "is not an APEX!", packageInfo.isApex);
+ List<Bundle> castedResult = (List<Bundle>) resultList;
+ for (Bundle resultBundle : castedResult) {
+ PackageInfo resultPackageInfo = resultBundle.getParcelable(
+ BinaryTransparencyService.BUNDLE_PACKAGE_INFO, PackageInfo.class);
+ Assert.assertNotNull("PackageInfo for APEX should not be null",
+ resultPackageInfo);
+ Assert.assertTrue(resultPackageInfo.packageName + "is not an APEX!",
+ resultPackageInfo.isApex);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index 0cff4f14bf23..bb0063427339 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -125,12 +125,9 @@ public class ALSProbeTest {
mProbe.destroy();
mProbe.enable();
- AtomicInteger lux = new AtomicInteger(10);
- mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
-
verify(mSensorManager, never()).registerListener(any(), any(), anyInt());
verifyNoMoreInteractions(mSensorManager);
- assertThat(lux.get()).isLessThan(0);
+ assertThat(mProbe.getMostRecentLux()).isLessThan(0);
}
@Test
@@ -323,15 +320,27 @@ public class ALSProbeTest {
}
@Test
- public void testNoNextLuxWhenDestroyed() {
+ public void testDestroyAllowsAwaitLuxExactlyOnce() {
+ final float lastValue = 5.5f;
mProbe.destroy();
- AtomicInteger lux = new AtomicInteger(-20);
+ AtomicInteger lux = new AtomicInteger(10);
mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
- assertThat(lux.get()).isEqualTo(-1);
- verify(mSensorManager, never()).registerListener(
+ verify(mSensorManager).registerListener(
mSensorEventListenerCaptor.capture(), any(), anyInt());
+ mSensorEventListenerCaptor.getValue().onSensorChanged(
+ new SensorEvent(mLightSensor, 1, 1, new float[]{lastValue}));
+
+ assertThat(lux.get()).isEqualTo(Math.round(lastValue));
+ verify(mSensorManager).unregisterListener(eq(mSensorEventListenerCaptor.getValue()));
+
+ lux.set(22);
+ mProbe.enable();
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+ mProbe.enable();
+
+ assertThat(lux.get()).isEqualTo(Math.round(lastValue));
verifyNoMoreInteractions(mSensorManager);
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 5fda3d6b36ab..c715a217f221 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -16,6 +16,9 @@
package com.android.server.companion.virtual;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
import static com.google.common.truth.Truth.assertThat;
@@ -44,6 +47,7 @@ import android.app.WindowConfiguration;
import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDeviceActivityListener;
+import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
@@ -240,6 +244,55 @@ public class VirtualDeviceManagerServiceTest {
mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
mActivityListener, mRunningAppsChangedCallback, params);
+ mVdms.addVirtualDevice(mDeviceImpl);
+ }
+
+ @Test
+ public void getDevicePolicy_invalidDeviceId_returnsDefault() {
+ assertThat(
+ mLocalService.getDevicePolicy(
+ VirtualDeviceManager.INVALID_DEVICE_ID, POLICY_TYPE_SENSORS))
+ .isEqualTo(DEVICE_POLICY_DEFAULT);
+ }
+
+ @Test
+ public void getDevicePolicy_defaultDeviceId_returnsDefault() {
+ assertThat(
+ mLocalService.getDevicePolicy(
+ VirtualDeviceManager.DEFAULT_DEVICE_ID, POLICY_TYPE_SENSORS))
+ .isEqualTo(DEVICE_POLICY_DEFAULT);
+ }
+
+ @Test
+ public void getDevicePolicy_nonExistentDeviceId_returnsDefault() {
+ assertThat(
+ mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS))
+ .isEqualTo(DEVICE_POLICY_DEFAULT);
+ }
+
+ @Test
+ public void getDevicePolicy_unspecifiedPolicy_returnsDefault() {
+ assertThat(
+ mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
+ .isEqualTo(DEVICE_POLICY_DEFAULT);
+ }
+
+ @Test
+ public void getDevicePolicy_returnsCustom() {
+ VirtualDeviceParams params = new VirtualDeviceParams
+ .Builder()
+ .setBlockedActivities(getBlockedActivities())
+ .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+ .build();
+ mDeviceImpl = new VirtualDeviceImpl(mContext,
+ mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
+ mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
+ mActivityListener, mRunningAppsChangedCallback, params);
+ mVdms.addVirtualDevice(mDeviceImpl);
+
+ assertThat(
+ mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
+ .isEqualTo(DEVICE_POLICY_CUSTOM);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
index 77f1e24ee771..036b6df92ef9 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -37,6 +37,8 @@ public class VirtualDeviceParamsTest {
VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
.setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
.setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
+ .addDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS,
+ VirtualDeviceParams.DEVICE_POLICY_CUSTOM)
.build();
Parcel parcel = Parcel.obtain();
originalParams.writeToParcel(parcel, 0);
@@ -47,5 +49,7 @@ public class VirtualDeviceParamsTest {
assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
assertThat(params.getUsersWithMatchingAccounts())
.containsExactly(UserHandle.of(123), UserHandle.of(456));
+ assertThat(params.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS))
+ .isEqualTo(VirtualDeviceParams.DEVICE_POLICY_CUSTOM);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/TestUtils.java b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
index 0454587bfefe..a419b3f80aac 100644
--- a/services/tests/servicestests/src/com/android/server/display/TestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/display/TestUtils.java
@@ -51,6 +51,12 @@ public final class TestUtils {
}
}
+ public static void setMaximumRange(Sensor sensor, float maximumRange) throws Exception {
+ Method setter = Sensor.class.getDeclaredMethod("setRange", Float.TYPE, Float.TYPE);
+ setter.setAccessible(true);
+ setter.invoke(sensor, maximumRange, 1);
+ }
+
public static Sensor createSensor(int type, String strType) throws Exception {
Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
constr.setAccessible(true);
@@ -59,6 +65,16 @@ public final class TestUtils {
return sensor;
}
+ public static Sensor createSensor(int type, String strType, float maximumRange)
+ throws Exception {
+ Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor();
+ constr.setAccessible(true);
+ Sensor sensor = constr.newInstance();
+ setSensorType(sensor, type, strType);
+ setMaximumRange(sensor, maximumRange);
+ return sensor;
+ }
+
/**
* Create a custom {@link DisplayAddress} to ensure we're not relying on any specific
* display-address implementation in our code. Intentionally uses default object (reference)
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
index fabf535b729a..d332b3081fdf 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java
@@ -39,8 +39,6 @@ public final class BrightnessEventTest {
getReason(BrightnessReason.REASON_DOZE, BrightnessReason.MODIFIER_LOW_POWER));
mBrightnessEvent.setPhysicalDisplayId("test");
mBrightnessEvent.setLux(100.0f);
- mBrightnessEvent.setFastAmbientLux(90.0f);
- mBrightnessEvent.setSlowAmbientLux(85.0f);
mBrightnessEvent.setPreThresholdLux(150.0f);
mBrightnessEvent.setTime(System.currentTimeMillis());
mBrightnessEvent.setInitialBrightness(25.0f);
@@ -50,6 +48,7 @@ public final class BrightnessEventTest {
mBrightnessEvent.setRbcStrength(-1);
mBrightnessEvent.setThermalMax(0.65f);
mBrightnessEvent.setPowerFactor(0.2f);
+ mBrightnessEvent.setWasShortTermModelActive(true);
mBrightnessEvent.setHbmMode(BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
mBrightnessEvent.setFlags(0);
mBrightnessEvent.setAdjustmentFlags(0);
@@ -69,9 +68,9 @@ public final class BrightnessEventTest {
String actualString = mBrightnessEvent.toString(false);
String expectedString =
"BrightnessEvent: disp=1, physDisp=test, brt=0.6, initBrt=25.0, rcmdBrt=0.6,"
- + " preBrt=NaN, lux=100.0, fastLux=90.0, slowLux=85.0, preLux=150.0, hbmMax=0.62,"
- + " hbmMode=off, rbcStrength=-1, thrmMax=0.65, powerFactor=0.2, flags=, reason=doze"
- + " [ low_pwr ], autoBrightness=true";
+ + " preBrt=NaN, lux=100.0, preLux=150.0, hbmMax=0.62, hbmMode=off, rbcStrength=-1,"
+ + " thrmMax=0.65, powerFactor=0.2, wasShortTermModelActive=true, flags=,"
+ + " reason=doze [ low_pwr ], autoBrightness=true";
assertEquals(expectedString, actualString);
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 54baf18da92a..82c340145f8e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -849,4 +849,53 @@ public class HdmiCecLocalDeviceTvTest {
verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
anyInt());
}
+
+ @Test
+ public void tvSendRequestArcTerminationOnSleep() {
+ // Emulate Audio device on port 0x2000 (supports ARC)
+
+ mNativeWrapper.setPortConnectionStatus(2, true);
+ HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mNativeWrapper.onCecMessage(hdmiCecMessage);
+ mTestLooper.dispatchAll();
+
+ mHdmiCecLocalDeviceTv.startArcAction(true);
+ mTestLooper.dispatchAll();
+ HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+ HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+ ADDR_AUDIO_SYSTEM,
+ ADDR_TV);
+ HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+ ADDR_TV,
+ ADDR_AUDIO_SYSTEM);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+
+ mNativeWrapper.onCecMessage(initiateArc);
+ mTestLooper.dispatchAll();
+
+ // Finish querying SADs
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+ mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+ mTestLooper.dispatchAll();
+
+ // ARC should be established after RequestSadAction is finished
+ assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+
+ mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+ assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 164161e34b6f..dc47b5eaea0e 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -8,7 +8,6 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -20,8 +19,6 @@ import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.net.NetworkRequest;
import android.os.Build;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.SystemClock;
import android.test.RenamingDelegatingContext;
@@ -32,7 +29,6 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.util.HexDump;
import com.android.server.LocalServices;
import com.android.server.job.JobStore.JobSet;
import com.android.server.job.controllers.JobStatus;
@@ -44,7 +40,6 @@ import org.junit.runner.RunWith;
import java.time.Clock;
import java.time.ZoneOffset;
-import java.util.Arrays;
import java.util.Iterator;
/**
@@ -143,15 +138,8 @@ public class JobStoreTest {
assertEquals("Didn't get expected number of persisted tasks.", 1, jobStatusSet.size());
final JobStatus loadedTaskStatus = jobStatusSet.getAllJobs().get(0);
- assertTasksEqual(task, loadedTaskStatus.getJob());
+ assertJobsEqual(ts, loadedTaskStatus);
assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(ts));
- assertEquals("Different uids.", SOME_UID, loadedTaskStatus.getUid());
- assertEquals(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION,
- loadedTaskStatus.getInternalFlags());
- compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
- ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime());
- compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
- ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed());
}
@Test
@@ -202,19 +190,10 @@ public class JobStoreTest {
loaded2 = tmp;
}
- assertTasksEqual(task1, loaded1.getJob());
- assertTasksEqual(task2, loaded2.getJob());
+ assertJobsEqual(taskStatus1, loaded1);
+ assertJobsEqual(taskStatus2, loaded2);
assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1));
assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2));
- // Check that the loaded task has the correct runtimes.
- compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
- taskStatus1.getEarliestRunTime(), loaded1.getEarliestRunTime());
- compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
- taskStatus1.getLatestRunTimeElapsed(), loaded1.getLatestRunTimeElapsed());
- compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
- taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime());
- compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
- taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed());
}
@Test
@@ -240,7 +219,7 @@ public class JobStoreTest {
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
- assertTasksEqual(task, loaded.getJob());
+ assertJobsEqual(taskStatus, loaded);
}
@Test
@@ -544,71 +523,30 @@ public class JobStoreTest {
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
final JobStatus second = jobStatusSet.getAllJobs().iterator().next();
- assertTasksEqual(first.getJob(), second.getJob());
+ assertJobsEqual(first, second);
}
/**
- * Helper function to throw an error if the provided task and TaskStatus objects are not equal.
+ * Helper function to throw an error if the provided JobStatus objects are not equal.
*/
- private void assertTasksEqual(JobInfo first, JobInfo second) {
- assertEquals("Different task ids.", first.getId(), second.getId());
- assertEquals("Different components.", first.getService(), second.getService());
- assertEquals("Different periodic status.", first.isPeriodic(), second.isPeriodic());
- assertEquals("Different period.", first.getIntervalMillis(), second.getIntervalMillis());
- assertEquals("Different inital backoff.", first.getInitialBackoffMillis(),
- second.getInitialBackoffMillis());
- assertEquals("Different backoff policy.", first.getBackoffPolicy(),
- second.getBackoffPolicy());
-
- assertEquals("Invalid charging constraint.", first.isRequireCharging(),
- second.isRequireCharging());
- assertEquals("Invalid battery not low constraint.", first.isRequireBatteryNotLow(),
- second.isRequireBatteryNotLow());
- assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(),
- second.isRequireDeviceIdle());
- assertEquals("Invalid network type.",
- first.getNetworkType(), second.getNetworkType());
- assertEquals("Invalid network.",
- first.getRequiredNetwork(), second.getRequiredNetwork());
- assertEquals("Download bytes don't match",
- first.getEstimatedNetworkDownloadBytes(),
- second.getEstimatedNetworkDownloadBytes());
- assertEquals("Upload bytes don't match",
- first.getEstimatedNetworkUploadBytes(),
- second.getEstimatedNetworkUploadBytes());
- assertEquals("Minimum chunk bytes don't match",
- first.getMinimumNetworkChunkBytes(),
- second.getMinimumNetworkChunkBytes());
- assertEquals("Invalid deadline constraint.",
- first.hasLateConstraint(),
- second.hasLateConstraint());
- assertEquals("Invalid delay constraint.",
- first.hasEarlyConstraint(),
- second.hasEarlyConstraint());
- assertEquals("Extras don't match",
- first.getExtras().toString(), second.getExtras().toString());
- assertEquals("Transient xtras don't match",
- first.getTransientExtras().toString(), second.getTransientExtras().toString());
-
- // Since people can forget to add tests here for new fields, do one last
- // validity check based on bits-on-wire equality.
- final byte[] firstBytes = marshall(first);
- final byte[] secondBytes = marshall(second);
- if (!Arrays.equals(firstBytes, secondBytes)) {
- Log.w(TAG, "First: " + HexDump.dumpHexString(firstBytes));
- Log.w(TAG, "Second: " + HexDump.dumpHexString(secondBytes));
- fail("Raw JobInfo aren't equal; see logs for details");
- }
- }
+ private void assertJobsEqual(JobStatus expected, JobStatus actual) {
+ assertEquals(expected.getJob(), actual.getJob());
- private static byte[] marshall(Parcelable p) {
- final Parcel parcel = Parcel.obtain();
- try {
- p.writeToParcel(parcel, 0);
- return parcel.marshall();
- } finally {
- parcel.recycle();
- }
+ // Source UID isn't persisted, but the rest of the app info is.
+ assertEquals("Source package not equal",
+ expected.getSourcePackageName(), actual.getSourcePackageName());
+ assertEquals("Source user not equal", expected.getSourceUserId(), actual.getSourceUserId());
+ assertEquals("Calling UID not equal", expected.getUid(), actual.getUid());
+ assertEquals("Calling user not equal", expected.getUserId(), actual.getUserId());
+
+ assertEquals("Internal flags not equal",
+ expected.getInternalFlags(), actual.getInternalFlags());
+
+ // Check that the loaded task has the correct runtimes.
+ compareTimestampsSubjectToIoLatency("Early run-times not the same after read.",
+ expected.getEarliestRunTime(), actual.getEarliestRunTime());
+ compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
+ expected.getLatestRunTimeElapsed(), actual.getLatestRunTimeElapsed());
}
/**
@@ -623,5 +561,4 @@ public class JobStoreTest {
}
private static class StubClass {}
-
}
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 83139b02430a..5a482fc37998 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -44,6 +44,7 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.om.IOverlayManager;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
@@ -669,7 +670,10 @@ public class StatusBarManagerServiceTest {
}
@Test
- public void testSetNavBarMode_setsModeKids() throws RemoteException {
+ public void testSetNavBarMode_setsModeKids() throws Exception {
+ mContext.setMockPackageManager(mPackageManager);
+ when(mPackageManager.getPackageInfo(anyString(),
+ any(PackageManager.PackageInfoFlags.class))).thenReturn(new PackageInfo());
int navBarModeKids = StatusBarManager.NAV_BAR_MODE_KIDS;
mStatusBarManagerService.setNavBarMode(navBarModeKids);
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index fed8b4040aba..bcdc65c19330 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -21,12 +21,14 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
import android.util.IndentingPrintWriter;
import java.util.ArrayList;
+import java.util.Objects;
public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
@@ -34,14 +36,17 @@ public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
new FakeServiceConfigAccessor();
private final ArrayList<StateChangeListener> mListeners = new ArrayList<>();
private TimeZoneState mTimeZoneState;
+ private TimeZoneDetectorStatus mStatus;
public FakeTimeZoneDetectorStrategy() {
mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(
this::notifyChangeListeners);
}
- public void initializeConfiguration(ConfigurationInternal configuration) {
+ public void initializeConfigurationAndStatus(
+ ConfigurationInternal configuration, TimeZoneDetectorStatus status) {
mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration);
+ mStatus = Objects.requireNonNull(status);
}
@Override
@@ -57,6 +62,7 @@ public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
assertEquals("Multi-user testing not supported",
configurationInternal.getUserId(), userId);
return new TimeZoneCapabilitiesAndConfig(
+ mStatus,
configurationInternal.asCapabilities(bypassUserPolicyChecks),
configurationInternal.asConfiguration());
}
@@ -90,7 +96,7 @@ public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
}
@Override
- public void suggestGeolocationTimeZone(GeolocationTimeZoneSuggestion timeZoneSuggestion) {
+ public void handleLocationAlgorithmEvent(LocationAlgorithmEvent locationAlgorithmEvent) {
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
index 0f667b3a690b..602842addff2 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/GeolocationTimeZoneSuggestionTest.java
@@ -16,13 +16,8 @@
package com.android.server.timezonedetector;
-import static com.android.server.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-
-import android.os.ShellCommand;
import org.junit.Test;
@@ -49,11 +44,6 @@ public class GeolocationTimeZoneSuggestionTest {
assertEquals(certain1v1, certain1v2);
assertEquals(certain1v2, certain1v1);
- // DebugInfo must not be considered in equals().
- certain1v1.addDebugInfo("Debug info 1");
- certain1v2.addDebugInfo("Debug info 2");
- assertEquals(certain1v1, certain1v2);
-
long time2 = 2222L;
GeolocationTimeZoneSuggestion certain2 =
GeolocationTimeZoneSuggestion.createCertainSuggestion(time2, ARBITRARY_ZONE_IDS1);
@@ -71,40 +61,4 @@ public class GeolocationTimeZoneSuggestionTest {
assertNotEquals(certain1v1, certain3);
assertNotEquals(certain3, certain1v1);
}
-
- @Test(expected = IllegalArgumentException.class)
- public void testParseCommandLineArg_noZoneIdsArg() {
- ShellCommand testShellCommand =
- createShellCommandWithArgsAndOptions(Collections.emptyList());
- GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
- }
-
- @Test
- public void testParseCommandLineArg_zoneIdsUncertain() {
- ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
- "--zone_ids UNCERTAIN");
- assertNull(GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand)
- .getZoneIds());
- }
-
- @Test
- public void testParseCommandLineArg_zoneIdsEmpty() {
- ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("--zone_ids EMPTY");
- assertEquals(Collections.emptyList(),
- GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand).getZoneIds());
- }
-
- @Test
- public void testParseCommandLineArg_zoneIdsPresent() {
- ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
- "--zone_ids Europe/London,Europe/Paris");
- assertEquals(Arrays.asList("Europe/London", "Europe/Paris"),
- GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand).getZoneIds());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testParseCommandLineArg_unknownArgument() {
- ShellCommand testShellCommand = createShellCommandWithArgsAndOptions("--bad_arg 0");
- GeolocationTimeZoneSuggestion.parseCommandLineArg(testShellCommand);
- }
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java
new file mode 100644
index 000000000000..4c14014405f4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/LocationAlgorithmEventTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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 com.android.server.timezonedetector;
+
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
+import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
+import static android.service.timezone.TimeZoneProviderStatus.OPERATION_STATUS_OK;
+
+import static com.android.server.timezonedetector.ShellCommandTestSupport.createShellCommandWithArgsAndOptions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.os.ShellCommand;
+import android.service.timezone.TimeZoneProviderStatus;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+public class LocationAlgorithmEventTest {
+
+ public static final TimeZoneProviderStatus ARBITRARY_PROVIDER_STATUS =
+ new TimeZoneProviderStatus.Builder()
+ .setConnectivityDependencyStatus(DEPENDENCY_STATUS_OK)
+ .setLocationDetectionDependencyStatus(DEPENDENCY_STATUS_NOT_APPLICABLE)
+ .setTimeZoneResolutionOperationStatus(OPERATION_STATUS_OK)
+ .build();
+
+ public static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_ALGORITHM_STATUS =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, ARBITRARY_PROVIDER_STATUS,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+
+ @Test
+ public void testEquals() {
+ GeolocationTimeZoneSuggestion suggestion1 =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L);
+ LocationTimeZoneAlgorithmStatus status1 = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_PRESENT, null);
+ LocationAlgorithmEvent event1v1 = new LocationAlgorithmEvent(status1, suggestion1);
+ assertEqualsAndHashCode(event1v1, event1v1);
+
+ LocationAlgorithmEvent event1v2 = new LocationAlgorithmEvent(status1, suggestion1);
+ assertEqualsAndHashCode(event1v1, event1v2);
+
+ GeolocationTimeZoneSuggestion suggestion2 =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(2222L);
+ LocationAlgorithmEvent event2 = new LocationAlgorithmEvent(status1, suggestion2);
+ assertNotEquals(event1v1, event2);
+
+ LocationTimeZoneAlgorithmStatus status2 = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_NOT_PRESENT, null, PROVIDER_STATUS_NOT_READY, null);
+ LocationAlgorithmEvent event3 = new LocationAlgorithmEvent(status2, suggestion1);
+ assertNotEquals(event1v1, event3);
+
+ // DebugInfo must not be considered in equals().
+ event1v1.addDebugInfo("Debug info 1");
+ event1v2.addDebugInfo("Debug info 2");
+ assertEquals(event1v1, event1v2);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_noStatus() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L);
+ ShellCommand testShellCommand =
+ createShellCommandWithArgsAndOptions(
+ Arrays.asList("--suggestion", suggestion.toString()));
+
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ }
+
+ @Test
+ public void testParseCommandLineArg_noSuggestion() {
+ GeolocationTimeZoneSuggestion suggestion = null;
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString()));
+
+ assertEquals(event, LocationAlgorithmEvent.parseCommandLineArg(testShellCommand));
+ }
+
+ @Test
+ public void testParseCommandLineArg_suggestionUncertain() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createUncertainSuggestion(1111L);
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+ "--suggestion", "UNCERTAIN"));
+
+ LocationAlgorithmEvent parsedEvent =
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus());
+ assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds());
+ }
+
+ @Test
+ public void testParseCommandLineArg_suggestionEmpty() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ 1111L, Collections.emptyList());
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+ "--suggestion", "EMPTY"));
+
+ LocationAlgorithmEvent parsedEvent =
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus());
+ assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds());
+ }
+
+ @Test
+ public void testParseCommandLineArg_suggestionPresent() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ 1111L, Arrays.asList("Europe/London", "Europe/Paris"));
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+ "--suggestion", "Europe/London,Europe/Paris"));
+
+ LocationAlgorithmEvent parsedEvent =
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ assertEquals(event.getAlgorithmStatus(), parsedEvent.getAlgorithmStatus());
+ assertEquals(event.getSuggestion().getZoneIds(), parsedEvent.getSuggestion().getZoneIds());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testParseCommandLineArg_unknownArgument() {
+ GeolocationTimeZoneSuggestion suggestion =
+ GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ 1111L, Arrays.asList("Europe/London", "Europe/Paris"));
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_ALGORITHM_STATUS, suggestion);
+ ShellCommand testShellCommand = createShellCommandWithArgsAndOptions(
+ Arrays.asList("--status", event.getAlgorithmStatus().toString(),
+ "--suggestion", "Europe/London,Europe/Paris", "--bad_arg"));
+ LocationAlgorithmEvent.parseCommandLineArg(testShellCommand);
+ }
+
+ private static void assertEqualsAndHashCode(Object one, Object two) {
+ assertEquals(one, two);
+ assertEquals(two, one);
+ assertEquals(one.hashCode(), two.hashCode());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
index 223c53233065..ea801e887c4c 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -16,6 +16,10 @@
package com.android.server.timezonedetector;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+
import static com.android.server.timezonedetector.MetricsTimeZoneDetectorState.DETECTION_MODE_GEO;
import static org.junit.Assert.assertEquals;
@@ -23,6 +27,7 @@ import static org.junit.Assert.assertNull;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.UserIdInt;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -31,6 +36,7 @@ import com.android.server.timezonedetector.MetricsTimeZoneDetectorState.MetricsT
import org.junit.Test;
import java.util.Arrays;
+import java.util.List;
import java.util.function.Function;
/** Tests for {@link MetricsTimeZoneDetectorState}. */
@@ -38,6 +44,9 @@ public class MetricsTimeZoneDetectorStateTest {
private static final @UserIdInt int ARBITRARY_USER_ID = 1;
private static final @ElapsedRealtimeLong long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
+ private static final LocationTimeZoneAlgorithmStatus ARBITRARY_CERTAIN_STATUS =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
private static final String DEVICE_TIME_ZONE_ID = "DeviceTimeZoneId";
private static final ManualTimeZoneSuggestion MANUAL_TIME_ZONE_SUGGESTION =
@@ -50,11 +59,14 @@ public class MetricsTimeZoneDetectorStateTest {
.setQuality(TelephonyTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
.build();
- private static final GeolocationTimeZoneSuggestion GEOLOCATION_TIME_ZONE_SUGGESTION =
+ public static final GeolocationTimeZoneSuggestion GEOLOCATION_SUGGESTION_CERTAIN =
GeolocationTimeZoneSuggestion.createCertainSuggestion(
ARBITRARY_ELAPSED_REALTIME_MILLIS,
Arrays.asList("GeoTimeZoneId1", "GeoTimeZoneId2"));
+ private static final LocationAlgorithmEvent LOCATION_ALGORITHM_EVENT =
+ new LocationAlgorithmEvent(ARBITRARY_CERTAIN_STATUS, GEOLOCATION_SUGGESTION_CERTAIN);
+
private final OrdinalGenerator<String> mOrdinalGenerator =
new OrdinalGenerator<>(Function.identity());
@@ -68,7 +80,7 @@ public class MetricsTimeZoneDetectorStateTest {
MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
- TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+ TELEPHONY_TIME_ZONE_SUGGESTION, LOCATION_ALGORITHM_EVENT);
// Assert the content.
assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
@@ -88,9 +100,10 @@ public class MetricsTimeZoneDetectorStateTest {
assertEquals(expectedTelephonySuggestion,
metricsTimeZoneDetectorState.getLatestTelephonySuggestion());
+ List<String> expectedZoneIds = LOCATION_ALGORITHM_EVENT.getSuggestion().getZoneIds();
MetricsTimeZoneSuggestion expectedGeoSuggestion =
MetricsTimeZoneSuggestion.createCertain(
- GEOLOCATION_TIME_ZONE_SUGGESTION.getZoneIds().toArray(new String[0]),
+ expectedZoneIds.toArray(new String[0]),
new int[] { 3, 4 });
assertEquals(expectedGeoSuggestion,
metricsTimeZoneDetectorState.getLatestGeolocationSuggestion());
@@ -106,7 +119,7 @@ public class MetricsTimeZoneDetectorStateTest {
MetricsTimeZoneDetectorState metricsTimeZoneDetectorState =
MetricsTimeZoneDetectorState.create(mOrdinalGenerator, configurationInternal,
DEVICE_TIME_ZONE_ID, MANUAL_TIME_ZONE_SUGGESTION,
- TELEPHONY_TIME_ZONE_SUGGESTION, GEOLOCATION_TIME_ZONE_SUGGESTION);
+ TELEPHONY_TIME_ZONE_SUGGESTION, LOCATION_ALGORITHM_EVENT);
// Assert the content.
assertCommonConfiguration(configurationInternal, metricsTimeZoneDetectorState);
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index 8909832391a4..a02c8ca001ce 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -16,14 +16,22 @@
package com.android.server.timezonedetector;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.content.Context;
import android.os.HandlerThread;
@@ -41,6 +49,15 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
public class TimeZoneDetectorInternalImplTest {
+ private static final TelephonyTimeZoneAlgorithmStatus ARBITRARY_TELEPHONY_STATUS =
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
+ private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_CERTAIN_STATUS =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
+ private static final TimeZoneDetectorStatus ARBITRARY_DETECTOR_STATUS =
+ new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING, ARBITRARY_TELEPHONY_STATUS,
+ ARBITRARY_LOCATION_CERTAIN_STATUS);
+
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
private static final String ARBITRARY_ZONE_ID = "TestZoneId";
private static final List<String> ARBITRARY_ZONE_IDS = Arrays.asList(ARBITRARY_ZONE_ID);
@@ -81,7 +98,8 @@ public class TimeZoneDetectorInternalImplTest {
public void testGetCapabilitiesAndConfigForDpm() throws Exception {
final boolean autoDetectionEnabled = true;
ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled);
- mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(testConfig);
+ TimeZoneDetectorStatus testStatus = ARBITRARY_DETECTOR_STATUS;
+ mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(testConfig, testStatus);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorInternal.getCapabilitiesAndConfigForDpm();
@@ -93,6 +111,7 @@ public class TimeZoneDetectorInternalImplTest {
TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
new TimeZoneCapabilitiesAndConfig(
+ testStatus,
testConfig.asCapabilities(expectedBypassUserPolicyChecks),
testConfig.asConfiguration());
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
@@ -103,7 +122,9 @@ public class TimeZoneDetectorInternalImplTest {
final boolean autoDetectionEnabled = false;
ConfigurationInternal initialConfigurationInternal =
createConfigurationInternal(autoDetectionEnabled);
- mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfigurationInternal);
+ TimeZoneDetectorStatus testStatus = ARBITRARY_DETECTOR_STATUS;
+ mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(
+ initialConfigurationInternal, testStatus);
TimeZoneConfiguration timeConfiguration = new TimeZoneConfiguration.Builder()
.setAutoDetectionEnabled(true)
@@ -131,13 +152,15 @@ public class TimeZoneDetectorInternalImplTest {
}
@Test
- public void testSuggestGeolocationTimeZone() throws Exception {
+ public void testHandleLocationAlgorithmEvent() throws Exception {
GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
- mTimeZoneDetectorInternal.suggestGeolocationTimeZone(timeZoneSuggestion);
+ LocationAlgorithmEvent suggestionEvent = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion);
+ mTimeZoneDetectorInternal.handleLocationAlgorithmEvent(suggestionEvent);
mTestHandler.assertTotalMessagesEnqueued(1);
mTestHandler.waitForMessagesToBeProcessed();
- verify(mFakeTimeZoneDetectorStrategySpy).suggestGeolocationTimeZone(timeZoneSuggestion);
+ verify(mFakeTimeZoneDetectorStrategySpy).handleLocationAlgorithmEvent(suggestionEvent);
}
private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() {
return new ManualTimeZoneSuggestion(ARBITRARY_ZONE_ID);
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index d8346ee4355b..d9d8053e6220 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -16,6 +16,11 @@
package com.android.server.timezonedetector;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
@@ -34,8 +39,11 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.time.ITimeZoneDetectorListener;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -59,6 +67,13 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
public class TimeZoneDetectorServiceTest {
+ private static final LocationTimeZoneAlgorithmStatus ARBITRARY_LOCATION_CERTAIN_STATUS =
+ new LocationTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING,
+ PROVIDER_STATUS_IS_CERTAIN, null, PROVIDER_STATUS_NOT_PRESENT, null);
+ private static final TimeZoneDetectorStatus ARBITRARY_DETECTOR_STATUS =
+ new TimeZoneDetectorStatus(DETECTOR_STATUS_RUNNING,
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING),
+ ARBITRARY_LOCATION_CERTAIN_STATUS);
private static final int ARBITRARY_USER_ID = 9999;
private static final List<String> ARBITRARY_TIME_ZONE_IDS = Arrays.asList("TestZoneId");
private static final long ARBITRARY_ELAPSED_REALTIME_MILLIS = 1234L;
@@ -113,7 +128,8 @@ public class TimeZoneDetectorServiceTest {
ConfigurationInternal configuration =
createConfigurationInternal(true /* autoDetectionEnabled*/);
- mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(configuration);
+ mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(configuration,
+ ARBITRARY_DETECTOR_STATUS);
TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
mTimeZoneDetectorService.getCapabilitiesAndConfig();
@@ -128,6 +144,7 @@ public class TimeZoneDetectorServiceTest {
TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
new TimeZoneCapabilitiesAndConfig(
+ ARBITRARY_DETECTOR_STATUS,
configuration.asCapabilities(expectedBypassUserPolicyChecks),
configuration.asConfiguration());
assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
@@ -161,7 +178,9 @@ public class TimeZoneDetectorServiceTest {
public void testListenerRegistrationAndCallbacks() throws Exception {
ConfigurationInternal initialConfiguration =
createConfigurationInternal(false /* autoDetectionEnabled */);
- mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfiguration);
+
+ mFakeTimeZoneDetectorStrategySpy.initializeConfigurationAndStatus(
+ initialConfiguration, ARBITRARY_DETECTOR_STATUS);
IBinder mockListenerBinder = mock(IBinder.class);
ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
@@ -231,31 +250,35 @@ public class TimeZoneDetectorServiceTest {
}
@Test
- public void testSuggestGeolocationTimeZone_withoutPermission() {
+ public void testHandleLocationAlgorithmEvent_withoutPermission() {
doThrow(new SecurityException("Mock"))
.when(mMockContext).enforceCallingPermission(anyString(), any());
GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion);
assertThrows(SecurityException.class,
- () -> mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion));
+ () -> mTimeZoneDetectorService.handleLocationAlgorithmEvent(event));
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME_ZONE), anyString());
}
@Test
- public void testSuggestGeolocationTimeZone() throws Exception {
+ public void testHandleLocationAlgorithmEvent() throws Exception {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
GeolocationTimeZoneSuggestion timeZoneSuggestion = createGeolocationTimeZoneSuggestion();
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(
+ ARBITRARY_LOCATION_CERTAIN_STATUS, timeZoneSuggestion);
- mTimeZoneDetectorService.suggestGeolocationTimeZone(timeZoneSuggestion);
+ mTimeZoneDetectorService.handleLocationAlgorithmEvent(event);
mTestHandler.assertTotalMessagesEnqueued(1);
verify(mMockContext).enforceCallingPermission(
eq(android.Manifest.permission.SET_TIME_ZONE), anyString());
mTestHandler.waitForMessagesToBeProcessed();
- verify(mFakeTimeZoneDetectorStrategySpy).suggestGeolocationTimeZone(timeZoneSuggestion);
+ verify(mFakeTimeZoneDetectorStrategySpy).handleLocationAlgorithmEvent(event);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index f50e7fbc76bb..b991c5a30415 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -16,6 +16,12 @@
package com.android.server.timezonedetector;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTOR_STATUS_RUNNING;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_CERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_IS_UNCERTAIN;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_PRESENT;
+import static android.app.time.LocationTimeZoneAlgorithmStatus.PROVIDER_STATUS_NOT_READY;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET;
import static android.app.timezonedetector.TelephonyTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_ONLY;
@@ -35,6 +41,7 @@ import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.T
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -47,8 +54,11 @@ import static org.mockito.Mockito.verify;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.time.LocationTimeZoneAlgorithmStatus;
+import android.app.time.TelephonyTimeZoneAlgorithmStatus;
import android.app.time.TimeZoneCapabilitiesAndConfig;
import android.app.time.TimeZoneConfiguration;
+import android.app.time.TimeZoneDetectorStatus;
import android.app.time.TimeZoneState;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -189,6 +199,9 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabledSetting(true)
.build();
+ private static final TelephonyTimeZoneAlgorithmStatus TELEPHONY_ALGORITHM_RUNNING_STATUS =
+ new TelephonyTimeZoneAlgorithmStatus(DETECTION_ALGORITHM_STATUS_RUNNING);
+
private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
private FakeEnvironment mFakeEnvironment;
private HandlerThread mHandlerThread;
@@ -233,9 +246,7 @@ public class TimeZoneDetectorStrategyImplTest {
{
mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
CONFIG_AUTO_DISABLED_GEO_DISABLED);
- mTestHandler.waitForMessagesToBeProcessed();
-
- stateChangeListener.assertNotificationsReceived(0);
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
assertEquals(CONFIG_AUTO_DISABLED_GEO_DISABLED,
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
}
@@ -244,10 +255,7 @@ public class TimeZoneDetectorStrategyImplTest {
{
mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
CONFIG_AUTO_ENABLED_GEO_ENABLED);
- mTestHandler.waitForMessagesToBeProcessed();
-
- stateChangeListener.assertNotificationsReceived(1);
- stateChangeListener.resetNotificationsReceivedCount();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
assertEquals(CONFIG_AUTO_ENABLED_GEO_ENABLED,
mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
}
@@ -258,10 +266,7 @@ public class TimeZoneDetectorStrategyImplTest {
new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
mTimeZoneDetectorStrategy.updateConfiguration(
USER_ID, requestedChanges, bypassUserPolicyChecks);
- mTestHandler.waitForMessagesToBeProcessed();
-
- stateChangeListener.assertNotificationsReceived(1);
- stateChangeListener.resetNotificationsReceivedCount();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
}
}
@@ -290,11 +295,9 @@ public class TimeZoneDetectorStrategyImplTest {
new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
mTimeZoneDetectorStrategy.updateConfiguration(
otherUserId, requestedChanges, bypassUserPolicyChecks);
- mTestHandler.waitForMessagesToBeProcessed();
// Only changes to the current user's config are notified.
- stateChangeListener.assertNotificationsReceived(0);
- stateChangeListener.resetNotificationsReceivedCount();
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
}
// Current user behavior: the strategy caches and returns the latest configuration.
@@ -426,9 +429,9 @@ public class TimeZoneDetectorStrategyImplTest {
QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex1ScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(slotIndex1TimeZoneSuggestion,
TELEPHONY_SCORE_NONE);
- assertEquals(expectedSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertNull(mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(SLOT_INDEX2, null);
assertEquals(expectedSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -439,10 +442,10 @@ public class TimeZoneDetectorStrategyImplTest {
QualifiedTelephonyTimeZoneSuggestion expectedSlotIndex2ScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(slotIndex2TimeZoneSuggestion,
TELEPHONY_SCORE_NONE);
- assertEquals(expectedSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedSlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedSlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedSlotIndex2ScoredSuggestion);
// SlotIndex1 should always beat slotIndex2, all other things being equal.
assertEquals(expectedSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -477,8 +480,8 @@ public class TimeZoneDetectorStrategyImplTest {
QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(
lowQualitySuggestion, testCase.expectedScore);
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -494,8 +497,8 @@ public class TimeZoneDetectorStrategyImplTest {
QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(
goodQualitySuggestion, testCase2.expectedScore);
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -511,8 +514,8 @@ public class TimeZoneDetectorStrategyImplTest {
QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(
lowQualitySuggestion2, testCase.expectedScore);
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -543,8 +546,8 @@ public class TimeZoneDetectorStrategyImplTest {
// Assert internal service state.
QualifiedTelephonyTimeZoneSuggestion expectedScoredSuggestion =
new QualifiedTelephonyTimeZoneSuggestion(suggestion, testCase.expectedScore);
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -560,8 +563,8 @@ public class TimeZoneDetectorStrategyImplTest {
}
// Assert internal service state.
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -570,8 +573,8 @@ public class TimeZoneDetectorStrategyImplTest {
.verifyTimeZoneNotChanged();
// Assert internal service state.
- assertEquals(expectedScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedScoredSuggestion);
assertEquals(expectedScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -622,8 +625,8 @@ public class TimeZoneDetectorStrategyImplTest {
}
// Assert internal service state.
- assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion);
assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
}
@@ -677,10 +680,10 @@ public class TimeZoneDetectorStrategyImplTest {
}
// Assert internal service state.
- assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedEmptySlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedEmptySlotIndex2ScoredSuggestion);
assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -690,10 +693,10 @@ public class TimeZoneDetectorStrategyImplTest {
script.verifyTimeZoneNotChanged();
// Assert internal service state.
- assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedZoneSlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedZoneSlotIndex2ScoredSuggestion);
// SlotIndex1 should always beat slotIndex2, all other things being equal.
assertEquals(expectedZoneSlotIndex1ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
@@ -709,20 +712,20 @@ public class TimeZoneDetectorStrategyImplTest {
}
// Assert internal service state.
- assertEquals(expectedEmptySlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedEmptySlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedZoneSlotIndex2ScoredSuggestion);
assertEquals(expectedZoneSlotIndex2ScoredSuggestion,
mTimeZoneDetectorStrategy.findBestTelephonySuggestionForTests());
// Reset the state for the next loop.
script.simulateTelephonyTimeZoneSuggestion(emptySlotIndex2Suggestion)
.verifyTimeZoneNotChanged();
- assertEquals(expectedEmptySlotIndex1ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1));
- assertEquals(expectedEmptySlotIndex2ScoredSuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX2));
+ script.verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX1, expectedEmptySlotIndex1ScoredSuggestion)
+ .verifyLatestQualifiedTelephonySuggestionReceived(
+ SLOT_INDEX2, expectedEmptySlotIndex2ScoredSuggestion);
}
}
@@ -866,53 +869,185 @@ public class TimeZoneDetectorStrategyImplTest {
}
@Test
- public void testGeoSuggestion_uncertain() {
+ public void testLocationAlgorithmEvent_statusChangesOnly() {
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
- .resetConfigurationTracking();
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
+
+ TimeZoneDetectorStatus expectedInitialDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ LocationTimeZoneAlgorithmStatus.UNKNOWN);
+ script.verifyCachedDetectorStatus(expectedInitialDetectorStatus);
+
+ LocationTimeZoneAlgorithmStatus algorithmStatus1 = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_NOT_READY, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ LocationTimeZoneAlgorithmStatus algorithmStatus2 = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_NOT_PRESENT, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ assertNotEquals(algorithmStatus1, algorithmStatus2);
+
+ {
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ new LocationAlgorithmEvent(algorithmStatus1, null);
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
- GeolocationTimeZoneSuggestion uncertainSuggestion = createUncertainGeolocationSuggestion();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
- script.simulateGeolocationTimeZoneSuggestion(uncertainSuggestion)
+ // Assert internal service state.
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ algorithmStatus1);
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+ // Assert internal service state.
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+ }
+
+ {
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ new LocationAlgorithmEvent(algorithmStatus2, null);
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
+ // Assert internal service state.
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ algorithmStatus2);
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+ // Assert internal service state.
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+ }
+ }
+
+ @Test
+ public void testLocationAlgorithmEvent_uncertain() {
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+ Script script = new Script()
+ .initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
+ .simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
+
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
// Assert internal service state.
- assertEquals(uncertainSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ locationAlgorithmEvent.getAlgorithmStatus());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
+
+ // Detector remains running and location algorithm is still uncertain so nothing to report.
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
+ // Assert internal service state.
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
}
@Test
- public void testGeoSuggestion_noZones() {
+ public void testLocationAlgorithmEvent_noZones() {
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
- .resetConfigurationTracking();
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
+
+ LocationAlgorithmEvent locationAlgorithmEvent = createCertainLocationAlgorithmEvent();
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
- GeolocationTimeZoneSuggestion noZonesSuggestion = createCertainGeolocationSuggestion();
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
- script.simulateGeolocationTimeZoneSuggestion(noZonesSuggestion)
+ // Assert internal service state.
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ locationAlgorithmEvent.getAlgorithmStatus());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged();
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
+
// Assert internal service state.
- assertEquals(noZonesSuggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
}
@Test
- public void testGeoSuggestion_oneZone() {
- GeolocationTimeZoneSuggestion suggestion =
- createCertainGeolocationSuggestion("Europe/London");
-
+ public void testLocationAlgorithmEvent_oneZone() {
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
- .resetConfigurationTracking();
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
+
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent);
+
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
+ // Assert internal service state.
+ TimeZoneDetectorStatus expectedDetectorStatus = new TimeZoneDetectorStatus(
+ DETECTOR_STATUS_RUNNING,
+ TELEPHONY_ALGORITHM_RUNNING_STATUS,
+ locationAlgorithmEvent.getAlgorithmStatus());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
+
+ // Repeat the event to demonstrate the state change notifier is not triggered.
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged();
- script.simulateGeolocationTimeZoneSuggestion(suggestion)
- .verifyTimeZoneChangedAndReset(suggestion);
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
// Assert internal service state.
- assertEquals(suggestion, mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ script.verifyCachedDetectorStatus(expectedDetectorStatus)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
}
/**
@@ -921,41 +1056,35 @@ public class TimeZoneDetectorStrategyImplTest {
* set to until that unambiguously can't be correct.
*/
@Test
- public void testGeoSuggestion_multiZone() {
- GeolocationTimeZoneSuggestion londonOnlySuggestion =
- createCertainGeolocationSuggestion("Europe/London");
- GeolocationTimeZoneSuggestion londonOrParisSuggestion =
- createCertainGeolocationSuggestion("Europe/Paris", "Europe/London");
- GeolocationTimeZoneSuggestion parisOnlySuggestion =
- createCertainGeolocationSuggestion("Europe/Paris");
-
+ public void testLocationAlgorithmEvent_multiZone() {
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_ENABLED_GEO_ENABLED)
.resetConfigurationTracking();
- script.simulateGeolocationTimeZoneSuggestion(londonOnlySuggestion)
- .verifyTimeZoneChangedAndReset(londonOnlySuggestion);
- assertEquals(londonOnlySuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ LocationAlgorithmEvent londonOnlyEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
+ script.simulateLocationAlgorithmEvent(londonOnlyEvent)
+ .verifyTimeZoneChangedAndReset(londonOnlyEvent)
+ .verifyLatestLocationAlgorithmEventReceived(londonOnlyEvent);
// Confirm bias towards the current device zone when there's multiple zones to choose from.
- script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
- .verifyTimeZoneNotChanged();
- assertEquals(londonOrParisSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ LocationAlgorithmEvent londonOrParisEvent =
+ createCertainLocationAlgorithmEvent("Europe/Paris", "Europe/London");
+ script.simulateLocationAlgorithmEvent(londonOrParisEvent)
+ .verifyTimeZoneNotChanged()
+ .verifyLatestLocationAlgorithmEventReceived(londonOrParisEvent);
- script.simulateGeolocationTimeZoneSuggestion(parisOnlySuggestion)
- .verifyTimeZoneChangedAndReset(parisOnlySuggestion);
- assertEquals(parisOnlySuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ LocationAlgorithmEvent parisOnlyEvent = createCertainLocationAlgorithmEvent("Europe/Paris");
+ script.simulateLocationAlgorithmEvent(parisOnlyEvent)
+ .verifyTimeZoneChangedAndReset(parisOnlyEvent)
+ .verifyLatestLocationAlgorithmEventReceived(parisOnlyEvent);
// Now the suggestion that previously left the device on Europe/London will leave the device
// on Europe/Paris.
- script.simulateGeolocationTimeZoneSuggestion(londonOrParisSuggestion)
- .verifyTimeZoneNotChanged();
- assertEquals(londonOrParisSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ script.simulateLocationAlgorithmEvent(londonOrParisEvent)
+ .verifyTimeZoneNotChanged()
+ .verifyLatestLocationAlgorithmEventReceived(londonOrParisEvent);
}
/**
@@ -964,8 +1093,9 @@ public class TimeZoneDetectorStrategyImplTest {
*/
@Test
public void testChangingGeoDetectionEnabled() {
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/London");
+ TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
TelephonyTimeZoneSuggestion telephonySuggestion = createTelephonySuggestion(
SLOT_INDEX1, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET, QUALITY_SINGLE_ZONE,
"Europe/Paris");
@@ -973,20 +1103,22 @@ public class TimeZoneDetectorStrategyImplTest {
Script script = new Script()
.initializeTimeZoneSetting(ARBITRARY_TIME_ZONE_ID, TIME_ZONE_CONFIDENCE_LOW)
.simulateConfigurationInternalChange(CONFIG_AUTO_DISABLED_GEO_DISABLED)
- .resetConfigurationTracking();
+ .resetConfigurationTracking()
+ .registerStateChangeListener(stateChangeListener);
// Add suggestions. Nothing should happen as time zone detection is disabled.
- script.simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneNotChanged();
+ script.simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneNotChanged()
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
- assertEquals(geolocationSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ // A detector status change is considered a "state change".
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
- .verifyTimeZoneNotChanged();
+ .verifyTimeZoneNotChanged()
+ .verifyLatestTelephonySuggestionReceived(SLOT_INDEX1, telephonySuggestion);
- assertEquals(telephonySuggestion,
- mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(SLOT_INDEX1).suggestion);
+ assertStateChangeNotificationsSent(stateChangeListener, 0);
// Toggling the time zone detection enabled setting on should cause the device setting to be
// set from the telephony signal, as we've started with geolocation time zone detection
@@ -994,18 +1126,25 @@ public class TimeZoneDetectorStrategyImplTest {
script.simulateSetAutoMode(true)
.verifyTimeZoneChangedAndReset(telephonySuggestion);
+ // A configuration change is considered a "state change".
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
+
// Changing the detection to enable geo detection will cause the device tz setting to
// change to use the latest geolocation suggestion.
script.simulateSetGeoDetectionEnabled(true)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion);
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent);
+
+ // A configuration change is considered a "state change".
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
// Changing the detection to disable geo detection should cause the device tz setting to
// change to the telephony suggestion.
script.simulateSetGeoDetectionEnabled(false)
- .verifyTimeZoneChangedAndReset(telephonySuggestion);
+ .verifyTimeZoneChangedAndReset(telephonySuggestion)
+ .verifyLatestLocationAlgorithmEventReceived(locationAlgorithmEvent);
- assertEquals(geolocationSuggestion,
- mTimeZoneDetectorStrategy.getLatestGeolocationSuggestion());
+ // A configuration change is considered a "state change".
+ assertStateChangeNotificationsSent(stateChangeListener, 1);
}
@Test
@@ -1039,21 +1178,20 @@ public class TimeZoneDetectorStrategyImplTest {
// Receiving an "uncertain" geolocation suggestion should have no effect.
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(true);
}
// Receiving a "certain" geolocation suggestion should disable telephony fallback mode.
{
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/London");
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/London");
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
}
@@ -1076,22 +1214,22 @@ public class TimeZoneDetectorStrategyImplTest {
// Geolocation suggestions should continue to be used as normal (previous telephony
// suggestions are not used, even when the geolocation suggestion is uncertain).
{
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/Rome");
+ LocationAlgorithmEvent certainLocationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/Rome");
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(certainLocationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent uncertainLocationAlgorithmEvent =
+ createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(uncertainLocationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false);
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(certainLocationAlgorithmEvent)
// No change needed, device will already be set to Europe/Rome.
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false);
@@ -1108,21 +1246,20 @@ public class TimeZoneDetectorStrategyImplTest {
// Make the geolocation algorithm uncertain.
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneChangedAndReset(lastTelephonySuggestion)
.verifyTelephonyFallbackIsEnabled(true);
}
// Make the geolocation algorithm certain, disabling telephony fallback.
{
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/Lisbon");
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Europe/Lisbon");
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
+ .verifyTimeZoneChangedAndReset(locationAlgorithmEvent)
.verifyTelephonyFallbackIsEnabled(false);
}
@@ -1130,10 +1267,9 @@ public class TimeZoneDetectorStrategyImplTest {
// Demonstrate what happens when geolocation is uncertain when telephony fallback is
// enabled.
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false)
.simulateEnableTelephonyFallback()
@@ -1161,10 +1297,9 @@ public class TimeZoneDetectorStrategyImplTest {
// Receiving an "uncertain" geolocation suggestion should have no effect.
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(true);
}
@@ -1172,10 +1307,9 @@ public class TimeZoneDetectorStrategyImplTest {
// Make an uncertain geolocation suggestion, there is no telephony suggestion to fall back
// to
{
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent locationAlgorithmEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(true);
}
@@ -1185,17 +1319,16 @@ public class TimeZoneDetectorStrategyImplTest {
// Geolocation suggestions should continue to be used as normal (previous telephony
// suggestions are not used, even when the geolocation suggestion is uncertain).
{
- GeolocationTimeZoneSuggestion geolocationSuggestion =
- createCertainGeolocationSuggestion("Europe/Rome");
+ LocationAlgorithmEvent certainEvent =
+ createCertainLocationAlgorithmEvent("Europe/Rome");
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(geolocationSuggestion)
- .verifyTimeZoneChangedAndReset(geolocationSuggestion)
+ .simulateLocationAlgorithmEvent(certainEvent)
+ .verifyTimeZoneChangedAndReset(certainEvent)
.verifyTelephonyFallbackIsEnabled(false);
- GeolocationTimeZoneSuggestion uncertainGeolocationSuggestion =
- createUncertainGeolocationSuggestion();
+ LocationAlgorithmEvent uncertainEvent = createUncertainLocationAlgorithmEvent();
script.simulateIncrementClock()
- .simulateGeolocationTimeZoneSuggestion(uncertainGeolocationSuggestion)
+ .simulateLocationAlgorithmEvent(uncertainEvent)
.verifyTimeZoneNotChanged()
.verifyTelephonyFallbackIsEnabled(false);
@@ -1319,15 +1452,15 @@ public class TimeZoneDetectorStrategyImplTest {
TelephonyTimeZoneSuggestion telephonySuggestion =
createTelephonySuggestion(0 /* slotIndex */, MATCH_TYPE_NETWORK_COUNTRY_ONLY,
QUALITY_SINGLE_ZONE, "Zone2");
- GeolocationTimeZoneSuggestion geolocationTimeZoneSuggestion =
- createCertainGeolocationSuggestion("Zone3", "Zone2");
+ LocationAlgorithmEvent locationAlgorithmEvent =
+ createCertainLocationAlgorithmEvent("Zone3", "Zone2");
script.simulateTelephonyTimeZoneSuggestion(telephonySuggestion)
.verifyTimeZoneNotChanged()
- .simulateGeolocationTimeZoneSuggestion(geolocationTimeZoneSuggestion)
+ .simulateLocationAlgorithmEvent(locationAlgorithmEvent)
.verifyTimeZoneNotChanged();
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
- manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion,
+ manualSuggestion, telephonySuggestion, locationAlgorithmEvent,
MetricsTimeZoneDetectorState.DETECTION_MODE_MANUAL);
// Update the config and confirm that the config metrics state updates also.
@@ -1336,11 +1469,11 @@ public class TimeZoneDetectorStrategyImplTest {
.setGeoDetectionEnabledSetting(true)
.build();
- expectedDeviceTimeZoneId = geolocationTimeZoneSuggestion.getZoneIds().get(0);
+ expectedDeviceTimeZoneId = locationAlgorithmEvent.getSuggestion().getZoneIds().get(0);
script.simulateConfigurationInternalChange(expectedInternalConfig)
.verifyTimeZoneChangedAndReset(expectedDeviceTimeZoneId, TIME_ZONE_CONFIDENCE_HIGH);
assertMetricsState(expectedInternalConfig, expectedDeviceTimeZoneId,
- manualSuggestion, telephonySuggestion, geolocationTimeZoneSuggestion,
+ manualSuggestion, telephonySuggestion, locationAlgorithmEvent,
MetricsTimeZoneDetectorState.DETECTION_MODE_GEO);
}
@@ -1352,7 +1485,7 @@ public class TimeZoneDetectorStrategyImplTest {
ConfigurationInternal expectedInternalConfig,
String expectedDeviceTimeZoneId, ManualTimeZoneSuggestion expectedManualSuggestion,
TelephonyTimeZoneSuggestion expectedTelephonySuggestion,
- GeolocationTimeZoneSuggestion expectedGeolocationTimeZoneSuggestion,
+ LocationAlgorithmEvent expectedLocationAlgorithmEvent,
int expectedDetectionMode) {
MetricsTimeZoneDetectorState actualState = mTimeZoneDetectorStrategy.generateMetricsState();
@@ -1365,7 +1498,7 @@ public class TimeZoneDetectorStrategyImplTest {
MetricsTimeZoneDetectorState.create(
tzIdOrdinalGenerator, expectedInternalConfig, expectedDeviceTimeZoneId,
expectedManualSuggestion, expectedTelephonySuggestion,
- expectedGeolocationTimeZoneSuggestion);
+ expectedLocationAlgorithmEvent);
// Rely on MetricsTimeZoneDetectorState.equals() for time zone ID / ID ordinal comparisons.
assertEquals(expectedState, actualState);
}
@@ -1405,20 +1538,37 @@ public class TimeZoneDetectorStrategyImplTest {
return new TelephonyTimeZoneSuggestion.Builder(SLOT_INDEX2).build();
}
+ private LocationAlgorithmEvent createCertainLocationAlgorithmEvent(@NonNull String... zoneIds) {
+ GeolocationTimeZoneSuggestion suggestion = createCertainGeolocationSuggestion(zoneIds);
+ LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_CERTAIN, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
+ event.addDebugInfo("Test certain event");
+ return event;
+ }
+
+ private LocationAlgorithmEvent createUncertainLocationAlgorithmEvent() {
+ GeolocationTimeZoneSuggestion suggestion = createUncertainGeolocationSuggestion();
+ LocationTimeZoneAlgorithmStatus algorithmStatus = new LocationTimeZoneAlgorithmStatus(
+ DETECTION_ALGORITHM_STATUS_RUNNING, PROVIDER_STATUS_IS_UNCERTAIN, null,
+ PROVIDER_STATUS_NOT_PRESENT, null);
+ LocationAlgorithmEvent event = new LocationAlgorithmEvent(algorithmStatus, suggestion);
+ event.addDebugInfo("Test uncertain event");
+ return event;
+ }
+
private GeolocationTimeZoneSuggestion createUncertainGeolocationSuggestion() {
- return GeolocationTimeZoneSuggestion.createCertainSuggestion(
- mFakeEnvironment.elapsedRealtimeMillis(), null);
+ return GeolocationTimeZoneSuggestion.createUncertainSuggestion(
+ mFakeEnvironment.elapsedRealtimeMillis());
}
private GeolocationTimeZoneSuggestion createCertainGeolocationSuggestion(
@NonNull String... zoneIds) {
assertNotNull(zoneIds);
- GeolocationTimeZoneSuggestion suggestion =
- GeolocationTimeZoneSuggestion.createCertainSuggestion(
- mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds));
- suggestion.addDebugInfo("Test suggestion");
- return suggestion;
+ return GeolocationTimeZoneSuggestion.createCertainSuggestion(
+ mFakeEnvironment.elapsedRealtimeMillis(), Arrays.asList(zoneIds));
}
static class FakeEnvironment implements TimeZoneDetectorStrategyImpl.Environment {
@@ -1499,6 +1649,14 @@ public class TimeZoneDetectorStrategyImplTest {
}
}
+ private void assertStateChangeNotificationsSent(
+ TestStateChangeListener stateChangeListener, int expectedCount) {
+ // State change notifications are asynchronous, so we have to wait.
+ mTestHandler.waitForMessagesToBeProcessed();
+
+ stateChangeListener.assertNotificationsReceivedAndReset(expectedCount);
+ }
+
/**
* A "fluent" class allows reuse of code in tests: initialization, simulation and verification
* logic.
@@ -1516,6 +1674,11 @@ public class TimeZoneDetectorStrategyImplTest {
return this;
}
+ Script registerStateChangeListener(StateChangeListener stateChangeListener) {
+ mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+ return this;
+ }
+
Script simulateIncrementClock() {
mFakeEnvironment.incrementClock();
return this;
@@ -1555,11 +1718,10 @@ public class TimeZoneDetectorStrategyImplTest {
}
/**
- * Simulates the time zone detection strategy receiving a geolocation-originated
- * suggestion.
+ * Simulates the time zone detection strategy receiving a location algorithm event.
*/
- Script simulateGeolocationTimeZoneSuggestion(GeolocationTimeZoneSuggestion suggestion) {
- mTimeZoneDetectorStrategy.suggestGeolocationTimeZone(suggestion);
+ Script simulateLocationAlgorithmEvent(LocationAlgorithmEvent event) {
+ mTimeZoneDetectorStrategy.handleLocationAlgorithmEvent(event);
return this;
}
@@ -1616,7 +1778,9 @@ public class TimeZoneDetectorStrategyImplTest {
return this;
}
- Script verifyTimeZoneChangedAndReset(GeolocationTimeZoneSuggestion suggestion) {
+ Script verifyTimeZoneChangedAndReset(LocationAlgorithmEvent event) {
+ GeolocationTimeZoneSuggestion suggestion = event.getSuggestion();
+ assertNotNull("Only events with suggestions can change the time zone", suggestion);
assertEquals("Only use this method with unambiguous geo suggestions",
1, suggestion.getZoneIds().size());
verifyTimeZoneChangedAndReset(
@@ -1631,6 +1795,32 @@ public class TimeZoneDetectorStrategyImplTest {
return this;
}
+ Script verifyCachedDetectorStatus(TimeZoneDetectorStatus expectedStatus) {
+ assertEquals(expectedStatus,
+ mTimeZoneDetectorStrategy.getCachedDetectorStatusForTests());
+ return this;
+ }
+
+ Script verifyLatestLocationAlgorithmEventReceived(LocationAlgorithmEvent expectedEvent) {
+ assertEquals(expectedEvent,
+ mTimeZoneDetectorStrategy.getLatestLocationAlgorithmEvent());
+ return this;
+ }
+
+ Script verifyLatestTelephonySuggestionReceived(int slotIndex,
+ TelephonyTimeZoneSuggestion expectedSuggestion) {
+ assertEquals(expectedSuggestion,
+ mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(slotIndex).suggestion);
+ return this;
+ }
+
+ Script verifyLatestQualifiedTelephonySuggestionReceived(int slotIndex,
+ QualifiedTelephonyTimeZoneSuggestion expectedQualifiedSuggestion) {
+ assertEquals(expectedQualifiedSuggestion,
+ mTimeZoneDetectorStrategy.getLatestTelephonySuggestion(slotIndex));
+ return this;
+ }
+
Script resetConfigurationTracking() {
mFakeEnvironment.commitAllChanges();
return this;
@@ -1671,11 +1861,16 @@ public class TimeZoneDetectorStrategyImplTest {
mNotificationsReceived++;
}
- public void resetNotificationsReceivedCount() {
+ public void assertNotificationsReceivedAndReset(int expectedCount) {
+ assertNotificationsReceived(expectedCount);
+ resetNotificationsReceivedCount();
+ }
+
+ private void resetNotificationsReceivedCount() {
mNotificationsReceived = 0;
}
- public void assertNotificationsReceived(int expectedCount) {
+ private void assertNotificationsReceived(int expectedCount) {
assertEquals(expectedCount, mNotificationsReceived);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
index c18acd20e96a..7b1db953ef54 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerTest.java
@@ -15,6 +15,8 @@
*/
package com.android.server.timezonedetector.location;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_NOT_RUNNING;
+import static android.app.time.DetectorStatusTypes.DETECTION_ALGORITHM_STATUS_RUNNING;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_NOT_APPLICABLE;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_OK;
import static android.service.timezone.TimeZoneProviderStatus.DEPENDENCY_STATUS_TEMPORARILY_UNAVAILABLE;
@@ -42,6 +44,7 @@ import static com.android.server.timezonedetector.location.TestSupport.USER2_CON
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -51,6 +54,7 @@ import static java.util.Arrays.asList;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.time.DetectorStatusTypes.DetectionAlgorithmStatus;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.service.timezone.TimeZoneProviderEvent;
@@ -60,6 +64,7 @@ import android.util.IndentingPrintWriter;
import com.android.server.timezonedetector.ConfigurationInternal;
import com.android.server.timezonedetector.GeolocationTimeZoneSuggestion;
+import com.android.server.timezonedetector.LocationAlgorithmEvent;
import com.android.server.timezonedetector.TestState;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderMetricsLogger;
import com.android.server.timezonedetector.location.LocationTimeZoneProvider.ProviderState.ProviderStateEnum;
@@ -141,7 +146,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.setFailDuringInitialization(true);
// Initialize. After initialization the providers must be initialized and one should be
- // started.
+ // started. They should report their status change via the callback.
controller.initialize(testEnvironment, mTestCallback);
mTestPrimaryLocationTimeZoneProvider.assertInitialized();
@@ -154,7 +159,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertInitializationTimeoutSet(expectedInitTimeout);
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -184,7 +190,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -211,7 +218,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING, STATE_FAILED);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -239,7 +247,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -262,7 +271,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -282,7 +292,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
@@ -296,7 +307,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate time passing with no provider event being received from either the primary or
@@ -311,7 +322,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Finally, the uncertainty timeout should cause the controller to make an uncertain
@@ -324,7 +335,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit();
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -345,7 +356,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
@@ -358,7 +370,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -380,7 +392,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
@@ -392,7 +405,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the primary provider. This should cause a
@@ -405,7 +418,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -427,7 +440,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
@@ -439,7 +453,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the secondary provider. This should cause a
@@ -453,7 +467,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -475,7 +489,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
@@ -488,7 +503,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -501,7 +516,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// And a third, different event should cause another suggestion.
@@ -513,7 +528,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -535,7 +550,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate time passing with no provider event being received from the primary.
@@ -547,7 +563,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the secondary provider. This should cause a
@@ -561,7 +577,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -575,7 +591,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// And a third, different event should cause another suggestion.
@@ -588,7 +604,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -610,7 +626,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
@@ -623,7 +640,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -639,7 +656,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate a location event being received from the secondary provider. This should cause a
@@ -654,7 +671,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -670,7 +687,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate time passing. This means the uncertainty timeout should fire and the uncertain
@@ -683,7 +700,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN);
- mTestCallback.assertUncertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithUncertainSuggestionReportedAndCommit(
USER1_UNCERTAIN_LOCATION_TIME_ZONE_EVENT);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -705,7 +722,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a location event being received from the primary provider. This should cause a
@@ -718,7 +736,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -733,7 +751,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// And a success event from the primary provider should cause the controller to make another
@@ -747,7 +765,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -767,7 +785,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
@@ -778,7 +797,8 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
@@ -788,7 +808,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -807,7 +828,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_PROVIDERS_INITIALIZING, STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
@@ -818,7 +840,8 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a success event being received from the primary provider.
@@ -830,7 +853,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -843,8 +866,9 @@ public class LocationTimeZoneProviderControllerTest {
assertControllerState(controller, STATE_STOPPED);
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestMetricsLogger.assertStateChangesAndCommit(STATE_UNCERTAIN, STATE_STOPPED);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -865,7 +889,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate the primary provider suggesting a time zone.
@@ -879,7 +904,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -897,9 +922,9 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertStateEnumAndConfig(
PROVIDER_STATE_STARTED_INITIALIZING, USER2_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
- mTestMetricsLogger.assertStateChangesAndCommit(
- STATE_UNCERTAIN, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED, STATE_INITIALIZING);
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -920,7 +945,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a failure location event being received from the primary provider. This should
@@ -933,7 +959,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate uncertainty from the secondary.
@@ -945,7 +971,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// And a success event from the secondary provider should cause the controller to make
@@ -958,7 +984,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -971,7 +997,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
}
@@ -992,7 +1018,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a failure location event being received from the primary provider. This should
@@ -1005,7 +1032,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is disabled.
@@ -1015,7 +1042,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled.
@@ -1026,7 +1054,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -1047,7 +1076,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate an uncertain event from the primary. This will start the secondary, which will
@@ -1062,7 +1092,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate failure event from the secondary. This should just affect the secondary's state.
@@ -1074,7 +1104,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// And a success event from the primary provider should cause the controller to make
@@ -1087,7 +1117,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT2);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -1100,7 +1130,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
}
@@ -1121,7 +1151,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate an uncertain event from the primary. This will start the secondary, which will
@@ -1136,7 +1167,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Simulate failure event from the secondary. This should just affect the secondary's state.
@@ -1148,7 +1179,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_UNCERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertUncertaintyTimeoutSet(testEnvironment, controller);
// Now signal a config change so that geo detection is disabled.
@@ -1158,7 +1189,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Now signal a config change so that geo detection is enabled. Only the primary can be
@@ -1170,7 +1202,8 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -1191,7 +1224,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate a failure event from the primary. This will start the secondary.
@@ -1203,7 +1237,7 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertStateEnumAndConfigAndCommit(
PROVIDER_STATE_STARTED_INITIALIZING, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestMetricsLogger.assertStateChangesAndCommit();
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertNoEventReported();
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate failure event from the secondary.
@@ -1214,7 +1248,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestPrimaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestSecondaryLocationTimeZoneProvider.assertIsPermFailedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_FAILED);
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
}
@@ -1233,7 +1268,7 @@ public class LocationTimeZoneProviderControllerTest {
{
LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(STATE_INITIALIZING, state.getControllerState());
- assertNull(state.getLastSuggestion());
+ assertNull(state.getLastEvent().getSuggestion());
assertControllerRecordedStates(state,
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
assertProviderStates(state.getPrimaryProviderStates(),
@@ -1251,7 +1286,7 @@ public class LocationTimeZoneProviderControllerTest {
{
LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(STATE_INITIALIZING, state.getControllerState());
- assertNull(state.getLastSuggestion());
+ assertNull(state.getLastEvent().getSuggestion());
assertControllerRecordedStates(state);
assertProviderStates(
state.getPrimaryProviderStates(), PROVIDER_STATE_STARTED_UNCERTAIN);
@@ -1268,7 +1303,7 @@ public class LocationTimeZoneProviderControllerTest {
LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(STATE_CERTAIN, state.getControllerState());
assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
- state.getLastSuggestion().getZoneIds());
+ state.getLastEvent().getSuggestion().getZoneIds());
assertControllerRecordedStates(state, STATE_CERTAIN);
assertProviderStates(state.getPrimaryProviderStates());
assertProviderStates(
@@ -1280,7 +1315,7 @@ public class LocationTimeZoneProviderControllerTest {
LocationTimeZoneManagerServiceState state = controller.getStateForTests();
assertEquals(STATE_CERTAIN, state.getControllerState());
assertEquals(USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1.getSuggestion().getTimeZoneIds(),
- state.getLastSuggestion().getZoneIds());
+ state.getLastEvent().getSuggestion().getZoneIds());
assertControllerRecordedStates(state);
assertProviderStates(state.getPrimaryProviderStates());
assertProviderStates(state.getSecondaryProviderStates());
@@ -1313,7 +1348,8 @@ public class LocationTimeZoneProviderControllerTest {
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(
STATE_PROVIDERS_INITIALIZING, STATE_STOPPED, STATE_INITIALIZING);
- mTestCallback.assertNoSuggestionMade();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_RUNNING);
assertFalse(controller.isUncertaintyTimeoutSet());
// Simulate the primary provider suggesting a time zone.
@@ -1327,7 +1363,7 @@ public class LocationTimeZoneProviderControllerTest {
PROVIDER_STATE_STARTED_CERTAIN, USER1_CONFIG_GEO_DETECTION_ENABLED);
mTestSecondaryLocationTimeZoneProvider.assertIsStoppedAndCommit();
mTestMetricsLogger.assertStateChangesAndCommit(STATE_CERTAIN);
- mTestCallback.assertCertainSuggestionMadeFromEventAndCommit(
+ mTestCallback.assertEventWithCertainSuggestionReportedAndCommit(
USER1_SUCCESS_LOCATION_TIME_ZONE_EVENT1);
assertFalse(controller.isUncertaintyTimeoutSet());
@@ -1335,11 +1371,11 @@ public class LocationTimeZoneProviderControllerTest {
controller.destroy();
assertControllerState(controller, STATE_DESTROYED);
- mTestMetricsLogger.assertStateChangesAndCommit(
- STATE_UNCERTAIN, STATE_STOPPED, STATE_DESTROYED);
+ mTestMetricsLogger.assertStateChangesAndCommit(STATE_STOPPED, STATE_DESTROYED);
// Confirm that the previous suggestion was overridden.
- mTestCallback.assertUncertainSuggestionMadeAndCommit();
+ mTestCallback.assertEventWithNoSuggestionReportedAndCommit(
+ DETECTION_ALGORITHM_STATUS_NOT_RUNNING);
mTestPrimaryLocationTimeZoneProvider.assertStateChangesAndCommit(
PROVIDER_STATE_STOPPED, PROVIDER_STATE_DESTROYED);
@@ -1517,63 +1553,101 @@ public class LocationTimeZoneProviderControllerTest {
private static class TestCallback extends LocationTimeZoneProviderController.Callback {
- private TestState<GeolocationTimeZoneSuggestion> mLatestSuggestion = new TestState<>();
+ private TestState<LocationAlgorithmEvent> mLatestEvent = new TestState<>();
TestCallback(ThreadingDomain threadingDomain) {
super(threadingDomain);
}
@Override
- void suggest(GeolocationTimeZoneSuggestion suggestion) {
- mLatestSuggestion.set(suggestion);
+ void sendEvent(LocationAlgorithmEvent event) {
+ mLatestEvent.set(event);
+ }
+
+ void assertNoEventReported() {
+ mLatestEvent.assertHasNotBeenSet();
+ }
+
+ /**
+ * Asserts one or more events have been reported, and the most recent does not contain a
+ * suggestion.
+ */
+ void assertEventWithNoSuggestionReportedAndCommit(
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus) {
+ mLatestEvent.assertHasBeenSet();
+
+ LocationAlgorithmEvent latest = mLatestEvent.getLatest();
+ assertEquals(expectedAlgorithmStatus, latest.getAlgorithmStatus().getStatus());
+ assertNull(latest.getSuggestion());
+ mLatestEvent.commitLatest();
}
- void assertCertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) {
+ void assertEventWithCertainSuggestionReportedAndCommit(TimeZoneProviderEvent event) {
// Test coding error if this fails.
assertEquals(TimeZoneProviderEvent.EVENT_TYPE_SUGGESTION, event.getType());
+ // By definition, the algorithm has to be running to report a suggestion.
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus =
+ DETECTION_ALGORITHM_STATUS_RUNNING;
TimeZoneProviderSuggestion suggestion = event.getSuggestion();
- assertSuggestionMadeAndCommit(
+ assertEventWithSuggestionReportedAndCommit(
+ expectedAlgorithmStatus,
suggestion.getElapsedRealtimeMillis(),
suggestion.getTimeZoneIds());
}
- void assertNoSuggestionMade() {
- mLatestSuggestion.assertHasNotBeenSet();
- }
-
- /** Asserts that an uncertain suggestion has been made from the supplied event. */
- void assertUncertainSuggestionMadeFromEventAndCommit(TimeZoneProviderEvent event) {
+ /**
+ * Asserts that one or more events have been reported, and the most recent contains an
+ * uncertain suggestion matching select details from the supplied provider event.
+ */
+ void assertEventWithUncertainSuggestionReportedAndCommit(TimeZoneProviderEvent event) {
// Test coding error if this fails.
assertEquals(TimeZoneProviderEvent.EVENT_TYPE_UNCERTAIN, event.getType());
- assertSuggestionMadeAndCommit(event.getCreationElapsedMillis(), null);
+ // By definition, the algorithm has to be running to report a suggestion.
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus =
+ DETECTION_ALGORITHM_STATUS_RUNNING;
+ assertEventWithSuggestionReportedAndCommit(
+ expectedAlgorithmStatus, event.getCreationElapsedMillis(), null);
}
/**
- * Asserts that an uncertain suggestion has been made.
- * Ignores the suggestion's effectiveFromElapsedMillis.
+ * Asserts that one or more events have been reported, and the most recent contains an
+ * uncertain suggestion. Ignores the suggestion's effectiveFromElapsedMillis.
*/
- void assertUncertainSuggestionMadeAndCommit() {
+ void assertEventWithUncertainSuggestionReportedAndCommit() {
+ // By definition, the algorithm has to be running to report a suggestion.
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus =
+ DETECTION_ALGORITHM_STATUS_RUNNING;
+
// An "uncertain" suggestion has null time zone IDs.
- assertSuggestionMadeAndCommit(null, null);
+ assertEventWithSuggestionReportedAndCommit(expectedAlgorithmStatus, null, null);
}
/**
- * Asserts that a suggestion has been made and some properties of that suggestion.
- * When expectedEffectiveFromElapsedMillis is null then its value isn't checked.
+ * Asserts that an event has been reported containing a suggestion and some properties of
+ * that suggestion. When expectedEffectiveFromElapsedMillis is null then its value isn't
+ * checked.
*/
- private void assertSuggestionMadeAndCommit(
+ private void assertEventWithSuggestionReportedAndCommit(
+ @DetectionAlgorithmStatus int expectedAlgorithmStatus,
@Nullable @ElapsedRealtimeLong Long expectedEffectiveFromElapsedMillis,
@Nullable List<String> expectedZoneIds) {
- mLatestSuggestion.assertHasBeenSet();
+ mLatestEvent.assertHasBeenSet();
+
+ LocationAlgorithmEvent latestEvent = mLatestEvent.getLatest();
+ assertEquals(expectedAlgorithmStatus, latestEvent.getAlgorithmStatus().getStatus());
+
+ GeolocationTimeZoneSuggestion suggestion = latestEvent.getSuggestion();
+ assertNotNull("Latest event doesn't contain a suggestion: event=" + latestEvent,
+ suggestion);
+
if (expectedEffectiveFromElapsedMillis != null) {
- assertEquals(
- expectedEffectiveFromElapsedMillis.longValue(),
- mLatestSuggestion.getLatest().getEffectiveFromElapsedMillis());
+ assertEquals(expectedEffectiveFromElapsedMillis.longValue(),
+ suggestion.getEffectiveFromElapsedMillis());
}
- assertEquals(expectedZoneIds, mLatestSuggestion.getLatest().getZoneIds());
- mLatestSuggestion.commitLatest();
+ assertEquals(expectedZoneIds, suggestion.getZoneIds());
+ mLatestEvent.commitLatest();
}
}
@@ -1598,11 +1672,9 @@ public class LocationTimeZoneProviderControllerTest {
}
@Override
- void onInitialize() {
+ boolean onInitialize() {
mInitialized = true;
- if (mFailDuringInitialization) {
- throw new RuntimeException("Simulated initialization failure");
- }
+ return !mFailDuringInitialization;
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
index 2bee7e66c43f..1ae74c679b53 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/LocationTimeZoneProviderTest.java
@@ -330,8 +330,9 @@ public class LocationTimeZoneProviderTest {
}
@Override
- void onInitialize() {
+ boolean onInitialize() {
mOnInitializeCalled = true;
+ return true;
}
@Override
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index d54d1fed1016..afec08505257 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -1101,6 +1101,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
new NotificationChannel("id", "name", IMPORTANCE_HIGH);
mBinderService.updateNotificationChannelForPackage(PKG, mUid, updatedChannel);
+ // pretend only this following part is called by the app (system permissions are required to
+ // update the notification channel on behalf of the user above)
+ mService.isSystemUid = false;
+
// Recreating with a lower importance leaves channel unchanged.
final NotificationChannel dupeChannel =
new NotificationChannel("id", "name", NotificationManager.IMPORTANCE_LOW);
@@ -1126,6 +1130,46 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testCreateNotificationChannels_fromAppCannotSetFields() throws Exception {
+ // Confirm that when createNotificationChannels is called from the relevant app and not
+ // system, then it cannot set fields that can't be set by apps
+ mService.isSystemUid = false;
+
+ final NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ channel.setBypassDnd(true);
+ channel.setAllowBubbles(true);
+
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(channel)));
+
+ final NotificationChannel createdChannel =
+ mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
+ assertFalse(createdChannel.canBypassDnd());
+ assertFalse(createdChannel.canBubble());
+ }
+
+ @Test
+ public void testCreateNotificationChannels_fromSystemCanSetFields() throws Exception {
+ // Confirm that when createNotificationChannels is called from system,
+ // then it can set fields that can't be set by apps
+ mService.isSystemUid = true;
+
+ final NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_DEFAULT);
+ channel.setBypassDnd(true);
+ channel.setAllowBubbles(true);
+
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(channel)));
+
+ final NotificationChannel createdChannel =
+ mBinderService.getNotificationChannel(PKG, mContext.getUserId(), PKG, "id");
+ assertTrue(createdChannel.canBypassDnd());
+ assertTrue(createdChannel.canBubble());
+ }
+
+ @Test
public void testBlockedNotifications_suspended() throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(true);
@@ -3088,6 +3132,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testDeleteChannelGroupChecksForFgses() throws Exception {
+ // the setup for this test requires it to seem like it's coming from the app
+ mService.isSystemUid = false;
when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
.thenReturn(singletonList(mock(AssociationInfo.class)));
CountDownLatch latch = new CountDownLatch(2);
@@ -3100,7 +3146,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
ParceledListSlice<NotificationChannel> pls =
new ParceledListSlice(ImmutableList.of(notificationChannel));
try {
- mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
+ mBinderService.createNotificationChannels(PKG, pls);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -3119,8 +3165,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
ParceledListSlice<NotificationChannel> pls =
new ParceledListSlice(ImmutableList.of(notificationChannel));
try {
- mBinderService.createNotificationChannelsForPackage(PKG, mUid, pls);
- mBinderService.deleteNotificationChannelGroup(PKG, "group");
+ // Because existing channels won't have their groups overwritten when the call
+ // is from the app, this call won't take the channel out of the group
+ mBinderService.createNotificationChannels(PKG, pls);
+ mBinderService.deleteNotificationChannelGroup(PKG, "group");
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -8681,7 +8729,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertEquals("friend", friendChannel.getConversationId());
assertEquals(null, original.getConversationId());
assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
- assertFalse(friendChannel.canBubble()); // can't be modified by app
+ assertEquals(original.canBubble(), friendChannel.canBubble()); // called by system
assertFalse(original.getId().equals(friendChannel.getId()));
assertNotNull(friendChannel.getId());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index bd8da4e713b7..079897bc099f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2806,7 +2806,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final Task task = activity.getTask();
final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
topActivity.setVisible(false);
- task.positionChildAt(topActivity, POSITION_TOP);
+ task.positionChildAt(POSITION_TOP, topActivity, false /* includeParents */);
activity.addStartingWindow(mPackageName, android.R.style.Theme, null, true, true, false,
true, false, false, false);
waitUntilHandlersIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index 13ebc932fcef..0568b3893aa2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -25,6 +25,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -36,6 +37,8 @@ import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceControl;
+import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,12 +53,18 @@ import org.junit.runner.RunWith;
@Presubmit
@RunWith(WindowTestRunner.class)
public class FrameRateSelectionPriorityTests extends WindowTestsBase {
- private static final float FLOAT_TOLERANCE = 0.01f;
private static final int LOW_MODE_ID = 3;
private DisplayPolicy mDisplayPolicy = mock(DisplayPolicy.class);
private RefreshRatePolicy mRefreshRatePolicy;
private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
+ private FrameRateVote mTempFrameRateVote = new FrameRateVote();
+
+ private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
+ private static final FrameRateVote FRAME_RATE_VOTE_60_EXACT =
+ new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ private static final FrameRateVote FRAME_RATE_VOTE_60_PREFERRED =
+ new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
WindowState createWindow(String name) {
WindowState window = createWindow(null, TYPE_APPLICATION, name);
@@ -85,12 +94,12 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
assertNotNull("Window state is created", appWindow);
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority doesn't change.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Call the function a few times.
appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -109,16 +118,15 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
.getPreferredModeId(appWindow), 0);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
- assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
- .getPreferredRefreshRate(appWindow), 0, FLOAT_TOLERANCE);
-
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
+ assertFalse(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
+ .updateFrameRateVote(appWindow));
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority stays MAX_VALUE.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -127,7 +135,7 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes to 1.
assertEquals(appWindow.mFrameRateSelectionPriority, 1);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), 1);
verify(appWindow.getPendingTransaction(), never()).setFrameRate(
@@ -138,27 +146,27 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
public void testApplicationInFocusWithModeId() {
final WindowState appWindow = createWindow("appWindow");
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Application is in focus.
appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes.
assertEquals(appWindow.mFrameRateSelectionPriority, 1);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Update the mode ID to a requested number.
appWindow.mAttrs.preferredDisplayModeId = 1;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes.
assertEquals(appWindow.mFrameRateSelectionPriority, 0);
- assertEquals(appWindow.mAppPreferredFrameRate, 60, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_60_EXACT);
// Remove the mode ID request.
appWindow.mAttrs.preferredDisplayModeId = 0;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes.
assertEquals(appWindow.mFrameRateSelectionPriority, 1);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Verify we called actions on Transactions correctly.
verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
@@ -175,7 +183,7 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
public void testApplicationNotInFocusWithModeId() {
final WindowState appWindow = createWindow("appWindow");
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
final WindowState inFocusWindow = createWindow("inFocus");
appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -183,14 +191,14 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// The window is not in focus.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Update the mode ID to a requested number.
appWindow.mAttrs.preferredDisplayModeId = 1;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority changes.
assertEquals(appWindow.mFrameRateSelectionPriority, 2);
- assertEquals(appWindow.mAppPreferredFrameRate, 60, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_60_EXACT);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -204,7 +212,7 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
public void testApplicationNotInFocusWithoutModeId() {
final WindowState appWindow = createWindow("appWindow");
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
final WindowState inFocusWindow = createWindow("inFocus");
appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
@@ -212,14 +220,14 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// The window is not in focus.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Make sure that the mode ID is not set.
appWindow.mAttrs.preferredDisplayModeId = 0;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
// Priority doesn't change.
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -237,11 +245,10 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(0, mRefreshRatePolicy.getPreferredModeId(appWindow));
- assertEquals(60, mRefreshRatePolicy.getPreferredRefreshRate(appWindow), FLOAT_TOLERANCE);
appWindow.updateFrameRateSelectionPriorityIfNeeded();
assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority);
- assertEquals(60, appWindow.mAppPreferredFrameRate, FLOAT_TOLERANCE);
+ assertEquals(FRAME_RATE_VOTE_60_EXACT, appWindow.mFrameRateVote);
// Call the function a few times.
appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -262,19 +269,19 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
.thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Update the mode ID to a requested number.
appWindow.mAttrs.preferredDisplayModeId = 1;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
// Remove the mode ID request.
appWindow.mAttrs.preferredDisplayModeId = 0;
appWindow.updateFrameRateSelectionPriorityIfNeeded();
- assertEquals(appWindow.mAppPreferredFrameRate, 0, FLOAT_TOLERANCE);
+ assertEquals(appWindow.mFrameRateVote, FRAME_RATE_VOTE_NONE);
verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
@@ -292,11 +299,10 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
appWindow.mAttrs.preferredRefreshRate = 60;
assertEquals(0, mRefreshRatePolicy.getPreferredModeId(appWindow));
- assertEquals(60, mRefreshRatePolicy.getPreferredRefreshRate(appWindow), FLOAT_TOLERANCE);
appWindow.updateFrameRateSelectionPriorityIfNeeded();
assertEquals(RefreshRatePolicy.LAYER_PRIORITY_UNSET, appWindow.mFrameRateSelectionPriority);
- assertEquals(60, appWindow.mAppPreferredFrameRate, FLOAT_TOLERANCE);
+ assertEquals(FRAME_RATE_VOTE_60_PREFERRED, appWindow.mFrameRateVote);
// Call the function a few times.
appWindow.updateFrameRateSelectionPriorityIfNeeded();
@@ -307,6 +313,6 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
any(SurfaceControl.class), anyInt());
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
appWindow.getSurfaceControl(), 60,
- Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
+ Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 9d2eb26f5f21..bcaf8860b072 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -16,22 +16,29 @@
package com.android.server.wm;
+import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.hardware.display.DisplayManager;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.view.Display.Mode;
+import android.view.Surface;
import android.view.WindowManager.LayoutParams;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
+import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,7 +52,6 @@ import org.junit.runner.RunWith;
@RunWith(WindowTestRunner.class)
@FlakyTest
public class RefreshRatePolicyTest extends WindowTestsBase {
- private static final float FLOAT_TOLERANCE = 0.01f;
private static final int HI_MODE_ID = 1;
private static final float HI_REFRESH_RATE = 90;
@@ -57,6 +63,19 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
private RefreshRatePolicy mPolicy;
private HighRefreshRateDenylist mDenylist = mock(HighRefreshRateDenylist.class);
+ private FrameRateVote mTempFrameRateVote = new FrameRateVote();
+
+ private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote();
+ private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST =
+ new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ private static final FrameRateVote FRAME_RATE_VOTE_LOW_EXACT =
+ new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ private static final FrameRateVote FRAME_RATE_VOTE_HI_EXACT =
+ new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT);
+ private static final FrameRateVote FRAME_RATE_VOTE_LOW_PREFERRED =
+ new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
+ private static final FrameRateVote FRAME_RATE_VOTE_HI_PREFERRED =
+ new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
// Parcel and Unparcel the LayoutParams in the window state to test the path the object
// travels from the app's process to system server
@@ -89,6 +108,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
WindowState createWindow(String name) {
WindowState window = createWindow(null, TYPE_BASE_APPLICATION, name);
when(window.getDisplayInfo()).thenReturn(mDisplayInfo);
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
return window;
}
@@ -98,20 +119,23 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
cameraUsingWindow.mAttrs.packageName = "com.android.test";
parcelLayoutParams(cameraUsingWindow);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.addRefreshRateRangeForPackage("com.android.test",
LOW_REFRESH_RATE, LOW_REFRESH_RATE);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.removeRefreshRateRangeForPackage("com.android.test");
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
}
@@ -122,20 +146,23 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
cameraUsingWindow.mAttrs.packageName = "com.android.test";
parcelLayoutParams(cameraUsingWindow);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.addRefreshRateRangeForPackage("com.android.test",
LOW_REFRESH_RATE, MID_REFRESH_RATE);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(MID_REFRESH_RATE,
mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.removeRefreshRateRangeForPackage("com.android.test");
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
}
@@ -146,20 +173,23 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
cameraUsingWindow.mAttrs.packageName = "com.android.test";
parcelLayoutParams(cameraUsingWindow);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.addRefreshRateRangeForPackage("com.android.test",
LOW_REFRESH_RATE - 10, HI_REFRESH_RATE + 10);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(HI_REFRESH_RATE,
mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
mPolicy.removeRefreshRateRangeForPackage("com.android.test");
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
}
@@ -171,8 +201,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
parcelLayoutParams(denylistedWindow);
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(0, mPolicy.getPreferredModeId(denylistedWindow));
- assertEquals(LOW_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(denylistedWindow));
+ assertEquals(FRAME_RATE_VOTE_DENY_LIST, denylistedWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(denylistedWindow), FLOAT_TOLERANCE);
}
@@ -185,8 +215,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
parcelLayoutParams(overrideWindow);
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(HI_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(HI_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_HI_EXACT, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@@ -199,8 +229,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
parcelLayoutParams(overrideWindow);
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(HI_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@@ -214,8 +244,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
mPolicy.addRefreshRateRangeForPackage("com.android.test",
LOW_REFRESH_RATE, LOW_REFRESH_RATE);
assertEquals(HI_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(HI_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_HI_EXACT, overrideWindow.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE,
@@ -231,8 +261,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
mPolicy.addRefreshRateRangeForPackage("com.android.test",
LOW_REFRESH_RATE, LOW_REFRESH_RATE);
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(HI_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, overrideWindow.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE,
@@ -246,8 +276,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
parcelLayoutParams(overrideWindow);
assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(LOW_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_LOW_EXACT, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
@@ -255,7 +285,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@@ -267,8 +298,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
overrideWindow.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
parcelLayoutParams(overrideWindow);
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(LOW_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
@@ -276,7 +307,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(overrideWindow), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
}
@@ -288,8 +320,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
parcelLayoutParams(window);
when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(LOW_REFRESH_RATE,
- mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_DENY_LIST, window.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
@@ -297,7 +329,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
window.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
}
@@ -311,7 +344,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
mPolicy.addRefreshRateRangeForPackage("com.android.test",
LOW_REFRESH_RATE, LOW_REFRESH_RATE);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE,
mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE,
@@ -321,7 +355,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
cameraUsingWindow.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(cameraUsingWindow));
- assertEquals(0, mPolicy.getPreferredRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(cameraUsingWindow));
+ assertEquals(FRAME_RATE_VOTE_NONE, cameraUsingWindow.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(cameraUsingWindow), FLOAT_TOLERANCE);
}
@@ -332,7 +367,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
window.mAttrs.preferredMaxDisplayRefreshRate = LOW_REFRESH_RATE;
parcelLayoutParams(window);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
@@ -340,7 +376,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
window.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
}
@@ -351,7 +388,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
window.mAttrs.preferredMinDisplayRefreshRate = LOW_REFRESH_RATE;
parcelLayoutParams(window);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
@@ -359,7 +397,8 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
window.getPendingTransaction(), mock(AnimationAdapter.class),
false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(0, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
}
@@ -370,8 +409,92 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
window.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
parcelLayoutParams(window);
assertEquals(0, mPolicy.getPreferredModeId(window));
- assertEquals(LOW_REFRESH_RATE, mPolicy.getPreferredRefreshRate(window), FLOAT_TOLERANCE);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, window.mFrameRateVote);
assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
}
+
+ @Test
+ public void testSwitchingTypeForExactVote() {
+ final WindowState window = createWindow("window");
+ window.mAttrs.preferredDisplayModeId = HI_MODE_ID;
+ parcelLayoutParams(window);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_HI_EXACT, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_HI_EXACT, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+ }
+
+ @Test
+ public void testSwitchingTypeForPreferredVote() {
+ final WindowState window = createWindow("window");
+ window.mAttrs.preferredRefreshRate = HI_REFRESH_RATE;
+ parcelLayoutParams(window);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_HI_PREFERRED, window.mFrameRateVote);
+ }
+
+ @Test
+ public void testSwitchingTypeForDenylist() {
+ when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
+
+ final WindowState window = createWindow("window");
+ window.mAttrs.packageName = "com.android.test";
+ parcelLayoutParams(window);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_NONE);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_LOW_EXACT, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+ assertFalse(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_LOW_EXACT, window.mFrameRateVote);
+
+ when(window.mWmService.mDisplayManagerInternal.getRefreshRateSwitchingType())
+ .thenReturn(DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+ assertTrue(mPolicy.updateFrameRateVote(window));
+ assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index 846a5066e036..5e1fae095db8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -192,16 +192,17 @@ public class SurfaceSyncGroupTest {
}
private static class SyncTarget implements SurfaceSyncGroup.SyncTarget {
- private SurfaceSyncGroup.SyncBufferCallback mSyncBufferCallback;
+ private SurfaceSyncGroup.TransactionReadyCallback mTransactionReadyCallback;
@Override
- public void onReadyToSync(SurfaceSyncGroup.SyncBufferCallback syncBufferCallback) {
- mSyncBufferCallback = syncBufferCallback;
+ public void onAddedToSyncGroup(SurfaceSyncGroup parentSyncGroup,
+ SurfaceSyncGroup.TransactionReadyCallback transactionReadyCallback) {
+ mTransactionReadyCallback = transactionReadyCallback;
}
void onBufferReady() {
SurfaceControl.Transaction t = new StubTransaction();
- mSyncBufferCallback.onBufferReady(t);
+ mTransactionReadyCallback.onTransactionReady(t);
}
}
}
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 66bf78b6a13b..d52c34bdc9e1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -36,6 +36,7 @@ import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_90;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
@@ -80,6 +81,7 @@ import android.util.DisplayMetrics;
import android.util.Xml;
import android.view.Display;
import android.view.DisplayInfo;
+import android.window.TaskFragmentOrganizer;
import androidx.test.filters.MediumTest;
@@ -433,6 +435,24 @@ public class TaskTests extends WindowTestsBase {
}
@Test
+ public void testPropagateFocusedStateToRootTask() {
+ final Task rootTask = createTask(mDefaultDisplay);
+ final Task leafTask = createTaskInRootTask(rootTask, 0 /* userId */);
+
+ final ActivityRecord activity = createActivityRecord(leafTask);
+
+ leafTask.getDisplayContent().setFocusedApp(activity);
+
+ assertTrue(leafTask.getTaskInfo().isFocused);
+ assertTrue(rootTask.getTaskInfo().isFocused);
+
+ leafTask.getDisplayContent().setFocusedApp(null);
+
+ assertFalse(leafTask.getTaskInfo().isFocused);
+ assertFalse(rootTask.getTaskInfo().isFocused);
+ }
+
+ @Test
public void testReturnsToHomeRootTask() throws Exception {
final Task task = createTask(1);
spyOn(task);
@@ -1471,6 +1491,26 @@ public class TaskTests extends WindowTestsBase {
tf0, parentTask.getTaskFragment(TaskFragment::isOrganizedTaskFragment));
}
+ @Test
+ public void testReorderActivityToFront() {
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
+ doNothing().when(task).onActivityVisibleRequestedChanged();
+ final ActivityRecord activity = task.getTopMostActivity();
+
+ final TaskFragment fragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
+ final ActivityRecord embeddedActivity = fragment.getTopMostActivity();
+ task.moveActivityToFront(activity);
+ assertEquals("Activity must be moved to front", activity, task.getTopMostActivity());
+
+ doNothing().when(fragment).sendTaskFragmentInfoChanged();
+ task.moveActivityToFront(embeddedActivity);
+ assertEquals("Activity must be moved to front", embeddedActivity,
+ task.getTopMostActivity());
+ assertEquals("Activity must not be embedded", embeddedActivity,
+ task.getTopChild());
+ }
+
private Task getTestTask() {
final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
return task.getBottomMostTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 3b64c512ca8f..690c2aa1fe2b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1176,7 +1176,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
assertTrue(rootTask2.isOrganized());
// Verify a back pressed does not call the organizer
- mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
+ mWm.mAtmService.mActivityClientController.onBackPressed(activity.token,
new IRequestFinishCallback.Default());
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1187,7 +1187,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
rootTask.mRemoteToken.toWindowContainerToken(), true);
// Verify now that the back press does call the organizer
- mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
+ mWm.mAtmService.mActivityClientController.onBackPressed(activity.token,
new IRequestFinishCallback.Default());
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1198,7 +1198,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
rootTask.mRemoteToken.toWindowContainerToken(), false);
// Verify now that the back press no longer calls the organizer
- mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token,
+ mWm.mAtmService.mActivityClientController.onBackPressed(activity.token,
new IRequestFinishCallback.Default());
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
@@ -1404,7 +1404,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
mWm.mWindowPlacerLocked.deferLayout();
rootTask.removeImmediately();
- mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(record.token,
+ mWm.mAtmService.mActivityClientController.onBackPressed(record.token,
new IRequestFinishCallback.Default());
waitUntilHandlersIdle();
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index ccec67e8687e..af37ed583438 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1851,7 +1851,7 @@ public class TelecomManager {
ITelecomService service = getTelecomService();
if (service != null) {
try {
- return service.getCallStateUsingPackage(mContext.getPackageName(),
+ return service.getCallStateUsingPackage(mContext.getOpPackageName(),
mContext.getAttributionTag());
} catch (RemoteException e) {
Log.d(TAG, "RemoteException calling getCallState().", e);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f38b902f7531..ed96a9b2f996 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -7095,6 +7095,274 @@ public class CarrierConfigManager {
public static final String KEY_REFRESH_GEOLOCATION_TIMEOUT_MILLIS_INT =
KEY_PREFIX + "refresh_geolocation_timeout_millis_int";
+ /**
+ * List of 3GPP access network technologies where e911 over IMS is supported
+ * in the home network and domestic 3rd-party networks. The order in the list represents
+ * the preference. The domain selection service shall scan the network type in the order
+ * of the preference.
+ *
+ * <p>Possible values are,
+ * {@link AccessNetworkConstants.AccessNetworkType#NGRAN}
+ * {@link AccessNetworkConstants.AccessNetworkType#EUTRAN}
+ *
+ * The default value for this key is
+ * {{@link AccessNetworkConstants.AccessNetworkType#EUTRAN},
+ * @hide
+ */
+ public static final String
+ KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
+ + "emergency_over_ims_supported_3gpp_network_types_int_array";
+
+ /**
+ * List of 3GPP access network technologies where e911 over IMS is supported
+ * in the roaming network and non-domestic 3rd-party networks. The order in the list
+ * represents the preference. The domain selection service shall scan the network type
+ * in the order of the preference.
+ *
+ * <p>Possible values are,
+ * {@link AccessNetworkConstants.AccessNetworkType#NGRAN}
+ * {@link AccessNetworkConstants.AccessNetworkType#EUTRAN}
+ *
+ * The default value for this key is
+ * {{@link AccessNetworkConstants.AccessNetworkType#EUTRAN},
+ * @hide
+ */
+ public static final String
+ KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
+ + "emergency_over_ims_roaming_supported_3gpp_network_types_int_array";
+
+ /**
+ * List of CS access network technologies where circuit-switched emergency calls are
+ * supported in the home network and domestic 3rd-party networks. The order in the list
+ * represents the preference. The domain selection service shall scan the network type
+ * in the order of the preference.
+ *
+ * <p>Possible values are,
+ * {@link AccessNetworkConstants.AccessNetworkType#GERAN}
+ * {@link AccessNetworkConstants.AccessNetworkType#UTRAN}
+ * {@link AccessNetworkConstants.AccessNetworkType#CDMA2000}
+ *
+ * The default value for this key is
+ * {{@link AccessNetworkConstants.AccessNetworkType#UTRAN},
+ * {@link AccessNetworkConstants.AccessNetworkType#GERAN}}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY =
+ KEY_PREFIX + "emergency_over_cs_supported_access_network_types_int_array";
+
+ /**
+ * List of CS access network technologies where circuit-switched emergency calls are
+ * supported in the roaming network and non-domestic 3rd-party networks. The order
+ * in the list represents the preference. The domain selection service shall scan
+ * the network type in the order of the preference.
+ *
+ * <p>Possible values are,
+ * {@link AccessNetworkConstants.AccessNetworkType#GERAN}
+ * {@link AccessNetworkConstants.AccessNetworkType#UTRAN}
+ * {@link AccessNetworkConstants.AccessNetworkType#CDMA2000}
+ *
+ * The default value for this key is
+ * {{@link AccessNetworkConstants.AccessNetworkType#UTRAN},
+ * {@link AccessNetworkConstants.AccessNetworkType#GERAN}}.
+ * @hide
+ */
+ public static final String
+ KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY = KEY_PREFIX
+ + "emergency_over_cs_roaming_supported_access_network_types_int_array";
+
+ /** @hide */
+ @IntDef({
+ DOMAIN_CS,
+ DOMAIN_PS_3GPP,
+ DOMAIN_PS_NON_3GPP
+ })
+ public @interface EmergencyDomain {}
+
+ /**
+ * Circuit switched domain.
+ * @hide
+ */
+ public static final int DOMAIN_CS = 1;
+
+ /**
+ * Packet switched domain over 3GPP networks.
+ * @hide
+ */
+ public static final int DOMAIN_PS_3GPP = 2;
+
+ /**
+ * Packet switched domain over non-3GPP networks such as Wi-Fi.
+ * @hide
+ */
+ public static final int DOMAIN_PS_NON_3GPP = 3;
+
+ /**
+ * Specifies the emergency call domain preference for the home network.
+ * The domain selection service shall choose the domain in the order
+ * for attempting the emergency call
+ *
+ * <p>Possible values are,
+ * {@link #DOMAIN_CS}
+ * {@link #DOMAIN_PS_3GPP}
+ * {@link #DOMAIN_PS_NON_3GPP}.
+ *
+ * The default value for this key is
+ * {{@link #DOMAIN_PS_3GPP},
+ * {@link #DOMAIN_CS},
+ * {@link #DOMAIN_PS_NON_3GPP}}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY =
+ KEY_PREFIX + "emergency_domain_preference_int_array";
+
+ /**
+ * Specifies the emergency call domain preference for the roaming network.
+ * The domain selection service shall choose the domain in the order
+ * for attempting the emergency call.
+ *
+ * <p>Possible values are,
+ * {@link #DOMAIN_CS}
+ * {@link #DOMAIN_PS_3GPP}
+ * {@link #DOMAIN_PS_NON_3GPP}.
+ *
+ * The default value for this key is
+ * {{@link #DOMAIN_PS_3GPP},
+ * {@link #DOMAIN_CS},
+ * {@link #DOMAIN_PS_NON_3GPP}}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY =
+ KEY_PREFIX + "emergency_domain_preference_roaming_int_array";
+
+ /**
+ * Specifies if emergency call shall be attempted on IMS, if PS is attached even though IMS
+ * is not registered and normal calls fallback to the CS networks.
+ *
+ * The default value for this key is {@code false}.
+ * @hide
+ */
+ public static final String KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL =
+ KEY_PREFIX + "prefer_ims_emergency_when_voice_calls_on_cs_bool";
+
+ /**
+ * Specifies maximum number of emergency call retries over Wi-Fi.
+ * This is valid only when {@link #DOMAIN_PS_NON_3GPP} is included in
+ * {@link #KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY} or
+ * {@link #KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY}.
+ *
+ * The default value for this key is 1.
+ * @hide
+ */
+ public static final String KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT =
+ KEY_PREFIX + "maximum_number_of_emergency_tries_over_vowifi_int";
+
+ /**
+ * Emergency scan timer to wait for scan results from radio before attempting the call
+ * over Wi-Fi. On timer expiry, if emergency call on Wi-Fi is allowed and possible,
+ * telephony shall cancel the scan and place the call on Wi-Fi. If emergency call on Wi-Fi
+ * is not possible, then domain seleciton continues to wait for the scan result from the
+ * radio. If an emergency scan result is received before the timer expires, the timer shall
+ * be stopped and no dialing over Wi-Fi will be tried. If this value is set to 0, then
+ * the timer is never started and domain selection waits for the scan result from the radio.
+ *
+ * The default value for the timer is 10 seconds.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_SCAN_TIMER_SEC_INT =
+ KEY_PREFIX + "emergency_scan_timer_sec_int";
+
+ /** @hide */
+ @IntDef(prefix = "SCAN_TYPE_",
+ value = {
+ SCAN_TYPE_NO_PREFERENCE,
+ SCAN_TYPE_FULL_SERVICE,
+ SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE})
+ public @interface EmergencyScanType {}
+
+ /**
+ * No specific preference given to the modem. Modem can return an emergency
+ * capable network either with limited service or full service.
+ * @hide
+ */
+ public static final int SCAN_TYPE_NO_PREFERENCE = 0;
+
+ /**
+ * Modem will attempt to camp on a network with full service only.
+ * @hide
+ */
+ public static final int SCAN_TYPE_FULL_SERVICE = 1;
+
+ /**
+ * Telephony shall attempt full service scan first.
+ * If a full service network is not found, telephony shall attempt a limited service scan.
+ * @hide
+ */
+ public static final int SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE = 2;
+
+ /**
+ * Specifies the preferred emergency network scanning type.
+ *
+ * <p>Possible values are,
+ * {@link #SCAN_TYPE_NO_PREFERENCE}
+ * {@link #SCAN_TYPE_FULL_SERVICE}
+ * {@link #SCAN_TYPE_FULL_SERVICE_FOLLOWED_BY_LIMITED_SERVICE}
+ *
+ * The default value for this key is {@link #SCAN_TYPE_NO_PREFERENCE}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT =
+ KEY_PREFIX + "emergency_network_scan_type_int";
+
+ /**
+ * Specifies the time by which a call should be set up on the current network
+ * once the call is routed on the network. If the call cannot be set up by timer expiry,
+ * call shall be re-dialed on the next available network.
+ * If this value is set to 0, the timer shall be disabled.
+ *
+ * The default value for this key is 0.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT =
+ KEY_PREFIX + "emergency_call_setup_timer_on_current_network_sec_int";
+
+ /**
+ * Specifies if emergency call shall be attempted on IMS only when IMS is registered.
+ * This is applicable only for the case PS is in service.
+ *
+ * The default value for this key is {@code false}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL =
+ KEY_PREFIX + "emergency_requires_ims_registration_bool";
+
+ /**
+ * Specifies if LTE is preferred when re-scanning networks after the failure of dialing
+ * over NR. If not, CS will be preferred.
+ *
+ * The default value for this key is {@code false}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL =
+ KEY_PREFIX + "emergency_lte_preferred_after_nr_failed_bool";
+
+ /**
+ * Specifies the numbers to be dialed over CDMA network in case of dialing over CS network.
+ *
+ * The default value for this key is an empty string array.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY =
+ KEY_PREFIX + "emergency_cdma_preferred_numbers_string_array";
+
+ /**
+ * Specifies if emergency call shall be attempted on IMS only when VoLTE is enabled.
+ *
+ * The default value for this key is {@code false}.
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL =
+ KEY_PREFIX + "emergency_requires_volte_enabled_bool";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putBoolean(KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL, false);
@@ -7111,6 +7379,56 @@ public class CarrierConfigManager {
defaults.putInt(KEY_EMERGENCY_REGISTRATION_TIMER_MILLIS_INT, 10000);
defaults.putInt(KEY_REFRESH_GEOLOCATION_TIMEOUT_MILLIS_INT, 5000);
+ defaults.putIntArray(
+ KEY_EMERGENCY_OVER_IMS_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+ new int[] {
+ AccessNetworkType.EUTRAN,
+ });
+
+ defaults.putIntArray(
+ KEY_EMERGENCY_OVER_IMS_ROAMING_SUPPORTED_3GPP_NETWORK_TYPES_INT_ARRAY,
+ new int[] {
+ AccessNetworkType.EUTRAN,
+ });
+
+ defaults.putIntArray(
+ KEY_EMERGENCY_OVER_CS_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+ new int[] {
+ AccessNetworkType.UTRAN,
+ AccessNetworkType.GERAN,
+ });
+
+ defaults.putIntArray(
+ KEY_EMERGENCY_OVER_CS_ROAMING_SUPPORTED_ACCESS_NETWORK_TYPES_INT_ARRAY,
+ new int[] {
+ AccessNetworkType.UTRAN,
+ AccessNetworkType.GERAN,
+ });
+
+ defaults.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_INT_ARRAY,
+ new int[] {
+ DOMAIN_PS_3GPP,
+ DOMAIN_CS,
+ DOMAIN_PS_NON_3GPP
+ });
+ defaults.putIntArray(KEY_EMERGENCY_DOMAIN_PREFERENCE_ROAMING_INT_ARRAY,
+ new int[] {
+ DOMAIN_PS_3GPP,
+ DOMAIN_CS,
+ DOMAIN_PS_NON_3GPP
+ });
+
+ defaults.putBoolean(KEY_PREFER_IMS_EMERGENCY_WHEN_VOICE_CALLS_ON_CS_BOOL, false);
+ defaults.putInt(KEY_MAXIMUM_NUMBER_OF_EMERGENCY_TRIES_OVER_VOWIFI_INT, 1);
+ defaults.putInt(KEY_EMERGENCY_SCAN_TIMER_SEC_INT, 10);
+ defaults.putInt(KEY_EMERGENCY_NETWORK_SCAN_TYPE_INT, SCAN_TYPE_NO_PREFERENCE);
+ defaults.putInt(KEY_EMERGENCY_CALL_SETUP_TIMER_ON_CURRENT_NETWORK_SEC_INT, 0);
+ defaults.putBoolean(KEY_EMERGENCY_REQUIRES_IMS_REGISTRATION_BOOL, false);
+ defaults.putBoolean(KEY_EMERGENCY_LTE_PREFERRED_AFTER_NR_FAILED_BOOL, false);
+ defaults.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, false);
+ defaults.putStringArray(KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY,
+ new String[] {});
+
return defaults;
}
}
@@ -7920,7 +8238,8 @@ public class CarrierConfigManager {
KEY_XCAP_OVER_UT_SUPPORTED_RATS_INT_ARRAY,
new int[] {
AccessNetworkType.EUTRAN,
- AccessNetworkType.IWLAN
+ AccessNetworkType.IWLAN,
+ AccessNetworkType.NGRAN
});
defaults.putString(KEY_UT_AS_SERVER_FQDN_STRING, "");
defaults.putBoolean(KEY_TERMINAL_BASED_CALL_WAITING_DEFAULT_ENABLED_BOOL, true);
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 06cfd6718664..6e3cfacf0208 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -107,7 +107,7 @@ public abstract class CellIdentity implements Parcelable {
if ((mMccStr != null && mMncStr == null) || (mMccStr == null && mMncStr != null)) {
AnomalyReporter.reportAnomaly(
- UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+ UUID.fromString("e257ae06-ac0a-44c0-ba63-823b9f07b3e4"),
"CellIdentity Missing Half of PLMN ID");
}
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
index 383561ad01a0..c352f2bc3f83 100644
--- a/telephony/java/android/telephony/DomainSelectionService.java
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -28,7 +28,6 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.ims.ImsReasonInfo;
@@ -703,9 +702,9 @@ public class DomainSelectionService extends Service {
}
@Override
- public void onDomainSelected(@RadioAccessNetworkType int accessNetworkType) {
+ public void onDomainSelected(@NetworkRegistrationInfo.Domain int domain) {
try {
- mCallback.onDomainSelected(accessNetworkType);
+ mCallback.onDomainSelected(domain);
} catch (Exception e) {
Rlog.e(TAG, "onDomainSelected e=" + e);
}
@@ -835,7 +834,14 @@ public class DomainSelectionService extends Service {
return Runnable::run;
}
- private @NonNull Executor getCachedExecutor() {
+ /**
+ * Gets the {@link Executor} which executes methods of this service.
+ * This method should be private when this service is implemented in a separated process
+ * other than telephony framework.
+ * @return {@link Executor} instance.
+ * @hide
+ */
+ public @NonNull Executor getCachedExecutor() {
synchronized (mExecutorLock) {
if (mExecutor == null) {
Executor e = getExecutor();
diff --git a/telephony/java/android/telephony/RadioAccessSpecifier.java b/telephony/java/android/telephony/RadioAccessSpecifier.java
index a403095eeceb..9511db622a27 100644
--- a/telephony/java/android/telephony/RadioAccessSpecifier.java
+++ b/telephony/java/android/telephony/RadioAccessSpecifier.java
@@ -161,9 +161,17 @@ public final class RadioAccessSpecifier implements Parcelable {
}
@Override
- public int hashCode () {
+ public int hashCode() {
return ((mRadioAccessNetwork * 31)
+ (Arrays.hashCode(mBands) * 37)
+ (Arrays.hashCode(mChannels)) * 39);
}
+
+ @Override
+ public String toString() {
+ return "RadioAccessSpecifier[mRadioAccessNetwork="
+ + AccessNetworkConstants.AccessNetworkType.toString(mRadioAccessNetwork)
+ + ", mBands=" + Arrays.toString(mBands)
+ + ", mChannels=" + Arrays.toString(mChannels) + "]";
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 541573cad3ec..8c3ef675dabc 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -8298,16 +8298,23 @@ public class TelephonyManager {
/** Authentication type for UICC challenge is EAP AKA. See RFC 4187 for details. */
public static final int AUTHTYPE_EAP_AKA = PhoneConstants.AUTH_CONTEXT_EAP_AKA;
/**
- * Authentication type for GBA Bootstrap Challenge is GBA_BOOTSTRAP.
- * See 3GPP 33.220 Section 5.3.2.
- * @hide
+ * Authentication type for GBA Bootstrap Challenge.
+ * Pass this authentication type into the {@link #getIccAuthentication} API to perform a GBA
+ * Bootstrap challenge (BSF), with {@code data} (generated according to the procedure defined in
+ * 3GPP 33.220 Section 5.3.2 step.4) in base64 encoding.
+ * This method will return the Bootstrapping response in base64 encoding when ICC authentication
+ * is completed.
+ * Ref 3GPP 33.220 Section 5.3.2.
*/
public static final int AUTHTYPE_GBA_BOOTSTRAP = PhoneConstants.AUTH_CONTEXT_GBA_BOOTSTRAP;
/**
- * Authentication type for GBA Network Application Functions (NAF) key
- * External Challenge is AUTHTYPE_GBA_NAF_KEY_EXTERNAL.
- * See 3GPP 33.220 Section 5.3.2.
- * @hide
+ * Authentication type for GBA Network Application Functions (NAF) key External Challenge.
+ * Pass this authentication type into the {@link #getIccAuthentication} API to perform a GBA
+ * Network Applications Functions (NAF) key External challenge using the NAF_ID parameter
+ * as the {@code data} in base64 encoding.
+ * This method will return the Ks_Ext_Naf key in base64 encoding when ICC authentication
+ * is completed.
+ * Ref 3GPP 33.220 Section 5.3.2.
*/
public static final int AUTHTYPE_GBA_NAF_KEY_EXTERNAL =
PhoneConstants.AUTHTYPE_GBA_NAF_KEY_EXTERNAL;
@@ -8336,7 +8343,8 @@ public class TelephonyManager {
*
* @param appType the icc application type, like {@link #APPTYPE_USIM}
* @param authType the authentication type, any one of {@link #AUTHTYPE_EAP_AKA} or
- * {@link #AUTHTYPE_EAP_SIM}
+ * {@link #AUTHTYPE_EAP_SIM} or {@link #AUTHTYPE_GBA_BOOTSTRAP} or
+ * {@link #AUTHTYPE_GBA_NAF_KEY_EXTERNAL}
* @param data authentication challenge data, base64 encoded.
* See 3GPP TS 31.102 7.1.2 for more details.
* @return the response of authentication. This value will be null in the following cases:
@@ -8364,7 +8372,8 @@ public class TelephonyManager {
* @param subId subscription ID used for authentication
* @param appType the icc application type, like {@link #APPTYPE_USIM}
* @param authType the authentication type, any one of {@link #AUTHTYPE_EAP_AKA} or
- * {@link #AUTHTYPE_EAP_SIM}
+ * {@link #AUTHTYPE_EAP_SIM} or {@link #AUTHTYPE_GBA_BOOTSTRAP} or
+ * {@link #AUTHTYPE_GBA_NAF_KEY_EXTERNAL}
* @param data authentication challenge data, base64 encoded.
* See 3GPP TS 31.102 7.1.2 for more details.
* @return the response of authentication. This value will be null in the following cases only
@@ -9531,12 +9540,13 @@ public class TelephonyManager {
/**
* Set the allowed network types of the device and provide the reason triggering the allowed
* network change.
- * <p>Requires permission: android.Manifest.MODIFY_PHONE_STATE or
+ * <p>Requires permission: {@link android.Manifest.permission#MODIFY_PHONE_STATE} or
* that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
- * This can be called for following reasons
+ * This can be called for following reasons:
* <ol>
- * <li>Allowed network types control by USER {@link #ALLOWED_NETWORK_TYPES_REASON_USER}
+ * <li>Allowed network types control by USER
+ * {@link TelephonyManager#ALLOWED_NETWORK_TYPES_REASON_USER}
* <li>Allowed network types control by carrier {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER}
* </ol>
* This API will result in allowing an intersection of allowed network types for all reasons,
@@ -9546,7 +9556,13 @@ public class TelephonyManager {
* @param allowedNetworkTypes The bitmask of allowed network type
* @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
- * @throws SecurityException if the caller does not have the required privileges
+ * @throws SecurityException if the caller does not have the required privileges or if the
+ * caller tries to use one of the following security-based reasons without
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE} permissions.
+ * <ol>
+ * <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}</li>
+ * <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS}</li>
+ * </ol>
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@RequiresFeature(
@@ -12530,7 +12546,7 @@ public class TelephonyManager {
Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e);
} catch (NullPointerException e) {
AnomalyReporter.reportAnomaly(
- UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
+ UUID.fromString("e2bed88e-def9-476e-bd71-3e572a8de6d1"),
"getServiceStateForSubscriber " + subId + " NPE");
}
return null;
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
index 489a589d2b62..b3682caa4eb3 100644
--- a/telephony/java/android/telephony/WwanSelectorCallback.java
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -18,7 +18,6 @@ package android.telephony;
import android.annotation.NonNull;
import android.os.CancellationSignal;
-import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
import android.telephony.DomainSelectionService.EmergencyScanType;
import java.util.List;
@@ -46,7 +45,7 @@ public interface WwanSelectorCallback {
* Notifies the FW that the domain has been selected. After this method is called,
* this interface can be discarded.
*
- * @param accessNetworkType the selected network type.
+ * @param domain The selected domain.
*/
- void onDomainSelected(@RadioAccessNetworkType int accessNetworkType);
+ void onDomainSelected(@NetworkRegistrationInfo.Domain int domain);
}
diff --git a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
index 65d994b9066b..339fbee91812 100644
--- a/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
@@ -21,6 +21,6 @@ import com.android.internal.telephony.IWwanSelectorResultCallback;
oneway interface IWwanSelectorCallback {
void onRequestEmergencyNetworkScan(in int[] preferredNetworks,
int scanType, in IWwanSelectorResultCallback cb);
- void onDomainSelected(int accessNetworkType);
+ void onDomainSelected(int domain);
void onCancel();
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 9f612e6d7dd9..0c14dbaa5a3f 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -542,6 +542,10 @@ public interface RILConstants {
int RIL_REQUEST_STOP_IMS_TRAFFIC = 236;
int RIL_REQUEST_SEND_ANBR_QUERY = 237;
int RIL_REQUEST_TRIGGER_EPS_FALLBACK = 238;
+ int RIL_REQUEST_SET_NULL_CIPHER_AND_INTEGRITY_ENABLED = 239;
+ int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240;
+ int RIL_REQUEST_SET_N1_MODE_ENABLED = 241;
+ int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
new file mode 100644
index 000000000000..0f9663442740
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/os/TimeoutRecordTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+
+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 androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link TimeoutRecord}. */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class TimeoutRecordTest {
+
+ @Test
+ public void forBroadcastReceiver_returnsCorrectTimeoutRecord() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(ComponentName.createRelative("com.example.app", "ExampleClass"));
+
+ TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent);
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
+ assertEquals(record.mReason,
+ "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
+ + ".app/ExampleClass }");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forBroadcastReceiver_withTimeoutDurationMs_returnsCorrectTimeoutRecord() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(ComponentName.createRelative("com.example.app", "ExampleClass"));
+
+ TimeoutRecord record = TimeoutRecord.forBroadcastReceiver(intent, 1000L);
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.BROADCAST_RECEIVER);
+ assertEquals(record.mReason,
+ "Broadcast of Intent { act=android.intent.action.MAIN cmp=com.example"
+ + ".app/ExampleClass }, waited 1000ms");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forInputDispatchNoFocusedWindow_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forInputDispatchNoFocusedWindow("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.INPUT_DISPATCH_NO_FOCUSED_WINDOW);
+ assertEquals(record.mReason,
+ "Test ANR reason");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forInputDispatchWindowUnresponsive_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forInputDispatchWindowUnresponsive("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.INPUT_DISPATCH_WINDOW_UNRESPONSIVE);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forServiceExec_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forServiceExec("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_EXEC);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forServiceStartWithEndTime_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forServiceStartWithEndTime("Test ANR reason", 1000L);
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.SERVICE_START);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertEquals(record.mEndUptimeMillis, 1000L);
+ assertTrue(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forContentProvider_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forContentProvider("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.CONTENT_PROVIDER);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertFalse(record.mEndTakenBeforeLocks);
+ }
+
+ @Test
+ public void forApp_returnsCorrectTimeoutRecord() {
+ TimeoutRecord record = TimeoutRecord.forApp("Test ANR reason");
+
+ assertNotNull(record);
+ assertEquals(record.mKind, TimeoutRecord.TimeoutKind.APP_REGISTERED);
+ assertEquals(record.mReason, "Test ANR reason");
+ assertFalse(record.mEndTakenBeforeLocks);
+ }
+}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 47750fc11a6e..4e597fb3b30a 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -3743,15 +3743,15 @@ ssize_t ResourceTable::Entry::flatten(Bundle* /* bundle */, const sp<AaptFile>&
size_t amt = 0;
ResTable_entry header;
memset(&header, 0, sizeof(header));
- header.size = htods(sizeof(header));
+ header.full.size = htods(sizeof(header));
const type ty = mType;
if (ty == TYPE_BAG) {
- header.flags |= htods(header.FLAG_COMPLEX);
+ header.full.flags |= htods(header.FLAG_COMPLEX);
}
if (isPublic) {
- header.flags |= htods(header.FLAG_PUBLIC);
+ header.full.flags |= htods(header.FLAG_PUBLIC);
}
- header.key.index = htodl(mNameIndex);
+ header.full.key.index = htodl(mNameIndex);
if (ty != TYPE_BAG) {
status_t err = data->writeData(&header, sizeof(header));
if (err != NO_ERROR) {
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index f9e52b491413..df878899fa28 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -687,32 +687,32 @@ class ChunkPrinter {
continue;
}
- printer_->Print((entry->flags & ResTable_entry::FLAG_COMPLEX) ? "[ResTable_map_entry]"
- : "[ResTable_entry]");
+ if (entry->is_complex()) {
+ printer_->Print("[ResTable_map_entry]");
+ } else if (entry->is_compact()) {
+ printer_->Print("[ResTable_entry_compact]");
+ } else {
+ printer_->Print("[ResTable_entry]");
+ }
+
printer_->Print(StringPrintf(" id: 0x%04x", it.index()));
printer_->Print(StringPrintf(
- " name: %s",
- android::util::GetString(key_pool_, android::util::DeviceToHost32(entry->key.index))
- .c_str()));
- printer_->Print(
- StringPrintf(" keyIndex: %u", android::util::DeviceToHost32(entry->key.index)));
- printer_->Print(StringPrintf(" size: %u", android::util::DeviceToHost32(entry->size)));
- printer_->Print(StringPrintf(" flags: 0x%04x", android::util::DeviceToHost32(entry->flags)));
+ " name: %s", android::util::GetString(key_pool_, entry->key()).c_str()));
+ printer_->Print(StringPrintf(" keyIndex: %u", entry->key()));
+ printer_->Print(StringPrintf(" size: %zu", entry->size()));
+ printer_->Print(StringPrintf(" flags: 0x%04x", entry->flags()));
printer_->Indent();
- if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
- auto map_entry = (const ResTable_map_entry*)entry;
- printer_->Print(
- StringPrintf(" count: 0x%04x", android::util::DeviceToHost32(map_entry->count)));
+ if (auto map_entry = entry->map_entry()) {
+ uint32_t map_entry_count = android::util::DeviceToHost32(map_entry->count);
+ printer_->Print(StringPrintf(" count: 0x%04x", map_entry_count));
printer_->Print(StringPrintf(" parent: 0x%08x\n",
android::util::DeviceToHost32(map_entry->parent.ident)));
// Print the name and value mappings
- auto maps = (const ResTable_map*)((const uint8_t*)entry +
- android::util::DeviceToHost32(entry->size));
- for (size_t i = 0, count = android::util::DeviceToHost32(map_entry->count); i < count;
- i++) {
+ auto maps = (const ResTable_map*)((const uint8_t*)entry + entry->size());
+ for (size_t i = 0; i < map_entry_count; i++) {
PrintResValue(&(maps[i].value), config, type);
printer_->Print(StringPrintf(
@@ -725,9 +725,8 @@ class ChunkPrinter {
printer_->Print("\n");
// Print the value of the entry
- auto value =
- (const Res_value*)((const uint8_t*)entry + android::util::DeviceToHost32(entry->size));
- PrintResValue(value, config, type);
+ Res_value value = entry->value();
+ PrintResValue(&value, config, type);
}
printer_->Undent();
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index cffcdf2f1710..5fdfb66bdf4e 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -159,6 +159,9 @@ class LinkCommand : public Command {
AddOptionalSwitch("--enable-sparse-encoding",
"This decreases APK size at the cost of resource retrieval performance.",
&options_.use_sparse_encoding);
+ AddOptionalSwitch("--enable-compact-entries",
+ "This decreases APK size by using compact resource entries for simple data types.",
+ &options_.table_flattener_options.use_compact_entries);
AddOptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01.",
&legacy_x_flag_);
AddOptionalSwitch("-z", "Require localization of strings marked 'suggested'.",
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index d9e379db84b7..82918629f1f4 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -384,21 +384,16 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
continue;
}
- const ResourceName name(
- package->name, *parsed_type,
- android::util::GetString(key_pool_, android::util::DeviceToHost32(entry->key.index)));
+ const ResourceName name(package->name, *parsed_type,
+ android::util::GetString(key_pool_, entry->key()));
const ResourceId res_id(package_id, type->id, static_cast<uint16_t>(it.index()));
std::unique_ptr<Value> resource_value;
- if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
- const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
-
+ if (auto mapEntry = entry->map_entry()) {
// TODO(adamlesinski): Check that the entry count is valid.
resource_value = ParseMapEntry(name, config, mapEntry);
} else {
- const Res_value* value =
- (const Res_value*)((const uint8_t*)entry + android::util::DeviceToHost32(entry->size));
- resource_value = ParseValue(name, config, *value);
+ resource_value = ParseValue(name, config, entry->value());
}
if (!resource_value) {
@@ -419,7 +414,7 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
.SetId(res_id, OnIdConflict::CREATE_ENTRY)
.SetAllowMangled(true);
- if (entry->flags & ResTable_entry::FLAG_PUBLIC) {
+ if (entry->flags() & ResTable_entry::FLAG_PUBLIC) {
Visibility visibility{Visibility::Level::kPublic};
auto spec_flags = entry_type_spec_flags_.find(res_id);
diff --git a/tools/aapt2/format/binary/ResEntryWriter.cpp b/tools/aapt2/format/binary/ResEntryWriter.cpp
index 8832c24842ee..9dc205f4c1ba 100644
--- a/tools/aapt2/format/binary/ResEntryWriter.cpp
+++ b/tools/aapt2/format/binary/ResEntryWriter.cpp
@@ -24,11 +24,6 @@
namespace aapt {
-using android::BigBuffer;
-using android::Res_value;
-using android::ResTable_entry;
-using android::ResTable_map;
-
struct less_style_entries {
bool operator()(const Style::Entry* a, const Style::Entry* b) const {
if (a->key.id) {
@@ -189,26 +184,40 @@ class MapFlattenVisitor : public ConstValueVisitor {
};
template <typename T>
-void WriteEntry(const FlatEntry* entry, T* out_result) {
+void WriteEntry(const FlatEntry* entry, T* out_result, bool compact = false) {
static_assert(std::is_same_v<ResTable_entry, T> || std::is_same_v<ResTable_entry_ext, T>,
"T must be ResTable_entry or ResTable_entry_ext");
ResTable_entry* out_entry = (ResTable_entry*)out_result;
+ uint16_t flags = 0;
+
if (entry->entry->visibility.level == Visibility::Level::kPublic) {
- out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
+ flags |= ResTable_entry::FLAG_PUBLIC;
}
if (entry->value->IsWeak()) {
- out_entry->flags |= ResTable_entry::FLAG_WEAK;
+ flags |= ResTable_entry::FLAG_WEAK;
}
if constexpr (std::is_same_v<ResTable_entry_ext, T>) {
- out_entry->flags |= ResTable_entry::FLAG_COMPLEX;
+ flags |= ResTable_entry::FLAG_COMPLEX;
}
- out_entry->flags = android::util::HostToDevice16(out_entry->flags);
- out_entry->key.index = android::util::HostToDevice32(entry->entry_key);
- out_entry->size = android::util::HostToDevice16(sizeof(T));
+ if (!compact) {
+ out_entry->full.flags = android::util::HostToDevice16(flags);
+ out_entry->full.key.index = android::util::HostToDevice32(entry->entry_key);
+ out_entry->full.size = android::util::HostToDevice16(sizeof(T));
+ } else {
+ Res_value value;
+ CHECK(entry->entry_key < 0xffffu) << "cannot encode key in 16-bit";
+ CHECK(compact && (std::is_same_v<ResTable_entry, T>)) << "cannot encode complex entry";
+ CHECK(ValueCast<Item>(entry->value)->Flatten(&value)) << "flatten failed";
+
+ flags |= ResTable_entry::FLAG_COMPACT | (value.dataType << 8);
+ out_entry->compact.flags = android::util::HostToDevice16(flags);
+ out_entry->compact.key = android::util::HostToDevice16(entry->entry_key);
+ out_entry->compact.data = value.data;
+ }
}
int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer) {
@@ -222,57 +231,26 @@ int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer) {
return offset;
}
-void WriteItemToPair(const FlatEntry* item_entry, ResEntryValuePair* out_pair) {
- static_assert(sizeof(ResEntryValuePair) == sizeof(ResTable_entry) + sizeof(Res_value),
- "ResEntryValuePair must not have padding between entry and value.");
-
- WriteEntry<ResTable_entry>(item_entry, &out_pair->entry);
-
- CHECK(ValueCast<Item>(item_entry->value)->Flatten(&out_pair->value)) << "flatten failed";
- out_pair->value.size = android::util::HostToDevice16(sizeof(out_pair->value));
-}
-
-int32_t SequentialResEntryWriter::WriteMap(const FlatEntry* entry) {
- return WriteMapToBuffer(entry, entries_buffer_);
-}
-
-int32_t SequentialResEntryWriter::WriteItem(const FlatEntry* entry) {
- int32_t offset = entries_buffer_->size();
- auto* out_pair = entries_buffer_->NextBlock<ResEntryValuePair>();
- WriteItemToPair(entry, out_pair);
- return offset;
-}
-
-std::size_t ResEntryValuePairContentHasher::operator()(const ResEntryValuePairRef& ref) const {
- return android::JenkinsHashMixBytes(0, ref.ptr, sizeof(ResEntryValuePair));
-}
-
-bool ResEntryValuePairContentEqualTo::operator()(const ResEntryValuePairRef& a,
- const ResEntryValuePairRef& b) const {
- return std::memcmp(a.ptr, b.ptr, sizeof(ResEntryValuePair)) == 0;
-}
+template <bool compact_entry, typename T>
+std::pair<int32_t, T*> WriteItemToBuffer(const FlatEntry* item_entry, BigBuffer* buffer) {
+ int32_t offset = buffer->size();
+ T* out_entry = buffer->NextBlock<T>();
-int32_t DeduplicateItemsResEntryWriter::WriteMap(const FlatEntry* entry) {
- return WriteMapToBuffer(entry, entries_buffer_);
+ if constexpr (compact_entry) {
+ WriteEntry(item_entry, out_entry, true);
+ } else {
+ WriteEntry(item_entry, &out_entry->entry);
+ CHECK(ValueCast<Item>(item_entry->value)->Flatten(&out_entry->value)) << "flatten failed";
+ out_entry->value.size = android::util::HostToDevice16(sizeof(out_entry->value));
+ }
+ return {offset, out_entry};
}
-int32_t DeduplicateItemsResEntryWriter::WriteItem(const FlatEntry* entry) {
- int32_t initial_offset = entries_buffer_->size();
-
- auto* out_pair = entries_buffer_->NextBlock<ResEntryValuePair>();
- WriteItemToPair(entry, out_pair);
+// explicitly specialize both versions
+template std::pair<int32_t, ResEntryValue<false>*> WriteItemToBuffer<false>(
+ const FlatEntry* item_entry, BigBuffer* buffer);
- auto ref = ResEntryValuePairRef{*out_pair};
- auto [it, inserted] = entry_offsets.insert({ref, initial_offset});
- if (inserted) {
- // If inserted just return a new offset as this is a first time we store
- // this entry.
- return initial_offset;
- }
- // If not inserted this means that this is a duplicate, backup allocated block to the buffer
- // and return offset of previously stored entry.
- entries_buffer_->BackUp(sizeof(ResEntryValuePair));
- return it->second;
-}
+template std::pair<int32_t, ResEntryValue<true>*> WriteItemToBuffer<true>(
+ const FlatEntry* item_entry, BigBuffer* buffer);
-} // namespace aapt \ No newline at end of file
+} // namespace aapt
diff --git a/tools/aapt2/format/binary/ResEntryWriter.h b/tools/aapt2/format/binary/ResEntryWriter.h
index a36ceec2613b..c11598ec12f7 100644
--- a/tools/aapt2/format/binary/ResEntryWriter.h
+++ b/tools/aapt2/format/binary/ResEntryWriter.h
@@ -27,6 +27,11 @@
namespace aapt {
+using android::BigBuffer;
+using android::Res_value;
+using android::ResTable_entry;
+using android::ResTable_map;
+
struct FlatEntry {
const ResourceTableEntryView* entry;
const Value* value;
@@ -39,28 +44,42 @@ struct FlatEntry {
// We introduce this structure for ResEntryWriter to a have single allocation using
// BigBuffer::NextBlock which allows to return it back with BigBuffer::Backup.
struct ResEntryValuePair {
- android::ResTable_entry entry;
- android::Res_value value;
+ ResTable_entry entry;
+ Res_value value;
};
-// References ResEntryValuePair object stored in BigBuffer used as a key in std::unordered_map.
-// Allows access to memory address where ResEntryValuePair is stored.
-union ResEntryValuePairRef {
- const std::reference_wrapper<const ResEntryValuePair> pair;
+static_assert(sizeof(ResEntryValuePair) == sizeof(ResTable_entry) + sizeof(Res_value),
+ "ResEntryValuePair must not have padding between entry and value.");
+
+template <bool compact>
+using ResEntryValue = std::conditional_t<compact, ResTable_entry, ResEntryValuePair>;
+
+// References ResEntryValue object stored in BigBuffer used as a key in std::unordered_map.
+// Allows access to memory address where ResEntryValue is stored.
+template <bool compact>
+union ResEntryValueRef {
+ using T = ResEntryValue<compact>;
+ const std::reference_wrapper<const T> ref;
const u_char* ptr;
- explicit ResEntryValuePairRef(const ResEntryValuePair& ref) : pair(ref) {
+ explicit ResEntryValueRef(const T& rev) : ref(rev) {
}
};
-// Hasher which computes hash of ResEntryValuePair using its bytes representation in memory.
-struct ResEntryValuePairContentHasher {
- std::size_t operator()(const ResEntryValuePairRef& ref) const;
+// Hasher which computes hash of ResEntryValue using its bytes representation in memory.
+struct ResEntryValueContentHasher {
+ template <typename R>
+ std::size_t operator()(const R& ref) const {
+ return android::JenkinsHashMixBytes(0, ref.ptr, sizeof(typename R::T));
+ }
};
// Equaler which compares ResEntryValuePairs using theirs bytes representation in memory.
-struct ResEntryValuePairContentEqualTo {
- bool operator()(const ResEntryValuePairRef& a, const ResEntryValuePairRef& b) const;
+struct ResEntryValueContentEqualTo {
+ template <typename R>
+ bool operator()(const R& a, const R& b) const {
+ return std::memcmp(a.ptr, b.ptr, sizeof(typename R::T)) == 0;
+ }
};
// Base class that allows to write FlatEntries into entries_buffer.
@@ -79,9 +98,9 @@ class ResEntryWriter {
}
protected:
- ResEntryWriter(android::BigBuffer* entries_buffer) : entries_buffer_(entries_buffer) {
+ ResEntryWriter(BigBuffer* entries_buffer) : entries_buffer_(entries_buffer) {
}
- android::BigBuffer* entries_buffer_;
+ BigBuffer* entries_buffer_;
virtual int32_t WriteItem(const FlatEntry* entry) = 0;
@@ -91,18 +110,29 @@ class ResEntryWriter {
DISALLOW_COPY_AND_ASSIGN(ResEntryWriter);
};
+int32_t WriteMapToBuffer(const FlatEntry* map_entry, BigBuffer* buffer);
+
+template <bool compact_entry, typename T=ResEntryValue<compact_entry>>
+std::pair<int32_t, T*> WriteItemToBuffer(const FlatEntry* item_entry, BigBuffer* buffer);
+
// ResEntryWriter which writes FlatEntries sequentially into entries_buffer.
// Next entry is always written right after previous one in the buffer.
+template <bool compact_entry = false>
class SequentialResEntryWriter : public ResEntryWriter {
public:
- explicit SequentialResEntryWriter(android::BigBuffer* entries_buffer)
+ explicit SequentialResEntryWriter(BigBuffer* entries_buffer)
: ResEntryWriter(entries_buffer) {
}
~SequentialResEntryWriter() override = default;
- int32_t WriteItem(const FlatEntry* entry) override;
+ int32_t WriteItem(const FlatEntry* entry) override {
+ auto result = WriteItemToBuffer<compact_entry>(entry, entries_buffer_);
+ return result.first;
+ }
- int32_t WriteMap(const FlatEntry* entry) override;
+ int32_t WriteMap(const FlatEntry* entry) override {
+ return WriteMapToBuffer(entry, entries_buffer_);
+ }
private:
DISALLOW_COPY_AND_ASSIGN(SequentialResEntryWriter);
@@ -111,25 +141,44 @@ class SequentialResEntryWriter : public ResEntryWriter {
// ResEntryWriter that writes only unique entry and value pairs into entries_buffer.
// Next entry is written into buffer only if there is no entry with the same bytes representation
// in memory written before. Otherwise returns offset of already written entry.
+template <bool compact_entry = false>
class DeduplicateItemsResEntryWriter : public ResEntryWriter {
public:
- explicit DeduplicateItemsResEntryWriter(android::BigBuffer* entries_buffer)
+ explicit DeduplicateItemsResEntryWriter(BigBuffer* entries_buffer)
: ResEntryWriter(entries_buffer) {
}
~DeduplicateItemsResEntryWriter() override = default;
- int32_t WriteItem(const FlatEntry* entry) override;
+ int32_t WriteItem(const FlatEntry* entry) override {
+ const auto& [offset, out_entry] = WriteItemToBuffer<compact_entry>(entry, entries_buffer_);
+
+ auto [it, inserted] = entry_offsets.insert({Ref{*out_entry}, offset});
+ if (inserted) {
+ // If inserted just return a new offset as this is a first time we store
+ // this entry
+ return offset;
+ }
- int32_t WriteMap(const FlatEntry* entry) override;
+ // If not inserted this means that this is a duplicate, backup allocated block to the buffer
+ // and return offset of previously stored entry
+ entries_buffer_->BackUp(sizeof(*out_entry));
+ return it->second;
+ }
+
+ int32_t WriteMap(const FlatEntry* entry) override {
+ return WriteMapToBuffer(entry, entries_buffer_);
+ }
private:
DISALLOW_COPY_AND_ASSIGN(DeduplicateItemsResEntryWriter);
- std::unordered_map<ResEntryValuePairRef, int32_t, ResEntryValuePairContentHasher,
- ResEntryValuePairContentEqualTo>
- entry_offsets;
+ using Ref = ResEntryValueRef<compact_entry>;
+ using Map = std::unordered_map<Ref, int32_t,
+ ResEntryValueContentHasher,
+ ResEntryValueContentEqualTo>;
+ Map entry_offsets;
};
} // namespace aapt
-#endif \ No newline at end of file
+#endif
diff --git a/tools/aapt2/format/binary/ResEntryWriter_test.cpp b/tools/aapt2/format/binary/ResEntryWriter_test.cpp
index 56ca1332ec5d..4cb17c33e64a 100644
--- a/tools/aapt2/format/binary/ResEntryWriter_test.cpp
+++ b/tools/aapt2/format/binary/ResEntryWriter_test.cpp
@@ -56,14 +56,28 @@ TEST_F(SequentialResEntryWriterTest, WriteEntriesOneByOne) {
.AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
.Build();
- BigBuffer out(512);
- SequentialResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
-
- std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair),
- 2 * sizeof(ResEntryValuePair)};
- EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair));
- EXPECT_EQ(offsets, expected_offsets);
+ {
+ BigBuffer out(512);
+ SequentialResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResEntryValuePair),
+ 2 * sizeof(ResEntryValuePair)};
+ EXPECT_EQ(out.size(), 3 * sizeof(ResEntryValuePair));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* expect a compact entry to only take sizeof(ResTable_entry) */
+ BigBuffer out(512);
+ SequentialResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry),
+ 2 * sizeof(ResTable_entry)};
+ EXPECT_EQ(out.size(), 3 * sizeof(ResTable_entry));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
};
TEST_F(SequentialResEntryWriterTest, WriteMapEntriesOneByOne) {
@@ -83,13 +97,26 @@ TEST_F(SequentialResEntryWriterTest, WriteMapEntriesOneByOne) {
.AddValue("com.app.test:array/arr2", std::move(array2))
.Build();
- BigBuffer out(512);
- SequentialResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ SequentialResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
- EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
- EXPECT_EQ(offsets, expected_offsets);
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* compact_entry should have no impact to map items */
+ BigBuffer out(512);
+ SequentialResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
};
TEST_F(DeduplicateItemsResEntryWriterTest, DeduplicateItemEntries) {
@@ -100,13 +127,26 @@ TEST_F(DeduplicateItemsResEntryWriterTest, DeduplicateItemEntries) {
.AddSimple("com.app.test:id/id3", ResourceId(0x7f010002))
.Build();
- BigBuffer out(512);
- DeduplicateItemsResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, 0, 0};
- EXPECT_EQ(out.size(), sizeof(ResEntryValuePair));
- EXPECT_EQ(offsets, expected_offsets);
+ std::vector<int32_t> expected_offsets{0, 0, 0};
+ EXPECT_EQ(out.size(), sizeof(ResEntryValuePair));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* expect a compact entry to only take sizeof(ResTable_entry) */
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, 0, 0};
+ EXPECT_EQ(out.size(), sizeof(ResTable_entry));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
};
TEST_F(DeduplicateItemsResEntryWriterTest, WriteMapEntriesOneByOne) {
@@ -126,13 +166,26 @@ TEST_F(DeduplicateItemsResEntryWriterTest, WriteMapEntriesOneByOne) {
.AddValue("com.app.test:array/arr2", std::move(array2))
.Build();
- BigBuffer out(512);
- DeduplicateItemsResEntryWriter writer(&out);
- auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+ {
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<false> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
- std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
- EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
- EXPECT_EQ(offsets, expected_offsets);
-};
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+
+ {
+ /* compact_entry should have no impact to map items */
+ BigBuffer out(512);
+ DeduplicateItemsResEntryWriter<true> writer(&out);
+ auto offsets = WriteAllEntries(table->GetPartitionedView(), writer);
+
+ std::vector<int32_t> expected_offsets{0, sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)};
+ EXPECT_EQ(out.size(), 2 * (sizeof(ResTable_entry_ext) + 2 * sizeof(ResTable_map)));
+ EXPECT_EQ(offsets, expected_offsets);
+ }
+ };
} // namespace aapt \ No newline at end of file
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 318b8b6dcd31..f19223411232 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -16,6 +16,7 @@
#include "format/binary/TableFlattener.h"
+#include <limits>
#include <sstream>
#include <type_traits>
#include <variant>
@@ -67,7 +68,9 @@ class PackageFlattener {
public:
PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package,
const std::map<size_t, std::string>* shared_libs,
- SparseEntriesMode sparse_entries, bool collapse_key_stringpool,
+ SparseEntriesMode sparse_entries,
+ bool compact_entries,
+ bool collapse_key_stringpool,
const std::set<ResourceName>& name_collapse_exemptions,
bool deduplicate_entry_values)
: context_(context),
@@ -75,6 +78,7 @@ class PackageFlattener {
package_(package),
shared_libs_(shared_libs),
sparse_entries_(sparse_entries),
+ compact_entries_(compact_entries),
collapse_key_stringpool_(collapse_key_stringpool),
name_collapse_exemptions_(name_collapse_exemptions),
deduplicate_entry_values_(deduplicate_entry_values) {
@@ -135,6 +139,33 @@ class PackageFlattener {
private:
DISALLOW_COPY_AND_ASSIGN(PackageFlattener);
+ // Use compact entries only if
+ // 1) it is enabled, and that
+ // 2) the entries will be accessed on platforms U+, and
+ // 3) all entry keys can be encoded in 16 bits
+ bool UseCompactEntries(const ConfigDescription& config, std::vector<FlatEntry>* entries) const {
+ return compact_entries_ &&
+ (context_->GetMinSdkVersion() > SDK_TIRAMISU || config.sdkVersion > SDK_TIRAMISU) &&
+ std::none_of(entries->cbegin(), entries->cend(),
+ [](const auto& e) { return e.entry_key >= std::numeric_limits<uint16_t>::max(); });
+ }
+
+ std::unique_ptr<ResEntryWriter> GetResEntryWriter(bool dedup, bool compact, BigBuffer* buffer) {
+ if (dedup) {
+ if (compact) {
+ return std::make_unique<DeduplicateItemsResEntryWriter<true>>(buffer);
+ } else {
+ return std::make_unique<DeduplicateItemsResEntryWriter<false>>(buffer);
+ }
+ } else {
+ if (compact) {
+ return std::make_unique<SequentialResEntryWriter<true>>(buffer);
+ } else {
+ return std::make_unique<SequentialResEntryWriter<false>>(buffer);
+ }
+ }
+ }
+
bool FlattenConfig(const ResourceTableTypeView& type, const ConfigDescription& config,
const size_t num_total_entries, std::vector<FlatEntry>* entries,
BigBuffer* buffer) {
@@ -150,21 +181,20 @@ class PackageFlattener {
std::vector<uint32_t> offsets;
offsets.resize(num_total_entries, 0xffffffffu);
+ bool compact_entry = UseCompactEntries(config, entries);
+
android::BigBuffer values_buffer(512);
- std::variant<std::monostate, DeduplicateItemsResEntryWriter, SequentialResEntryWriter>
- writer_variant;
- ResEntryWriter* res_entry_writer;
- if (deduplicate_entry_values_) {
- res_entry_writer = &writer_variant.emplace<DeduplicateItemsResEntryWriter>(&values_buffer);
- } else {
- res_entry_writer = &writer_variant.emplace<SequentialResEntryWriter>(&values_buffer);
- }
+ auto res_entry_writer = GetResEntryWriter(deduplicate_entry_values_,
+ compact_entry, &values_buffer);
for (FlatEntry& flat_entry : *entries) {
CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
offsets[flat_entry.entry->id.value()] = res_entry_writer->Write(&flat_entry);
}
+ // whether the offsets can be represented in 2 bytes
+ bool short_offsets = (values_buffer.size() / 4u) < std::numeric_limits<uint16_t>::max();
+
bool sparse_encode = sparse_entries_ == SparseEntriesMode::Enabled ||
sparse_entries_ == SparseEntriesMode::Forced;
@@ -177,8 +207,7 @@ class PackageFlattener {
}
// Only sparse encode if the offsets are representable in 2 bytes.
- sparse_encode =
- sparse_encode && (values_buffer.size() / 4u) <= std::numeric_limits<uint16_t>::max();
+ sparse_encode = sparse_encode && short_offsets;
// Only sparse encode if the ratio of populated entries to total entries is below some
// threshold.
@@ -200,12 +229,22 @@ class PackageFlattener {
}
} else {
type_header->entryCount = android::util::HostToDevice32(num_total_entries);
- uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries);
- for (size_t i = 0; i < num_total_entries; i++) {
- indices[i] = android::util::HostToDevice32(offsets[i]);
+ if (compact_entry && short_offsets) {
+ // use 16-bit offset only when compact_entry is true
+ type_header->flags |= ResTable_type::FLAG_OFFSET16;
+ uint16_t* indices = type_writer.NextBlock<uint16_t>(num_total_entries);
+ for (size_t i = 0; i < num_total_entries; i++) {
+ indices[i] = android::util::HostToDevice16(offsets[i] / 4u);
+ }
+ } else {
+ uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries);
+ for (size_t i = 0; i < num_total_entries; i++) {
+ indices[i] = android::util::HostToDevice32(offsets[i]);
+ }
}
}
+ type_writer.buffer()->Align4();
type_header->entriesStart = android::util::HostToDevice32(type_writer.size());
type_writer.buffer()->AppendBuffer(std::move(values_buffer));
type_writer.Finish();
@@ -512,6 +551,7 @@ class PackageFlattener {
const ResourceTablePackageView package_;
const std::map<size_t, std::string>* shared_libs_;
SparseEntriesMode sparse_entries_;
+ bool compact_entries_;
android::StringPool type_pool_;
android::StringPool key_pool_;
bool collapse_key_stringpool_;
@@ -568,7 +608,9 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
}
PackageFlattener flattener(context, package, &table->included_packages_,
- options_.sparse_entries, options_.collapse_key_stringpool,
+ options_.sparse_entries,
+ options_.use_compact_entries,
+ options_.collapse_key_stringpool,
options_.name_collapse_exemptions,
options_.deduplicate_entry_values);
if (!flattener.FlattenPackage(&package_buffer)) {
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 6151b7e33b3f..35254ba24e53 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -44,6 +44,9 @@ struct TableFlattenerOptions {
// as a sparse map of entry ID and offset to actual data.
SparseEntriesMode sparse_entries = SparseEntriesMode::Disabled;
+ // When true, use compact entries for simple data
+ bool use_compact_entries = false;
+
// When true, the key string pool in the final ResTable
// is collapsed to a single entry. All resource entries
// have name indices that point to this single value
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
index 4d69d26e46db..741655b402f2 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt
@@ -20,6 +20,7 @@ import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
import com.google.android.lint.aidl.EnforcePermissionDetector
+import com.google.android.lint.aidl.EnforcePermissionHelperDetector
import com.google.android.lint.aidl.ManualPermissionCheckDetector
import com.google.android.lint.parcel.SaferParcelChecker
import com.google.auto.service.AutoService
@@ -38,6 +39,7 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() {
CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED,
EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
+ EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
ManualPermissionCheckDetector.ISSUE_USE_ENFORCE_PERMISSION_ANNOTATION,
SaferParcelChecker.ISSUE_UNSAFE_API_USAGE,
PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS,
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
new file mode 100644
index 000000000000..3c2ea1db0ad6
--- /dev/null
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiElement
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UDeclarationsExpression
+import org.jetbrains.uast.UExpression
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+ listOf(UMethod::class.java)
+
+ override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+ private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+ override fun visitMethod(node: UMethod) {
+ if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
+
+ val targetExpression = "super.${node.name}$HELPER_SUFFIX()"
+
+ val body = node.uastBody as? UBlockExpression
+ if (body == null) {
+ context.report(
+ ISSUE_ENFORCE_PERMISSION_HELPER,
+ context.getLocation(node),
+ "Method must start with $targetExpression",
+ )
+ return
+ }
+
+ val firstExpression = body.expressions.firstOrNull()
+ if (firstExpression == null) {
+ context.report(
+ ISSUE_ENFORCE_PERMISSION_HELPER,
+ context.getLocation(node),
+ "Method must start with $targetExpression",
+ )
+ return
+ }
+
+ val firstExpressionSource = firstExpression.asSourceString()
+ .filterNot(Char::isWhitespace)
+
+ if (firstExpressionSource != targetExpression) {
+ val locationTarget = getLocationTarget(firstExpression)
+ val expressionLocation = context.getLocation(locationTarget)
+ val indent = " ".repeat(expressionLocation.start?.column ?: 0)
+
+ val fix = fix()
+ .replace()
+ .range(expressionLocation)
+ .beginning()
+ .with("$targetExpression;\n\n$indent")
+ .reformat(true)
+ .autoFix()
+ .build()
+
+ context.report(
+ ISSUE_ENFORCE_PERMISSION_HELPER,
+ context.getLocation(node),
+ "Method must start with $targetExpression",
+ fix
+ )
+ }
+ }
+ }
+
+ companion object {
+ private const val HELPER_SUFFIX = "_enforcePermission"
+
+ private const val EXPLANATION = """
+ When @EnforcePermission is applied, the AIDL compiler generates a Stub method to do the
+ permission check called yourMethodName$HELPER_SUFFIX.
+
+ You must call this method as the first expression in your implementation.
+ """
+
+ val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
+ id = "MissingEnforcePermissionHelper",
+ briefDescription = """Missing permission-enforcing method call in AIDL method
+ |annotated with @EnforcePermission""".trimMargin(),
+ explanation = EXPLANATION,
+ category = Category.SECURITY,
+ priority = 6,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ EnforcePermissionHelperDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ /**
+ * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
+ * resulting in an incorrect Location if used directly
+ */
+ private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
+ if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
+ if (firstExpression is UDeclarationsExpression) {
+ return firstExpression.declarations.firstOrNull()?.sourcePsi
+ }
+ return null
+ }
+ }
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
new file mode 100644
index 000000000000..31e484628a04
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
@@ -0,0 +1,159 @@
+/*
+* Copyright (C) 2022 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+
+class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
+ override fun getDetector() = EnforcePermissionHelperDetector()
+ override fun getIssues() = listOf(
+ EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER)
+
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk()
+
+ fun testFirstExpressionIsFunctionCall() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ Binder.getCallingUid();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+ @Override
+ ^
+ 1 errors, 0 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Autofix for src/Foo.java line 5: Replace with super.test_enforcePermission();...:
+ @@ -8 +8
+ + super.test_enforcePermission();
+ +
+ """
+ )
+ }
+
+ fun testFirstExpressionIsVariableDeclaration() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ String foo = "bar";
+ Binder.getCallingUid();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+ @Override
+ ^
+ 1 errors, 0 warnings
+ """
+ )
+ .expectFixDiffs(
+ """
+ Autofix for src/Foo.java line 5: Replace with super.test_enforcePermission();...:
+ @@ -8 +8
+ + super.test_enforcePermission();
+ +
+ """
+ )
+ }
+
+ fun testMethodIsEmpty() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {}
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expect(
+ """
+ src/Foo.java:5: Error: Method must start with super.test_enforcePermission() [MissingEnforcePermissionHelper]
+ @Override
+ ^
+ 1 errors, 0 warnings
+ """
+ )
+ }
+
+ fun testOkay() {
+ lint().files(
+ java(
+ """
+ import android.content.Context;
+ import android.test.ITest;
+ public class Foo extends ITest.Stub {
+ private Context mContext;
+ @Override
+ @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS")
+ public void test() throws android.os.RemoteException {
+ super.test_enforcePermission();
+ }
+ }
+ """
+ ).indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ companion object {
+ val stubs = arrayOf(aidlStub, contextStub, binderStub)
+ }
+}
+
+
+
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
index a968f5e6cb1c..d4a34979ed9f 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/ManualPermissionCheckDetectorTest.kt
@@ -17,7 +17,6 @@
package com.google.android.lint.aidl
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
-import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.checks.infrastructure.TestMode
import com.android.tools.lint.detector.api.Detector
@@ -361,82 +360,6 @@ class ManualPermissionCheckDetectorTest : LintDetectorTest() {
companion object {
- private val aidlStub: TestFile = java(
- """
- package android.test;
- public interface ITest extends android.os.IInterface {
- public static abstract class Stub extends android.os.Binder implements android.test.ITest {}
- public void test() throws android.os.RemoteException;
- }
- """
- ).indented()
-
- private val contextStub: TestFile = java(
- """
- package android.content;
- public class Context {
- @android.content.pm.PermissionMethod
- public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
- }
- """
- ).indented()
-
- private val binderStub: TestFile = java(
- """
- package android.os;
- public class Binder {
- public static int getCallingUid() {}
- }
- """
- ).indented()
-
- private val permissionMethodStub: TestFile = java(
- """
- package android.content.pm;
-
- import static java.lang.annotation.ElementType.METHOD;
- import static java.lang.annotation.RetentionPolicy.CLASS;
-
- import java.lang.annotation.Retention;
- import java.lang.annotation.Target;
-
- @Retention(CLASS)
- @Target({METHOD})
- public @interface PermissionMethod {}
- """
- ).indented()
-
- private val permissionNameStub: TestFile = java(
- """
- package android.content.pm;
-
- import static java.lang.annotation.ElementType.FIELD;
- import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
- import static java.lang.annotation.ElementType.METHOD;
- import static java.lang.annotation.ElementType.PARAMETER;
- import static java.lang.annotation.RetentionPolicy.CLASS;
-
- import java.lang.annotation.Retention;
- import java.lang.annotation.Target;
-
- @Retention(CLASS)
- @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
- public @interface PermissionName {}
- """
- ).indented()
-
- private val manifestStub: TestFile = java(
- """
- package android;
-
- public final class Manifest {
- public static final class permission {
- public static final String READ_CONTACTS="android.permission.READ_CONTACTS";
- }
- }
- """.trimIndent()
- )
-
val stubs = arrayOf(
aidlStub,
contextStub,
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
new file mode 100644
index 000000000000..bd6b1952847c
--- /dev/null
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/aidl/Stubs.kt
@@ -0,0 +1,80 @@
+package com.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest.java
+import com.android.tools.lint.checks.infrastructure.TestFile
+
+val aidlStub: TestFile = java(
+ """
+ package android.test;
+ public interface ITest extends android.os.IInterface {
+ public static abstract class Stub extends android.os.Binder implements android.test.ITest {}
+ public void test() throws android.os.RemoteException;
+ }
+ """
+).indented()
+
+val contextStub: TestFile = java(
+ """
+ package android.content;
+ public class Context {
+ @android.content.pm.PermissionMethod
+ public void enforceCallingOrSelfPermission(@android.content.pm.PermissionName String permission, String message) {}
+ }
+ """
+).indented()
+
+val binderStub: TestFile = java(
+ """
+ package android.os;
+ public class Binder {
+ public static int getCallingUid() {}
+ }
+ """
+).indented()
+
+val permissionMethodStub: TestFile = java(
+"""
+ package android.content.pm;
+
+ import static java.lang.annotation.ElementType.METHOD;
+ import static java.lang.annotation.RetentionPolicy.CLASS;
+
+ import java.lang.annotation.Retention;
+ import java.lang.annotation.Target;
+
+ @Retention(CLASS)
+ @Target({METHOD})
+ public @interface PermissionMethod {}
+ """
+).indented()
+
+val permissionNameStub: TestFile = java(
+"""
+ package android.content.pm;
+
+ import static java.lang.annotation.ElementType.FIELD;
+ import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+ import static java.lang.annotation.ElementType.METHOD;
+ import static java.lang.annotation.ElementType.PARAMETER;
+ import static java.lang.annotation.RetentionPolicy.CLASS;
+
+ import java.lang.annotation.Retention;
+ import java.lang.annotation.Target;
+
+ @Retention(CLASS)
+ @Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+ public @interface PermissionName {}
+ """
+).indented()
+
+val manifestStub: TestFile = java(
+ """
+ package android;
+
+ public final class Manifest {
+ public static final class permission {
+ public static final String READ_CONTACTS="android.permission.READ_CONTACTS";
+ }
+ }
+ """.trimIndent()
+) \ No newline at end of file