summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java4
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java317
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java75
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java8
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java2
-rw-r--r--core/java/android/app/WallpaperManager.java59
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java3
-rw-r--r--core/java/android/service/notification/TEST_MAPPING49
-rw-r--r--core/java/android/view/ISurfaceControlViewHost.aidl6
-rw-r--r--core/java/android/view/ISurfaceControlViewHostParent.aidl27
-rw-r--r--core/java/android/view/SurfaceControlViewHost.java15
-rw-r--r--core/java/android/view/SurfaceView.java60
-rw-r--r--core/java/android/view/ViewRootImpl.java14
-rw-r--r--core/java/android/view/WindowlessWindowManager.java48
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java106
-rw-r--r--core/java/android/view/contentprotection/ContentProtectionEventProcessor.java26
-rw-r--r--core/java/android/window/TransitionInfo.java10
-rw-r--r--core/res/res/values/config.xml10
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java360
-rw-r--r--core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java52
-rw-r--r--graphics/java/android/graphics/Bitmap.java51
-rw-r--r--graphics/java/android/graphics/Gainmap.java11
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml21
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java72
-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/transition/DefaultMixedHandler.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt35
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt200
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/StubTransaction.java313
-rw-r--r--libs/hwui/jni/Gainmap.cpp11
-rw-r--r--media/java/android/media/MediaCodec.java45
-rw-r--r--media/java/android/media/MediaFormat.java5
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt20
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java14
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java87
-rw-r--r--packages/SystemUI/AndroidManifest.xml4
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt69
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt23
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt8
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt4
-rw-r--r--packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java8
-rw-r--r--packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java15
-rw-r--r--packages/SystemUI/proguard.flags8
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml4
-rw-r--r--packages/SystemUI/res-keyguard/values-sw600dp-land/integers.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml5
-rw-r--r--packages/SystemUI/res-keyguard/values/ids.xml14
-rw-r--r--packages/SystemUI/res/values-television/config.xml5
-rw-r--r--packages/SystemUI/res/values/config.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml54
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt15
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java36
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java78
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java14
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusAreaView.kt118
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java27
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt119
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt134
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java47
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt9
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java8
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java24
-rw-r--r--services/core/java/com/android/server/WallpaperUpdateReceiver.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java11
-rw-r--r--services/core/java/com/android/server/am/UidObserverController.java75
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java1
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java22
-rw-r--r--services/core/java/com/android/server/biometrics/AuthSession.java37
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java25
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java37
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java17
-rw-r--r--services/core/java/com/android/server/biometrics/PreAuthInfo.java37
-rw-r--r--services/core/java/com/android/server/cpu/CpuInfoReader.java12
-rw-r--r--services/core/java/com/android/server/input/BatteryController.java57
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java16
-rw-r--r--services/core/java/com/android/server/input/KeyboardBacklightController.java16
-rw-r--r--services/core/java/com/android/server/input/UEventManager.java42
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java1
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java12
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordLogger.java44
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java4
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java76
-rw-r--r--services/core/java/com/android/server/notification/TEST_MAPPING49
-rw-r--r--services/core/java/com/android/server/pm/Settings.java9
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java5
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java19
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java7
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperData.java16
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java6
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java13
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java52
-rw-r--r--services/core/java/com/android/server/wm/EmbeddedWindowController.java5
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java2
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java12
-rw-r--r--services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java52
-rw-r--r--services/core/java/com/android/server/wm/Task.java33
-rw-r--r--services/core/java/com/android/server/wm/Transition.java95
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java12
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java225
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java52
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/affected_cpus1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/related_cpus1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_cur_freq1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_max_freq1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/stats/time_in_state4
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/affected_cpus1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/related_cpus1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_cur_freq1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_max_freq1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/stats/time_in_state4
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/affected_cpus1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/related_cpus1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_cur_freq1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_max_freq1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/stats/time_in_state4
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/affected_cpus1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/related_cpus1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_cur_freq1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_max_freq1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/stats/time_in_state4
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/background/cpus1
-rw-r--r--services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/top-app/cpus1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java103
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java568
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java37
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java40
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java139
-rw-r--r--services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java29
-rw-r--r--services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt3
-rw-r--r--services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt53
-rw-r--r--services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java53
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java168
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java10
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java2
-rw-r--r--tests/SilkFX/res/layout/gainmap_transform_test.xml122
-rw-r--r--tests/SilkFX/src/com/android/test/silkfx/Main.kt4
-rw-r--r--tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt28
-rw-r--r--tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt116
232 files changed, 5821 insertions, 1602 deletions
diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
index fd8ddbcf3809..6c8af39015f5 100644
--- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java
@@ -59,6 +59,10 @@ public interface JobSchedulerInternal {
*/
void reportAppUsage(String packageName, int userId);
+ /** @return {@code true} if the app is considered buggy from JobScheduler's perspective. */
+ boolean isAppConsideredBuggy(int callingUserId, @NonNull String callingPackageName,
+ int timeoutBlameUserId, @NonNull String timeoutBlamePackageName);
+
/**
* @return {@code true} if the given notification is associated with any user-initiated jobs.
*/
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
index 8a5d09404ebb..ae86afbd8380 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
@@ -123,21 +123,21 @@ class JobNotificationCoordinator {
if (oldDetails == null) {
if (jobStatus.startedAsUserInitiatedJob) {
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_initial_setNotification_call_required",
+ "job_scheduler.value_cntr_w_uid_initial_set_notification_call_required",
jobStatus.getUid());
} else {
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_initial_setNotification_call_optional",
+ "job_scheduler.value_cntr_w_uid_initial_set_notification_call_optional",
jobStatus.getUid());
}
} else {
if (jobStatus.startedAsUserInitiatedJob) {
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_subsequent_setNotification_call_required",
+ "job_scheduler.value_cntr_w_uid_subsequent_set_notification_call_required",
jobStatus.getUid());
} else {
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_subsequent_setNotification_call_optional",
+ "job_scheduler.value_cntr_w_uid_subsequent_set_notification_call_optional",
jobStatus.getUid());
}
if (oldDetails.notificationId != notificationId) {
@@ -145,7 +145,7 @@ class JobNotificationCoordinator {
removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED,
jobStatus);
Counter.logIncrementWithUid(
- "job_scheduler.value_cntr_w_uid_setNotification_changed_notification_ids",
+ "job_scheduler.value_cntr_w_uid_set_notification_changed_notification_ids",
jobStatus.getUid());
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index cbc9263a2c3d..f99bcf144b91 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -322,16 +322,25 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
".schedulePersisted out-of-quota logged";
+ private static final String QUOTA_TRACKER_TIMEOUT_UIJ_TAG = "timeout-uij";
+ private static final String QUOTA_TRACKER_TIMEOUT_EJ_TAG = "timeout-ej";
+ private static final String QUOTA_TRACKER_TIMEOUT_REG_TAG = "timeout-reg";
+ private static final String QUOTA_TRACKER_TIMEOUT_TOTAL_TAG = "timeout-total";
+ private static final String QUOTA_TRACKER_ANR_TAG = "anr";
private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED = new Category(
".schedulePersisted()");
private static final Category QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED = new Category(
".schedulePersisted out-of-quota logged");
- private static final Categorizer QUOTA_CATEGORIZER = (userId, packageName, tag) -> {
- if (QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG.equals(tag)) {
- return QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED;
- }
- return QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED;
- };
+ private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_UIJ =
+ new Category(QUOTA_TRACKER_TIMEOUT_UIJ_TAG);
+ private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_EJ =
+ new Category(QUOTA_TRACKER_TIMEOUT_EJ_TAG);
+ private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_REG =
+ new Category(QUOTA_TRACKER_TIMEOUT_REG_TAG);
+ private static final Category QUOTA_TRACKER_CATEGORY_TIMEOUT_TOTAL =
+ new Category(QUOTA_TRACKER_TIMEOUT_TOTAL_TAG);
+ private static final Category QUOTA_TRACKER_CATEGORY_ANR = new Category(QUOTA_TRACKER_ANR_TAG);
+ private static final Category QUOTA_TRACKER_CATEGORY_DISABLED = new Category("disabled");
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
@@ -493,10 +502,18 @@ public class JobSchedulerService extends com.android.server.SystemService
}
switch (name) {
case Constants.KEY_ENABLE_API_QUOTAS:
+ case Constants.KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC:
case Constants.KEY_API_QUOTA_SCHEDULE_COUNT:
case Constants.KEY_API_QUOTA_SCHEDULE_WINDOW_MS:
case Constants.KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT:
case Constants.KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT:
+ case Constants.KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS:
if (!apiQuotaScheduleUpdated) {
mConstants.updateApiQuotaConstantsLocked();
updateQuotaTracker();
@@ -583,10 +600,26 @@ public class JobSchedulerService extends com.android.server.SystemService
@VisibleForTesting
void updateQuotaTracker() {
- mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS);
+ mQuotaTracker.setEnabled(
+ mConstants.ENABLE_API_QUOTAS || mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC);
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED,
mConstants.API_QUOTA_SCHEDULE_COUNT,
mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_UIJ,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_EJ,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_REG,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_TIMEOUT_TOTAL,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_ANR,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_ANR_COUNT,
+ mConstants.EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS);
}
/**
@@ -616,6 +649,8 @@ public class JobSchedulerService extends com.android.server.SystemService
"conn_low_signal_strength_relax_frac";
private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
"prefetch_force_batch_relax_threshold_ms";
+ // This has been enabled for 3+ full releases. We're unlikely to disable it.
+ // TODO(141645789): remove this flag
private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count";
private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms";
@@ -623,6 +658,22 @@ public class JobSchedulerService extends com.android.server.SystemService
"aq_schedule_throw_exception";
private static final String KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
"aq_schedule_return_failure";
+ private static final String KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC =
+ "enable_execution_safeguards_udc";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT =
+ "es_u_timeout_uij_count";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT =
+ "es_u_timeout_ej_count";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT =
+ "es_u_timeout_reg_count";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT =
+ "es_u_timeout_total_count";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS =
+ "es_u_timeout_window_ms";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT =
+ "es_u_anr_count";
+ private static final String KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS =
+ "es_u_anr_window_ms";
private static final String KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS =
"runtime_free_quota_max_limit_ms";
@@ -662,6 +713,17 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
private static final boolean DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
+ private static final boolean DEFAULT_ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
+ private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+ // EJs have a shorter timeout, so set a higher limit for them to start with.
+ private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 5;
+ private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 3;
+ private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT = 10;
+ private static final long DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS =
+ 24 * HOUR_IN_MILLIS;
+ private static final int DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT = 3;
+ private static final long DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS =
+ 6 * HOUR_IN_MILLIS;
@VisibleForTesting
public static final long DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = 30 * MINUTE_IN_MILLIS;
@VisibleForTesting
@@ -774,6 +836,55 @@ public class JobSchedulerService extends com.android.server.SystemService
public boolean API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT =
DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT;
+ /**
+ * Whether to enable the execution safeguards added in UDC.
+ */
+ public boolean ENABLE_EXECUTION_SAFEGUARDS_UDC = DEFAULT_ENABLE_EXECUTION_SAFEGUARDS_UDC;
+ /**
+ * The maximum number of times an app can have a user-iniated job time out before the system
+ * begins removing some of the app's privileges.
+ */
+ public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT =
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT;
+ /**
+ * The maximum number of times an app can have an expedited job time out before the system
+ * begins removing some of the app's privileges.
+ */
+ public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT =
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT;
+ /**
+ * The maximum number of times an app can have a regular job time out before the system
+ * begins removing some of the app's privileges.
+ */
+ public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT =
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT;
+ /**
+ * The maximum number of times an app can have jobs time out before the system
+ * attempts to restrict most of the app's privileges.
+ */
+ public int EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT =
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT;
+ /**
+ * The time window that {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT},
+ * {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT},
+ * {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT}, and
+ * {@link #EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT} should be evaluated over.
+ */
+ public long EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS =
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS;
+
+ /**
+ * The maximum number of times an app can ANR from JobScheduler's perspective before
+ * JobScheduler will attempt to restrict the app.
+ */
+ public int EXECUTION_SAFEGUARDS_UDC_ANR_COUNT = DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT;
+ /**
+ * The time window that {@link #EXECUTION_SAFEGUARDS_UDC_ANR_COUNT}
+ * should be evaluated over.
+ */
+ public long EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS =
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS;
+
/** The maximum amount of time we will let a job run for when quota is "free". */
public long RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
@@ -915,6 +1026,9 @@ public class JobSchedulerService extends com.android.server.SystemService
private void updateApiQuotaConstantsLocked() {
ENABLE_API_QUOTAS = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_ENABLE_API_QUOTAS, DEFAULT_ENABLE_API_QUOTAS);
+ ENABLE_EXECUTION_SAFEGUARDS_UDC = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC, DEFAULT_ENABLE_EXECUTION_SAFEGUARDS_UDC);
// Set a minimum value on the quota limit so it's not so low that it interferes with
// legitimate use cases.
API_QUOTA_SCHEDULE_COUNT = Math.max(250,
@@ -931,6 +1045,40 @@ public class JobSchedulerService extends com.android.server.SystemService
DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
DEFAULT_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT);
+
+ // Set a minimum value on the timeout limit so it's not so low that it interferes with
+ // legitimate use cases.
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = Math.max(2,
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT));
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = Math.max(2,
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT));
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = Math.max(2,
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT));
+ final int highestTimeoutCount = Math.max(EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
+ Math.max(EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT));
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT = Math.max(highestTimeoutCount,
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT));
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS);
+ EXECUTION_SAFEGUARDS_UDC_ANR_COUNT = Math.max(1,
+ DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT));
+ EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS,
+ DEFAULT_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS);
}
private void updateRuntimeConstantsLocked() {
@@ -1029,6 +1177,23 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.print(KEY_API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT,
API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT).println();
+ pw.print(KEY_ENABLE_EXECUTION_SAFEGUARDS_UDC, ENABLE_EXECUTION_SAFEGUARDS_UDC)
+ .println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT,
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT).println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT,
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT).println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT,
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT).println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT,
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_TOTAL_COUNT).println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS,
+ EXECUTION_SAFEGUARDS_UDC_TIMEOUT_WINDOW_MS).println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_ANR_COUNT,
+ EXECUTION_SAFEGUARDS_UDC_ANR_COUNT).println();
+ pw.print(KEY_EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS,
+ EXECUTION_SAFEGUARDS_UDC_ANR_WINDOW_MS).println();
+
pw.print(KEY_RUNTIME_MIN_GUARANTEE_MS, RUNTIME_MIN_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_MIN_EJ_GUARANTEE_MS, RUNTIME_MIN_EJ_GUARANTEE_MS).println();
pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
@@ -2252,12 +2417,52 @@ public class JobSchedulerService extends com.android.server.SystemService
// Set up the app standby bucketing tracker
mStandbyTracker = new StandbyTracker();
mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
- mQuotaTracker = new CountQuotaTracker(context, QUOTA_CATEGORIZER);
- mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED,
- mConstants.API_QUOTA_SCHEDULE_COUNT,
- mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
+
+ final Categorizer quotaCategorizer = (userId, packageName, tag) -> {
+ if (QUOTA_TRACKER_TIMEOUT_UIJ_TAG.equals(tag)) {
+ return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+ ? QUOTA_TRACKER_CATEGORY_TIMEOUT_UIJ
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ if (QUOTA_TRACKER_TIMEOUT_EJ_TAG.equals(tag)) {
+ return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+ ? QUOTA_TRACKER_CATEGORY_TIMEOUT_EJ
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ if (QUOTA_TRACKER_TIMEOUT_REG_TAG.equals(tag)) {
+ return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+ ? QUOTA_TRACKER_CATEGORY_TIMEOUT_REG
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ if (QUOTA_TRACKER_TIMEOUT_TOTAL_TAG.equals(tag)) {
+ return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+ ? QUOTA_TRACKER_CATEGORY_TIMEOUT_TOTAL
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ if (QUOTA_TRACKER_ANR_TAG.equals(tag)) {
+ return mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC
+ ? QUOTA_TRACKER_CATEGORY_ANR
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ if (QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG.equals(tag)) {
+ return mConstants.ENABLE_API_QUOTAS
+ ? QUOTA_TRACKER_CATEGORY_SCHEDULE_PERSISTED
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ if (QUOTA_TRACKER_SCHEDULE_LOGGED.equals(tag)) {
+ return mConstants.ENABLE_API_QUOTAS
+ ? QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED
+ : QUOTA_TRACKER_CATEGORY_DISABLED;
+ }
+ Slog.wtf(TAG, "Unexpected category tag: " + tag);
+ return QUOTA_TRACKER_CATEGORY_DISABLED;
+ };
+ mQuotaTracker = new CountQuotaTracker(context, quotaCategorizer);
+ updateQuotaTracker();
// Log at most once per minute.
+ // Set outside updateQuotaTracker() since this is intentionally not configurable.
mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_SCHEDULE_LOGGED, 1, 60_000);
+ mQuotaTracker.setCountLimit(QUOTA_TRACKER_CATEGORY_DISABLED, Integer.MAX_VALUE, 60_000);
mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
mAppStandbyInternal.addListener(mStandbyTracker);
@@ -2762,6 +2967,48 @@ public class JobSchedulerService extends com.android.server.SystemService
0 /* Reset cumulativeExecutionTime because of successful execution */);
}
+ @VisibleForTesting
+ void maybeProcessBuggyJob(@NonNull JobStatus jobStatus, int debugStopReason) {
+ boolean jobTimedOut = debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
+ // If madeActive = 0, the job never actually started.
+ if (!jobTimedOut && jobStatus.madeActive > 0) {
+ final long executionDurationMs = sUptimeMillisClock.millis() - jobStatus.madeActive;
+ // The debug reason may be different if we stopped the job for some other reason
+ // (eg. constraints), so look at total execution time to be safe.
+ if (jobStatus.startedAsUserInitiatedJob) {
+ // TODO: factor in different min guarantees for different UI job types
+ jobTimedOut = executionDurationMs >= mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+ } else if (jobStatus.startedAsExpeditedJob) {
+ jobTimedOut = executionDurationMs >= mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+ } else {
+ jobTimedOut = executionDurationMs >= mConstants.RUNTIME_MIN_GUARANTEE_MS;
+ }
+ }
+ if (jobTimedOut) {
+ final int userId = jobStatus.getTimeoutBlameUserId();
+ final String pkg = jobStatus.getTimeoutBlamePackageName();
+ mQuotaTracker.noteEvent(userId, pkg,
+ jobStatus.startedAsUserInitiatedJob
+ ? QUOTA_TRACKER_TIMEOUT_UIJ_TAG
+ : (jobStatus.startedAsExpeditedJob
+ ? QUOTA_TRACKER_TIMEOUT_EJ_TAG
+ : QUOTA_TRACKER_TIMEOUT_REG_TAG));
+ if (!mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_TIMEOUT_TOTAL_TAG)) {
+ mAppStandbyInternal.restrictApp(
+ pkg, userId, UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
+ }
+ }
+
+ if (debugStopReason == JobParameters.INTERNAL_STOP_REASON_ANR) {
+ final int callingUserId = jobStatus.getUserId();
+ final String callingPkg = jobStatus.getServiceComponent().getPackageName();
+ if (!mQuotaTracker.noteEvent(callingUserId, callingPkg, QUOTA_TRACKER_ANR_TAG)) {
+ mAppStandbyInternal.restrictApp(callingPkg, callingUserId,
+ UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY);
+ }
+ }
+ }
+
// JobCompletedListener implementations.
/**
@@ -2784,6 +3031,8 @@ public class JobSchedulerService extends com.android.server.SystemService
mLastCompletedJobTimeElapsed[mLastCompletedJobIndex] = sElapsedRealtimeClock.millis();
mLastCompletedJobIndex = (mLastCompletedJobIndex + 1) % NUM_COMPLETED_JOB_HISTORY;
+ maybeProcessBuggyJob(jobStatus, debugStopReason);
+
if (debugStopReason == JobParameters.INTERNAL_STOP_REASON_UNINSTALL
|| debugStopReason == JobParameters.INTERNAL_STOP_REASON_DATA_CLEARED) {
// The job should have already been cleared from the rest of the JS tracking. No need
@@ -3511,26 +3760,36 @@ public class JobSchedulerService extends com.android.server.SystemService
if (job.shouldTreatAsUserInitiatedJob()
&& checkRunUserInitiatedJobsPermission(
job.getSourceUid(), job.getSourcePackageName())) {
+ // The calling package is the one doing the work, so use it in the
+ // timeout quota checks.
+ final boolean isWithinTimeoutQuota = mQuotaTracker.isWithinQuota(
+ job.getTimeoutBlameUserId(), job.getTimeoutBlamePackageName(),
+ QUOTA_TRACKER_TIMEOUT_UIJ_TAG);
+ final long upperLimitMs = isWithinTimeoutQuota
+ ? mConstants.RUNTIME_UI_LIMIT_MS
+ : mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
if (job.getJob().getRequiredNetwork() != null) {
// User-initiated data transfers.
if (mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS) {
final long estimatedTransferTimeMs =
mConnectivityController.getEstimatedTransferTimeMs(job);
if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
- return mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS;
+ return Math.min(upperLimitMs,
+ mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS);
}
// Try to give the job at least as much time as we think the transfer
// will take, but cap it at the maximum limit.
final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs
* mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
- return Math.min(mConstants.RUNTIME_UI_LIMIT_MS,
+ return Math.min(upperLimitMs,
Math.max(factoredTransferTimeMs,
mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
}
- return Math.max(mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
- mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS);
+ return Math.min(upperLimitMs,
+ Math.max(mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS));
}
- return mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+ return Math.min(upperLimitMs, mConstants.RUNTIME_MIN_UI_GUARANTEE_MS);
} else if (job.shouldTreatAsExpeditedJob()) {
// Don't guarantee RESTRICTED jobs more than 5 minutes.
return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
@@ -3547,13 +3806,24 @@ public class JobSchedulerService extends com.android.server.SystemService
synchronized (mLock) {
if (job.shouldTreatAsUserInitiatedJob()
&& checkRunUserInitiatedJobsPermission(
- job.getSourceUid(), job.getSourcePackageName())) {
+ job.getSourceUid(), job.getSourcePackageName())
+ && mQuotaTracker.isWithinQuota(job.getTimeoutBlameUserId(),
+ job.getTimeoutBlamePackageName(),
+ QUOTA_TRACKER_TIMEOUT_UIJ_TAG)) {
return mConstants.RUNTIME_UI_LIMIT_MS;
}
if (job.shouldTreatAsUserInitiatedJob()) {
return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
}
- return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ // Only let the app use the higher runtime if it hasn't repeatedly timed out.
+ final String timeoutTag = job.shouldTreatAsExpeditedJob()
+ ? QUOTA_TRACKER_TIMEOUT_EJ_TAG : QUOTA_TRACKER_TIMEOUT_REG_TAG;
+ final long upperLimitMs =
+ mQuotaTracker.isWithinQuota(job.getTimeoutBlameUserId(),
+ job.getTimeoutBlamePackageName(), timeoutTag)
+ ? mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS
+ : mConstants.RUNTIME_MIN_GUARANTEE_MS;
+ return Math.min(upperLimitMs,
mConstants.USE_TARE_POLICY
? mTareController.getMaxJobExecutionTimeMsLocked(job)
: mQuotaController.getMaxJobExecutionTimeMsLocked(job));
@@ -3797,6 +4067,17 @@ public class JobSchedulerService extends com.android.server.SystemService
}
@Override
+ public boolean isAppConsideredBuggy(int callingUserId, @NonNull String callingPackageName,
+ int timeoutBlameUserId, @NonNull String timeoutBlamePackageName) {
+ return !mQuotaTracker.isWithinQuota(callingUserId, callingPackageName,
+ QUOTA_TRACKER_ANR_TAG)
+ || !mQuotaTracker.isWithinQuota(callingUserId, callingPackageName,
+ QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)
+ || !mQuotaTracker.isWithinQuota(timeoutBlameUserId, timeoutBlamePackageName,
+ QUOTA_TRACKER_TIMEOUT_TOTAL_TAG);
+ }
+
+ @Override
public boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId,
int userId, @NonNull String packageName) {
if (packageName == null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 0b08b6faf971..5f795b6fedd9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -1331,7 +1331,7 @@ public final class JobServiceContext implements ServiceConnection {
// FINISHED/NO-RETRY.
onSlowAppResponseLocked(/* reschedule */ false, /* updateStopReasons */ true,
/* texCounterMetricId */
- "job_scheduler.value_cntr_w_uid_slow_app_response_onStartJob",
+ "job_scheduler.value_cntr_w_uid_slow_app_response_on_start_job",
/* debugReason */ "timed out while starting",
/* anrMessage */ "No response to onStartJob",
CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
@@ -1343,7 +1343,7 @@ public final class JobServiceContext implements ServiceConnection {
// other reason.
onSlowAppResponseLocked(/* reschedule */ true, /* updateStopReasons */ false,
/* texCounterMetricId */
- "job_scheduler.value_cntr_w_uid_slow_app_response_onStopJob",
+ "job_scheduler.value_cntr_w_uid_slow_app_response_on_stop_job",
/* debugReason */ "timed out while stopping",
/* anrMessage */ "No response to onStopJob",
CompatChanges.isChangeEnabled(ANR_PRE_UDC_APIS_ON_SLOW_RESPONSES,
@@ -1400,7 +1400,7 @@ public final class JobServiceContext implements ServiceConnection {
} else if (mAwaitingNotification) {
onSlowAppResponseLocked(/* reschedule */ true, /* updateStopReasons */ true,
/* texCounterMetricId */
- "job_scheduler.value_cntr_w_uid_slow_app_response_setNotification",
+ "job_scheduler.value_cntr_w_uid_slow_app_response_set_notification",
/* debugReason */ "timed out while stopping",
/* anrMessage */ "required notification not provided",
/* triggerAnr */ true);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index edd531d13965..3baa9e6d3de9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -1088,13 +1088,77 @@ public final class JobStatus {
return UserHandle.getUserId(callingUid);
}
+ private boolean shouldBlameSourceForTimeout() {
+ // If the system scheduled the job on behalf of an app, assume the app is the one
+ // doing the work and blame the app directly. This is the case with things like
+ // syncs via SyncManager.
+ // If the system didn't schedule the job on behalf of an app, then
+ // blame the app doing the actual work. Proxied jobs are a little tricky.
+ // Proxied jobs scheduled by built-in system apps like DownloadManager may be fine
+ // and we could consider exempting those jobs. For example, in DownloadManager's
+ // case, all it does is download files and the code is vetted. A timeout likely
+ // means it's downloading a large file, which isn't an error. For now, DownloadManager
+ // is an exempted app, so this shouldn't be an issue.
+ // However, proxied jobs coming from other system apps (such as those that can
+ // be updated separately from an OTA) may not be fine and we would want to apply
+ // this policy to those jobs/apps.
+ // TODO(284512488): consider exempting DownloadManager or other system apps
+ return UserHandle.isCore(callingUid);
+ }
+
+ /**
+ * Returns the package name that should most likely be blamed for the job timing out.
+ */
+ public String getTimeoutBlamePackageName() {
+ if (shouldBlameSourceForTimeout()) {
+ return sourcePackageName;
+ }
+ return getServiceComponent().getPackageName();
+ }
+
+ /**
+ * Returns the UID that should most likely be blamed for the job timing out.
+ */
+ public int getTimeoutBlameUid() {
+ if (shouldBlameSourceForTimeout()) {
+ return sourceUid;
+ }
+ return callingUid;
+ }
+
+ /**
+ * Returns the userId that should most likely be blamed for the job timing out.
+ */
+ public int getTimeoutBlameUserId() {
+ if (shouldBlameSourceForTimeout()) {
+ return sourceUserId;
+ }
+ return UserHandle.getUserId(callingUid);
+ }
+
/**
* Returns an appropriate standby bucket for the job, taking into account any standby
* exemptions.
*/
public int getEffectiveStandbyBucket() {
+ final JobSchedulerInternal jsi = LocalServices.getService(JobSchedulerInternal.class);
+ final boolean isBuggy = jsi.isAppConsideredBuggy(
+ getUserId(), getServiceComponent().getPackageName(),
+ getTimeoutBlameUserId(), getTimeoutBlamePackageName());
+
final int actualBucket = getStandbyBucket();
if (actualBucket == EXEMPTED_INDEX) {
+ // EXEMPTED apps always have their jobs exempted, even if they're buggy, because the
+ // user has explicitly told the system to avoid restricting the app for power reasons.
+ if (isBuggy) {
+ final String pkg;
+ if (getServiceComponent().getPackageName().equals(sourcePackageName)) {
+ pkg = sourcePackageName;
+ } else {
+ pkg = getServiceComponent().getPackageName() + "/" + sourcePackageName;
+ }
+ Slog.w(TAG, "Exempted app " + pkg + " considered buggy");
+ }
return actualBucket;
}
if (uidActive || getJob().isExemptedFromAppStandby()) {
@@ -1102,13 +1166,18 @@ public final class JobStatus {
// like other ACTIVE apps.
return ACTIVE_INDEX;
}
+ // If the app is considered buggy, but hasn't yet been put in the RESTRICTED bucket
+ // (potentially because it's used frequently by the user), limit its effective bucket
+ // so that it doesn't get to run as much as a normal ACTIVE app.
+ final int highestBucket = isBuggy ? WORKING_INDEX : ACTIVE_INDEX;
if (actualBucket != RESTRICTED_INDEX && actualBucket != NEVER_INDEX
&& mHasMediaBackupExemption) {
- // Cap it at WORKING_INDEX as media back up jobs are important to the user, and the
+ // Treat it as if it's at least WORKING_INDEX since media backup jobs are important
+ // to the user, and the
// source package may not have been used directly in a while.
- return Math.min(WORKING_INDEX, actualBucket);
+ return Math.max(highestBucket, Math.min(WORKING_INDEX, actualBucket));
}
- return actualBucket;
+ return Math.max(highestBucket, actualBucket);
}
/** Returns the real standby bucket of the job. */
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 07958dd0fef5..1c29982dbd48 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -773,6 +773,14 @@ public final class QuotaController extends StateController {
// If quota is currently "free", then the job can run for the full amount of time,
// regardless of bucket (hence using charging instead of isQuotaFreeLocked()).
if (mService.isBatteryCharging()
+ // The top and foreground cases here were added because apps in those states
+ // aren't really restricted and the work could be something the user is
+ // waiting for. Now that user-initiated jobs are a defined concept, we may
+ // not need these exemptions as much. However, UIJs are currently limited
+ // (as of UDC) to data transfer work. There may be other work that could
+ // rely on this exception. Once we add more UIJ types, we can re-evaluate
+ // the need for these exceptions.
+ // TODO: re-evaluate the need for these exceptions
|| mTopAppCache.get(jobStatus.getSourceUid())
|| isTopStartedJobLocked(jobStatus)
|| isUidInForeground(jobStatus.getSourceUid())) {
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 55e681521048..7d3837786be9 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -3025,7 +3025,7 @@ public class AppStandbyController
public static final long DEFAULT_INITIAL_FOREGROUND_SERVICE_START_TIMEOUT =
COMPRESS_TIME ? ONE_MINUTE : 30 * ONE_MINUTE;
public static final long DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS =
- COMPRESS_TIME ? ONE_MINUTE : ONE_DAY;
+ COMPRESS_TIME ? ONE_MINUTE : ONE_HOUR;
public static final boolean DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS = true;
public static final long DEFAULT_BROADCAST_RESPONSE_WINDOW_DURATION_MS =
2 * ONE_MINUTE;
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index cfca7cbb0a9a..235e90dd96df 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2933,62 +2933,21 @@ public class WallpaperManager {
}
}
- if (!isComponentExist(context, cn)) {
- cn = null;
- }
-
- return cn;
- }
-
- /**
- * Return {@link ComponentName} of the CMF default wallpaper, or
- * {@link #getDefaultWallpaperComponent(Context)} if none is defined.
- *
- * @hide
- */
- public static ComponentName getCmfDefaultWallpaperComponent(Context context) {
- ComponentName cn = null;
- String[] cmfWallpaperMap = context.getResources().getStringArray(
- com.android.internal.R.array.cmf_default_wallpaper_component);
- if (cmfWallpaperMap == null || cmfWallpaperMap.length == 0) {
- Log.d(TAG, "No CMF wallpaper config");
- return getDefaultWallpaperComponent(context);
- }
-
- for (String entry : cmfWallpaperMap) {
- String[] cmfWallpaper;
- if (!TextUtils.isEmpty(entry)) {
- cmfWallpaper = entry.split(",");
- if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals(
- cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) {
- cn = ComponentName.unflattenFromString(cmfWallpaper[1]);
- break;
- }
+ // Check if the package exists
+ if (cn != null) {
+ try {
+ final PackageManager packageManager = context.getPackageManager();
+ packageManager.getPackageInfo(cn.getPackageName(),
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ } catch (PackageManager.NameNotFoundException e) {
+ cn = null;
}
}
- if (!isComponentExist(context, cn)) {
- cn = null;
- }
-
return cn;
}
- private static boolean isComponentExist(Context context, ComponentName cn) {
- if (cn == null) {
- return false;
- }
- try {
- final PackageManager packageManager = context.getPackageManager();
- packageManager.getPackageInfo(cn.getPackageName(),
- PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- return true;
- }
-
/**
* Register a callback for lock wallpaper observation. Only the OS may use this.
*
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index b2a923043dbe..da5e40aedbd2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -11369,7 +11369,8 @@ public class DevicePolicyManager {
* @throws SecurityException if the caller is not a profile owner on an organization-owned
* managed profile.
* @throws IllegalStateException if called after the device setup has been completed.
- * @throws UnsupportedOperationException if the api is not enabled.
+ * @throws UnsupportedOperationException if managed subscriptions policy is not explicitly
+ * enabled by the device policy management role holder during device setup.
* @see ManagedSubscriptionsPolicy
*/
public void setManagedSubscriptionsPolicy(@Nullable ManagedSubscriptionsPolicy policy) {
diff --git a/core/java/android/service/notification/TEST_MAPPING b/core/java/android/service/notification/TEST_MAPPING
new file mode 100644
index 000000000000..59b2bc1e4f73
--- /dev/null
+++ b/core/java/android/service/notification/TEST_MAPPING
@@ -0,0 +1,49 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsNotificationTestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksUiServicesTests",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsNotificationTestCases"
+ }
+ ]
+}
diff --git a/core/java/android/view/ISurfaceControlViewHost.aidl b/core/java/android/view/ISurfaceControlViewHost.aidl
index 15008ae18618..fd4b329570d9 100644
--- a/core/java/android/view/ISurfaceControlViewHost.aidl
+++ b/core/java/android/view/ISurfaceControlViewHost.aidl
@@ -19,6 +19,7 @@ package android.view;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.view.InsetsState;
+import android.view.ISurfaceControlViewHostParent;
import android.window.ISurfaceSyncGroup;
/**
@@ -34,4 +35,9 @@ interface ISurfaceControlViewHost {
oneway void onDispatchDetachedFromWindow();
oneway void onInsetsChanged(in InsetsState state, in Rect insetFrame);
ISurfaceSyncGroup getSurfaceSyncGroup();
+ /**
+ * Attaches the parent interface so the embedded content can communicate back to the parent.
+ * If null is passed in, it will remove the parent interface and no more updates will be sent.
+ */
+ oneway void attachParentInterface(in @nullable ISurfaceControlViewHostParent parentInterface);
}
diff --git a/core/java/android/view/ISurfaceControlViewHostParent.aidl b/core/java/android/view/ISurfaceControlViewHostParent.aidl
new file mode 100644
index 000000000000..f42e00148587
--- /dev/null
+++ b/core/java/android/view/ISurfaceControlViewHostParent.aidl
@@ -0,0 +1,27 @@
+/*
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.view;
+
+import android.view.WindowManager;
+
+/**
+ * API from embedded content in SurfaceControlViewHost to parent containing the embedded.
+ * {@hide}
+ */
+oneway interface ISurfaceControlViewHostParent {
+ void updateParams(in WindowManager.LayoutParams[] childAttrs);
+}
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index c8cf7d9a5194..effc127dabd2 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -54,7 +54,7 @@ public class SurfaceControlViewHost {
private final static String TAG = "SurfaceControlViewHost";
private final ViewRootImpl mViewRoot;
private final CloseGuard mCloseGuard = CloseGuard.get();
- private WindowlessWindowManager mWm;
+ private final WindowlessWindowManager mWm;
private SurfaceControl mSurfaceControl;
private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
@@ -67,9 +67,7 @@ public class SurfaceControlViewHost {
return;
}
mViewRoot.mHandler.post(() -> {
- if (mWm != null) {
- mWm.setConfiguration(configuration);
- }
+ mWm.setConfiguration(configuration);
if (mViewRoot != null) {
mViewRoot.forceWmRelayout();
}
@@ -116,6 +114,11 @@ public class SurfaceControlViewHost {
}
return null;
}
+
+ @Override
+ public void attachParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
+ mViewRoot.mHandler.post(() -> mWm.setParentInterface(parentInterface));
+ }
}
private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl();
@@ -148,10 +151,11 @@ public class SurfaceControlViewHost {
private SurfaceControl mSurfaceControl;
private final IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
private final IBinder mInputToken;
+ @NonNull
private final ISurfaceControlViewHost mRemoteInterface;
SurfacePackage(SurfaceControl sc, IAccessibilityEmbeddedConnection connection,
- IBinder inputToken, ISurfaceControlViewHost ri) {
+ IBinder inputToken, @NonNull ISurfaceControlViewHost ri) {
mSurfaceControl = sc;
mAccessibilityEmbeddedConnection = connection;
mInputToken = inputToken;
@@ -213,6 +217,7 @@ public class SurfaceControlViewHost {
/**
* @hide
*/
+ @NonNull
public ISurfaceControlViewHost getRemoteInterface() {
return mRemoteInterface;
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 0e4cf89e7772..1e268bed3b17 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_OVERLAY_SUBLAYER;
import static android.view.WindowManagerPolicyConstants.APPLICATION_MEDIA_SUBLAYER;
import static android.view.WindowManagerPolicyConstants.APPLICATION_PANEL_SUBLAYER;
@@ -40,6 +41,7 @@ import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.util.ArraySet;
import android.util.AttributeSet;
@@ -54,6 +56,8 @@ import com.android.internal.view.SurfaceCallbackHelper;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
@@ -302,6 +306,26 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
private SurfaceControl mBlastSurfaceControl;
private BLASTBufferQueue mBlastBufferQueue;
+ private final ConcurrentLinkedQueue<WindowManager.LayoutParams> mEmbeddedWindowParams =
+ new ConcurrentLinkedQueue<>();
+
+ private final ISurfaceControlViewHostParent mSurfaceControlViewHostParent =
+ new ISurfaceControlViewHostParent.Stub() {
+ @Override
+ public void updateParams(WindowManager.LayoutParams[] childAttrs) {
+ mEmbeddedWindowParams.clear();
+ mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs));
+
+ if (isAttachedToWindow()) {
+ runOnUiThread(() -> {
+ if (mParent != null) {
+ mParent.recomputeViewAttributes(SurfaceView.this);
+ }
+ });
+ }
+ }
+ };
+
public SurfaceView(Context context) {
this(context, null);
}
@@ -801,9 +825,18 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mBlastSurfaceControl = null;
}
- if (releaseSurfacePackage && mSurfacePackage != null) {
- mSurfacePackage.release();
- mSurfacePackage = null;
+ if (mSurfacePackage != null) {
+ try {
+ mSurfacePackage.getRemoteInterface().attachParentInterface(null);
+ mEmbeddedWindowParams.clear();
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is "
+ + "already dead");
+ }
+ if (releaseSurfacePackage) {
+ mSurfacePackage.release();
+ mSurfacePackage = null;
+ }
}
applyTransactionOnVriDraw(transaction);
@@ -1854,6 +1887,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
applyTransactionOnVriDraw(transaction);
}
mSurfacePackage = p;
+ try {
+ mSurfacePackage.getRemoteInterface().attachParentInterface(
+ mSurfaceControlViewHostParent);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is already dead.");
+ }
if (isFocused()) {
requestEmbeddedFocus(true);
@@ -2014,4 +2053,19 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mBlastBufferQueue.mergeWithNextTransaction(transaction, frameNumber);
}
}
+
+ @Override
+ void performCollectViewAttributes(AttachInfo attachInfo, int visibility) {
+ super.performCollectViewAttributes(attachInfo, visibility);
+ if (mEmbeddedWindowParams.isEmpty()) {
+ return;
+ }
+
+ for (WindowManager.LayoutParams embeddedWindowAttr : mEmbeddedWindowParams) {
+ if ((embeddedWindowAttr.flags & FLAG_KEEP_SCREEN_ON) == FLAG_KEEP_SCREEN_ON) {
+ attachInfo.mKeepScreenOn = true;
+ break;
+ }
+ }
+ }
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7108805d9c71..c396af6323d2 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5460,7 +5460,7 @@ public final class ViewRootImpl implements ViewParent,
}
private void updateRenderHdrSdrRatio() {
- mRenderHdrSdrRatio = mDisplay.getHdrSdrRatio();
+ mRenderHdrSdrRatio = Math.min(mDesiredHdrSdrRatio, mDisplay.getHdrSdrRatio());
mUpdateHdrSdrRatioInfo = true;
}
@@ -5488,22 +5488,14 @@ public final class ViewRootImpl implements ViewParent,
mHdrSdrRatioChangedListener = null;
} else {
mHdrSdrRatioChangedListener = display -> {
- setTargetHdrSdrRatio(display.getHdrSdrRatio());
+ updateRenderHdrSdrRatio();
+ invalidate();
};
mDisplay.registerHdrSdrRatioChangedListener(mExecutor, mHdrSdrRatioChangedListener);
}
}
}
- /** happylint */
- public void setTargetHdrSdrRatio(float ratio) {
- if (mRenderHdrSdrRatio != ratio) {
- mRenderHdrSdrRatio = ratio;
- mUpdateHdrSdrRatioInfo = true;
- invalidate();
- }
- }
-
@Override
public void requestChildFocus(View child, View focused) {
if (DEBUG_INPUT_RESIZE) {
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 96bfb2d9e1e6..7d3d283a45f2 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -49,7 +49,8 @@ public class WindowlessWindowManager implements IWindowSession {
private class State {
SurfaceControl mSurfaceControl;
- WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+ final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
+ final WindowManager.LayoutParams mLastReportedParams = new WindowManager.LayoutParams();
int mDisplayId;
IBinder mInputChannelToken;
Region mInputRegion;
@@ -94,6 +95,8 @@ public class WindowlessWindowManager implements IWindowSession {
private final MergedConfiguration mTmpConfig = new MergedConfiguration();
private final WindowlessWindowLayout mLayout = new WindowlessWindowLayout();
+ private ISurfaceControlViewHostParent mParentInterface;
+
public WindowlessWindowManager(Configuration c, SurfaceControl rootSurface,
IBinder hostInputToken) {
mRootSurface = rootSurface;
@@ -244,6 +247,7 @@ public class WindowlessWindowManager implements IWindowSession {
final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
WindowManagerGlobal.ADD_FLAG_USE_BLAST;
+ sendLayoutParamsToParent();
// Include whether the window is in touch mode.
return isInTouchModeInternal(displayId) ? res | WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE
: res;
@@ -425,6 +429,7 @@ public class WindowlessWindowManager implements IWindowSession {
outInsetsState.set(mInsetsState);
}
+ sendLayoutParamsToParent();
return 0;
}
@@ -645,4 +650,45 @@ public class WindowlessWindowManager implements IWindowSession {
" we shouldn't get here!");
return false;
}
+
+ void setParentInterface(@Nullable ISurfaceControlViewHostParent parentInterface) {
+ IBinder oldInterface = mParentInterface == null ? null : mParentInterface.asBinder();
+ IBinder newInterface = parentInterface == null ? null : parentInterface.asBinder();
+ // If the parent interface has changed, it needs to clear the last reported params so it
+ // will update the new interface with the params.
+ if (oldInterface != newInterface) {
+ clearLastReportedParams();
+ }
+ mParentInterface = parentInterface;
+ sendLayoutParamsToParent();
+ }
+
+ private void clearLastReportedParams() {
+ WindowManager.LayoutParams emptyParam = new WindowManager.LayoutParams();
+ for (State windowInfo : mStateForWindow.values()) {
+ windowInfo.mLastReportedParams.copyFrom(emptyParam);
+ }
+ }
+
+ private void sendLayoutParamsToParent() {
+ if (mParentInterface == null) {
+ return;
+ }
+ WindowManager.LayoutParams[] params =
+ new WindowManager.LayoutParams[mStateForWindow.size()];
+ int index = 0;
+ boolean hasChanges = false;
+ for (State windowInfo : mStateForWindow.values()) {
+ int changes = windowInfo.mLastReportedParams.copyFrom(windowInfo.mParams);
+ hasChanges |= (changes != 0);
+ params[index++] = windowInfo.mParams;
+ }
+
+ if (hasChanges) {
+ try {
+ mParentInterface.updateParams(params);
+ } catch (RemoteException e) {
+ }
+ }
+ }
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index efd50e7d2343..2241fd5dc37a 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -52,8 +52,10 @@ import android.util.Log;
import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.contentcapture.ViewNode.ViewStructureImpl;
+import android.view.contentprotection.ContentProtectionEventProcessor;
import android.view.inputmethod.BaseInputConnection;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.IResultReceiver;
import java.io.PrintWriter;
@@ -118,9 +120,13 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
/**
* Direct interface to the service binder object - it's used to send the events, including the
* last ones (when the session is finished)
+ *
+ * @hide
*/
- @NonNull
- private IContentCaptureDirectManager mDirectServiceInterface;
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public IContentCaptureDirectManager mDirectServiceInterface;
+
@Nullable
private DeathRecipient mDirectServiceVulture;
@@ -131,14 +137,19 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
@Nullable
private IBinder mShareableActivityToken;
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@Nullable
- private ComponentName mComponentName;
+ public ComponentName mComponentName;
/**
* List of events held to be sent as a batch.
+ *
+ * @hide
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@Nullable
- private ArrayList<ContentCaptureEvent> mEvents;
+ public ArrayList<ContentCaptureEvent> mEvents;
// Used just for debugging purposes (on dump)
private long mNextFlush;
@@ -157,6 +168,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
@NonNull
private final SessionStateReceiver mSessionStateReceiver;
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public ContentProtectionEventProcessor mContentProtectionEventProcessor;
+
private static class SessionStateReceiver extends IResultReceiver.Stub {
private final WeakReference<MainContentCaptureSession> mMainSession;
@@ -194,8 +210,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
}
- protected MainContentCaptureSession(@NonNull ContentCaptureManager.StrippedContext context,
- @NonNull ContentCaptureManager manager, @NonNull Handler handler,
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+ public MainContentCaptureSession(
+ @NonNull ContentCaptureManager.StrippedContext context,
+ @NonNull ContentCaptureManager manager,
+ @NonNull Handler handler,
@NonNull IContentCaptureManager systemServerInterface) {
mContext = context;
mManager = manager;
@@ -273,15 +293,16 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
/**
- * Callback from {@code system_server} after call to
- * {@link IContentCaptureManager#startSession(IBinder, ComponentName, String, int,
- * IResultReceiver)}.
+ * Callback from {@code system_server} after call to {@link
+ * IContentCaptureManager#startSession(IBinder, ComponentName, String, int, IResultReceiver)}.
*
* @param resultCode session state
* @param binder handle to {@code IContentCaptureDirectManager}
+ * @hide
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@UiThread
- private void onSessionStarted(int resultCode, @Nullable IBinder binder) {
+ public void onSessionStarted(int resultCode, @Nullable IBinder binder) {
if (binder != null) {
mDirectServiceInterface = IContentCaptureDirectManager.Stub.asInterface(binder);
mDirectServiceVulture = () -> {
@@ -296,6 +317,20 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
}
+ // Should not be possible for mComponentName to be null here but check anyway
+ if (mManager.mOptions.contentProtectionOptions.enableReceiver
+ && mManager.getContentProtectionEventBuffer() != null
+ && mComponentName != null) {
+ mContentProtectionEventProcessor =
+ new ContentProtectionEventProcessor(
+ mManager.getContentProtectionEventBuffer(),
+ mHandler,
+ mSystemServerInterface,
+ mComponentName.getPackageName());
+ } else {
+ mContentProtectionEventProcessor = null;
+ }
+
if ((resultCode & STATE_DISABLED) != 0) {
resetSession(resultCode);
} else {
@@ -311,8 +346,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
}
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@UiThread
- private void sendEvent(@NonNull ContentCaptureEvent event) {
+ public void sendEvent(@NonNull ContentCaptureEvent event) {
sendEvent(event, /* forceFlush= */ false);
}
@@ -337,6 +374,25 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
if (sVerbose) Log.v(TAG, "handleSendEvent(): ignoring when disabled");
return;
}
+
+ if (isContentProtectionReceiverEnabled()) {
+ sendContentProtectionEvent(event);
+ }
+ if (isContentCaptureReceiverEnabled()) {
+ sendContentCaptureEvent(event, forceFlush);
+ }
+ }
+
+ @UiThread
+ private void sendContentProtectionEvent(@NonNull ContentCaptureEvent event) {
+ if (mContentProtectionEventProcessor != null) {
+ mContentProtectionEventProcessor.processEvent(event);
+ }
+ }
+
+ @UiThread
+ private void sendContentCaptureEvent(@NonNull ContentCaptureEvent event, boolean forceFlush) {
+ final int eventType = event.getType();
final int maxBufferSize = mManager.mOptions.maxBufferSize;
if (mEvents == null) {
if (sVerbose) {
@@ -528,9 +584,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
flush(reason);
}
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@Override
@UiThread
- void flush(@FlushReason int reason) {
+ public void flush(@FlushReason int reason) {
if (mEvents == null || mEvents.size() == 0) {
if (sVerbose) {
Log.v(TAG, "Don't flush for empty event buffer.");
@@ -544,6 +602,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
return;
}
+ if (!isContentCaptureReceiverEnabled()) {
+ return;
+ }
+
if (mDirectServiceInterface == null) {
if (sVerbose) {
Log.v(TAG, "handleForceFlush(" + getDebugState(reason) + "): hold your horses, "
@@ -607,8 +669,10 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
return new ParceledListSlice<>(events);
}
+ /** hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@UiThread
- private void destroySession() {
+ public void destroySession() {
if (sDebug) {
Log.d(TAG, "Destroying session (ctx=" + mContext + ", id=" + mId + ") with "
+ (mEvents == null ? 0 : mEvents.size()) + " event(s) for "
@@ -626,12 +690,15 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
mDirectServiceInterface.asBinder().unlinkToDeath(mDirectServiceVulture, 0);
}
mDirectServiceInterface = null;
+ mContentProtectionEventProcessor = null;
}
// TODO(b/122454205): once we support multiple sessions, we might need to move some of these
// clearings out.
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@UiThread
- private void resetSession(int newState) {
+ public void resetSession(int newState) {
if (sVerbose) {
Log.v(TAG, "handleResetSession(" + getActivityName() + "): from "
+ getStateAsString(mState) + " to " + getStateAsString(newState));
@@ -651,6 +718,7 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
}
}
mDirectServiceInterface = null;
+ mContentProtectionEventProcessor = null;
mHandler.removeMessages(MSG_FLUSH);
}
@@ -878,4 +946,14 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
private String getDebugState(@FlushReason int reason) {
return getDebugState() + ", reason=" + getFlushReasonAsString(reason);
}
+
+ @UiThread
+ private boolean isContentProtectionReceiverEnabled() {
+ return mManager.mOptions.contentProtectionOptions.enableReceiver;
+ }
+
+ @UiThread
+ private boolean isContentCaptureReceiverEnabled() {
+ return mManager.mOptions.enableReceiver;
+ }
}
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
index e49df21692c5..b44abf3eb04d 100644
--- a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -75,6 +75,8 @@ public class ContentProtectionEventProcessor {
ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED)));
+ private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
+
@NonNull private final RingBuffer<ContentCaptureEvent> mEventBuffer;
@NonNull private final Handler mHandler;
@@ -93,6 +95,8 @@ public class ContentProtectionEventProcessor {
@Nullable
public Instant mLastFlushTime;
+ private int mResetLoginRemainingEventsToProcess;
+
public ContentProtectionEventProcessor(
@NonNull RingBuffer<ContentCaptureEvent> eventBuffer,
@NonNull Handler handler,
@@ -130,6 +134,8 @@ public class ContentProtectionEventProcessor {
mSuspiciousTextDetected |= isSuspiciousText(event);
if (mPasswordFieldDetected && mSuspiciousTextDetected) {
loginDetected();
+ } else {
+ maybeResetLoginFlags();
}
}
@@ -139,8 +145,28 @@ public class ContentProtectionEventProcessor {
|| Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) {
flush();
}
+ resetLoginFlags();
+ }
+
+ @UiThread
+ private void resetLoginFlags() {
mPasswordFieldDetected = false;
mSuspiciousTextDetected = false;
+ mResetLoginRemainingEventsToProcess = 0;
+ }
+
+ @UiThread
+ private void maybeResetLoginFlags() {
+ if (mPasswordFieldDetected || mSuspiciousTextDetected) {
+ if (mResetLoginRemainingEventsToProcess <= 0) {
+ mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS;
+ } else {
+ mResetLoginRemainingEventsToProcess--;
+ if (mResetLoginRemainingEventsToProcess <= 0) {
+ resetLoginFlags();
+ }
+ }
+ }
}
@UiThread
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 59238b40e0c8..d2a16a3a9212 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -550,6 +550,16 @@ public final class TransitionInfo implements Parcelable {
}
/**
+ * Updates the callsites of all the surfaces in this transition, which aids in the debugging of
+ * lingering surfaces.
+ */
+ public void setUnreleasedWarningCallSiteForAllSurfaces(String callsite) {
+ for (int i = mChanges.size() - 1; i >= 0; --i) {
+ mChanges.get(i).getLeash().setUnreleasedWarningCallSite(callsite);
+ }
+ }
+
+ /**
* Makes a copy of this as if it were parcel'd and unparcel'd. This implies that surfacecontrol
* refcounts are incremented which allows the "remote" receiver to release them without breaking
* the caller's references. Use this only if you need to "send" this to a local function which
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 828e7aa76566..7fd58f4aacbf 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1857,16 +1857,6 @@
specified -->
<string name="default_wallpaper_component" translatable="false">@null</string>
- <!-- CMF colors to default wallpaper component map, the component with color matching the device
- color will be the cmf default wallpapers. The default wallpaper will be default wallpaper
- component if not specified.
-
- E.g. for SLV color, and com.android.example/com.android.example.SlVDefaultWallpaper
- <item>SLV,com.android.example/com.android.example.SlVDefaultWallpaper</item> -->
- <string-array name="cmf_default_wallpaper_component" translatable="false">
- <!-- Add packages here -->
- </string-array>
-
<!-- By default a product has no distinct default lock wallpaper -->
<item name="default_lock_wallpaper" type="drawable">@null</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1c5755d7634f..bfc7f559a381 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2119,7 +2119,6 @@
<java-symbol type="string" name="data_usage_rapid_body" />
<java-symbol type="string" name="data_usage_rapid_app_body" />
<java-symbol type="string" name="default_wallpaper_component" />
- <java-symbol type="array" name="cmf_default_wallpaper_component" />
<java-symbol type="string" name="device_storage_monitor_notification_channel" />
<java-symbol type="string" name="dlg_ok" />
<java-symbol type="string" name="dump_heap_notification" />
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
new file mode 100644
index 000000000000..3373b8b13273
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentcapture;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.contentprotection.ContentProtectionEventProcessor;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test for {@link MainContentCaptureSession}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentcapture.MainContentCaptureSessionTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MainContentCaptureSessionTest {
+
+ private static final int BUFFER_SIZE = 100;
+
+ private static final int REASON = 123;
+
+ private static final ContentCaptureEvent EVENT =
+ new ContentCaptureEvent(/* sessionId= */ 0, TYPE_SESSION_STARTED);
+
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName("com.test.package", "TestClass");
+
+ private static final Context sContext = ApplicationProvider.getApplicationContext();
+
+ private static final ContentCaptureManager.StrippedContext sStrippedContext =
+ new ContentCaptureManager.StrippedContext(sContext);
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock private IContentCaptureManager mMockSystemServerInterface;
+
+ @Mock private ContentProtectionEventProcessor mMockContentProtectionEventProcessor;
+
+ @Mock private IContentCaptureDirectManager mMockContentCaptureDirectManager;
+
+ @Test
+ public void onSessionStarted_contentProtectionEnabled_processorCreated() {
+ MainContentCaptureSession session = createSession();
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+ assertThat(session.mContentProtectionEventProcessor).isNotNull();
+ }
+
+ @Test
+ public void onSessionStarted_contentProtectionDisabled_processorNotCreated() {
+ MainContentCaptureSession session =
+ createSession(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ false);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ }
+
+ @Test
+ public void onSessionStarted_contentProtectionNoBuffer_processorNotCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true, -BUFFER_SIZE));
+ MainContentCaptureSession session = createSession(options);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ }
+
+ @Test
+ public void onSessionStarted_noComponentName_processorNotCreated() {
+ MainContentCaptureSession session = createSession();
+ session.mComponentName = null;
+
+ session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ }
+
+ @Test
+ public void sendEvent_contentCaptureDisabled_contentProtectionDisabled() {
+ MainContentCaptureSession session =
+ createSession(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ false);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.sendEvent(EVENT);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isNull();
+ }
+
+ @Test
+ public void sendEvent_contentCaptureDisabled_contentProtectionEnabled() {
+ MainContentCaptureSession session =
+ createSession(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ true);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.sendEvent(EVENT);
+
+ verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
+ assertThat(session.mEvents).isNull();
+ }
+
+ @Test
+ public void sendEvent_contentCaptureEnabled_contentProtectionDisabled() {
+ MainContentCaptureSession session =
+ createSession(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ false);
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.sendEvent(EVENT);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isNotNull();
+ assertThat(session.mEvents).containsExactly(EVENT);
+ }
+
+ @Test
+ public void sendEvent_contentCaptureEnabled_contentProtectionEnabled() {
+ MainContentCaptureSession session = createSession();
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.sendEvent(EVENT);
+
+ verify(mMockContentProtectionEventProcessor).processEvent(EVENT);
+ assertThat(session.mEvents).isNotNull();
+ assertThat(session.mEvents).containsExactly(EVENT);
+ }
+
+ @Test
+ public void sendEvent_contentProtectionEnabled_processorNotCreated() {
+ MainContentCaptureSession session =
+ createSession(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ true);
+
+ session.sendEvent(EVENT);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isNull();
+ }
+
+ @Test
+ public void flush_contentCaptureDisabled_contentProtectionDisabled() throws Exception {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ false);
+ MainContentCaptureSession session = createSession(options);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.flush(REASON);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyZeroInteractions(mMockContentCaptureDirectManager);
+ assertThat(session.mEvents).containsExactly(EVENT);
+ }
+
+ @Test
+ public void flush_contentCaptureDisabled_contentProtectionEnabled() {
+ MainContentCaptureSession session =
+ createSession(
+ /* enableContentCaptureReceiver= */ false,
+ /* enableContentProtectionReceiver= */ true);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.flush(REASON);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ verifyZeroInteractions(mMockContentCaptureDirectManager);
+ assertThat(session.mEvents).containsExactly(EVENT);
+ }
+
+ @Test
+ public void flush_contentCaptureEnabled_contentProtectionDisabled() throws Exception {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ false);
+ MainContentCaptureSession session = createSession(options);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.flush(REASON);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isEmpty();
+ assertEventFlushedContentCapture(options);
+ }
+
+ @Test
+ public void flush_contentCaptureEnabled_contentProtectionEnabled() throws Exception {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSession session = createSession(options);
+ session.mEvents = new ArrayList<>(Arrays.asList(EVENT));
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.flush(REASON);
+
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mEvents).isEmpty();
+ assertEventFlushedContentCapture(options);
+ }
+
+ @Test
+ public void destroySession() throws Exception {
+ MainContentCaptureSession session = createSession();
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.destroySession();
+
+ verify(mMockSystemServerInterface).finishSession(anyInt());
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mDirectServiceInterface).isNull();
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ }
+
+ @Test
+ public void resetSession() {
+ MainContentCaptureSession session = createSession();
+ session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+ session.resetSession(/* newState= */ 0);
+
+ verifyZeroInteractions(mMockSystemServerInterface);
+ verifyZeroInteractions(mMockContentProtectionEventProcessor);
+ assertThat(session.mDirectServiceInterface).isNull();
+ assertThat(session.mContentProtectionEventProcessor).isNull();
+ }
+
+ private static ContentCaptureOptions createOptions(
+ boolean enableContentCaptureReceiver,
+ ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
+ return new ContentCaptureOptions(
+ /* loggingLevel= */ 0,
+ BUFFER_SIZE,
+ /* idleFlushingFrequencyMs= */ 0,
+ /* textChangeFlushingFrequencyMs= */ 0,
+ /* logHistorySize= */ 0,
+ /* disableFlushForViewTreeAppearing= */ false,
+ enableContentCaptureReceiver,
+ contentProtectionOptions,
+ /* whitelistedComponents= */ null);
+ }
+
+ private static ContentCaptureOptions createOptions(
+ boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) {
+ return createOptions(
+ enableContentCaptureReceiver,
+ new ContentCaptureOptions.ContentProtectionOptions(
+ enableContentProtectionReceiver, BUFFER_SIZE));
+ }
+
+ private ContentCaptureManager createManager(ContentCaptureOptions options) {
+ return new ContentCaptureManager(sContext, mMockSystemServerInterface, options);
+ }
+
+ private MainContentCaptureSession createSession(ContentCaptureManager manager) {
+ MainContentCaptureSession session =
+ new MainContentCaptureSession(
+ sStrippedContext,
+ manager,
+ new Handler(Looper.getMainLooper()),
+ mMockSystemServerInterface);
+ session.mComponentName = COMPONENT_NAME;
+ return session;
+ }
+
+ private MainContentCaptureSession createSession(ContentCaptureOptions options) {
+ return createSession(createManager(options));
+ }
+
+ private MainContentCaptureSession createSession(
+ boolean enableContentCaptureReceiver, boolean enableContentProtectionReceiver) {
+ return createSession(
+ createOptions(enableContentCaptureReceiver, enableContentProtectionReceiver));
+ }
+
+ private MainContentCaptureSession createSession() {
+ return createSession(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ }
+
+ private void assertEventFlushedContentCapture(ContentCaptureOptions options) throws Exception {
+ ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
+ verify(mMockContentCaptureDirectManager)
+ .sendEvents(captor.capture(), eq(REASON), eq(options));
+
+ assertThat(captor.getValue()).isNotNull();
+ List<ContentCaptureEvent> actual = captor.getValue().getList();
+ assertThat(actual).isNotNull();
+ assertThat(actual).containsExactly(EVENT);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
index 4adadf11903e..39a2e0e048e8 100644
--- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -91,6 +91,8 @@ public class ContentProtectionEventProcessorTest {
private static final Set<Integer> EVENT_TYPES_TO_STORE =
ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED);
+ private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
+
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock private RingBuffer<ContentCaptureEvent> mMockEventBuffer;
@@ -232,6 +234,56 @@ public class ContentProtectionEventProcessorTest {
}
@Test
+ public void processEvent_loginDetected_belowResetLimit() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+ for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) {
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+ }
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer).clear();
+ verify(mMockEventBuffer).toArray();
+ assertOnLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_aboveResetLimit() throws Exception {
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+ for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) {
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+ }
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ }
+
+ @Test
public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception {
when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 2307d6080f9f..b9d3756ac6d2 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -997,12 +997,63 @@ public final class Bitmap implements Parcelable {
canvas.concat(m);
canvas.drawBitmap(source, srcR, dstR, paint);
canvas.setBitmap(null);
+
+ // If the source has a gainmap, apply the same set of transformations to the gainmap
+ // and set it on the output
+ if (source.hasGainmap()) {
+ Bitmap newMapContents = transformGainmap(source, m, neww, newh, paint, srcR, dstR,
+ deviceR);
+ if (newMapContents != null) {
+ bitmap.setGainmap(new Gainmap(source.getGainmap(), newMapContents));
+ }
+ }
+
if (isHardware) {
return bitmap.copy(Config.HARDWARE, false);
}
return bitmap;
}
+ private static Bitmap transformGainmap(Bitmap source, Matrix m, int neww, int newh, Paint paint,
+ Rect srcR, RectF dstR, RectF deviceR) {
+ Canvas canvas;
+ Bitmap sourceGainmap = source.getGainmap().getGainmapContents();
+ // Gainmaps can be scaled relative to the base image (eg, 1/4th res)
+ // Preserve that relative scaling between the base & gainmap in the output
+ float scaleX = (sourceGainmap.getWidth() / (float) source.getWidth());
+ float scaleY = (sourceGainmap.getHeight() / (float) source.getHeight());
+ int mapw = Math.round(neww * scaleX);
+ int maph = Math.round(newh * scaleY);
+
+ if (mapw == 0 || maph == 0) {
+ // The gainmap has been scaled away entirely, drop it
+ return null;
+ }
+
+ // Scale the computed `srcR` used for rendering the source bitmap to the destination
+ // to be in gainmap dimensions
+ Rect gSrcR = new Rect((int) (srcR.left * scaleX),
+ (int) (srcR.top * scaleY), (int) (srcR.right * scaleX),
+ (int) (srcR.bottom * scaleY));
+
+ // Note: createBitmap isn't used as that requires a non-null colorspace, however
+ // gainmaps don't have a colorspace. So use `nativeCreate` directly to bypass
+ // that colorspace enforcement requirement (#getColorSpace() allows a null return)
+ Bitmap newMapContents = nativeCreate(null, 0, mapw, mapw, maph,
+ sourceGainmap.getConfig().nativeInt, true, 0);
+ newMapContents.eraseColor(0);
+ canvas = new Canvas(newMapContents);
+ // Scale the translate & matrix to be in gainmap-relative dimensions
+ canvas.scale(scaleX, scaleY);
+ canvas.translate(-deviceR.left, -deviceR.top);
+ canvas.concat(m);
+ canvas.drawBitmap(sourceGainmap, gSrcR, dstR, paint);
+ canvas.setBitmap(null);
+ // Create a new gainmap using a copy of the metadata information from the source but
+ // with the transformed bitmap created above
+ return newMapContents;
+ }
+
/**
* Returns a mutable bitmap with the specified width and height. Its
* initial density is as per {@link #getDensity}. The newly created
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index 9ac84a6159da..f639521ff250 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -122,6 +122,16 @@ public final class Gainmap implements Parcelable {
}
/**
+ * Creates a new gainmap using the provided gainmap as the metadata source and the provided
+ * bitmap as the replacement for the gainmapContents
+ * TODO: Make public, it's useful
+ * @hide
+ */
+ public Gainmap(@NonNull Gainmap gainmap, @NonNull Bitmap gainmapContents) {
+ this(gainmapContents, nCreateCopy(gainmap.mNativePtr));
+ }
+
+ /**
* @return Returns the image data of the gainmap represented as a Bitmap. This is represented
* as a Bitmap for broad API compatibility, however certain aspects of the Bitmap are ignored
* such as {@link Bitmap#getColorSpace()} or {@link Bitmap#getGainmap()} as they are not
@@ -325,6 +335,7 @@ public final class Gainmap implements Parcelable {
private static native long nGetFinalizer();
private static native long nCreateEmpty();
+ private static native long nCreateCopy(long source);
private static native void nSetBitmap(long ptr, Bitmap bitmap);
diff --git a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
index 5d7771366bec..ce242751c172 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_handle_dark.xml
@@ -13,13 +13,20 @@
~ 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">
- <group android:translateY="8.0">
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="128dp"
+ android:height="4dp"
+ android:viewportWidth="128"
+ android:viewportHeight="4"
+ >
+ <group>
+ <clip-path
+ android:pathData="M2 0H126C127.105 0 128 0.895431 128 2C128 3.10457 127.105 4 126 4H2C0.895431 4 0 3.10457 0 2C0 0.895431 0.895431 0 2 0Z"
+ />
<path
- android:fillColor="@android:color/black" android:pathData="M3,5V3H21V5Z"/>
+ android:pathData="M0 0V4H128V0"
+ android:fillColor="@android:color/black"
+ />
</group>
</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index 1d6864c152c2..0ca912e20527 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -28,6 +28,7 @@
android:layout_width="176dp"
android:layout_height="42dp"
android:paddingHorizontal="24dp"
+ android:paddingVertical="19dp"
android:contentDescription="@string/handle_text"
android:src="@drawable/decor_handle_dark"
tools:tint="@color/desktop_mode_caption_handle_bar_dark"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 7b37d5947f17..57d374b2b8f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -20,6 +20,8 @@ import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
import static android.window.TransitionInfo.FLAG_FILLS_TASK;
import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static com.android.wm.shell.transition.DefaultTransitionHandler.isSupportedOverrideAnimation;
+
import static java.util.Objects.requireNonNull;
import android.content.Context;
@@ -113,8 +115,11 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
return false;
}
final TransitionInfo.AnimationOptions options = info.getAnimationOptions();
- if (options != null && options.getType() == ANIM_SCENE_TRANSITION) {
- // Scene-transition will be handled by app side.
+ if (options != null
+ // Scene-transition will be handled by app side.
+ && (options.getType() == ANIM_SCENE_TRANSITION
+ // Use default transition handler to animate override animation.
+ || isSupportedOverrideAnimation(options))) {
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 9fcd207dc370..e1a3f3a1ac5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -55,6 +55,7 @@ import android.util.FloatProperty;
import android.util.IntProperty;
import android.util.Log;
import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -459,7 +460,9 @@ public class BubbleExpandedView extends LinearLayout {
if (mManageButton != null) {
int visibility = mManageButton.getVisibility();
removeView(mManageButton);
- mManageButton = (AlphaOptimizedButton) LayoutInflater.from(getContext()).inflate(
+ ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(),
+ com.android.internal.R.style.Theme_DeviceDefault_DayNight);
+ mManageButton = (AlphaOptimizedButton) LayoutInflater.from(ctw).inflate(
R.layout.bubble_manage_button, this /* parent */, false /* attach */);
addView(mManageButton);
mManageButton.setVisibility(visibility);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 3a46797d4a3d..80e29998e8d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -104,7 +104,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
@Override
protected BubbleViewInfo doInBackground(Void... voids) {
- if (mController.get().isShowingAsBubbleBar()) {
+ if (!verifyState()) {
+ // If we're in an inconsistent state, then switched modes and should just bail now.
+ return null;
+ }
+ if (mLayerView.get() != null) {
return BubbleViewInfo.populateForBubbleBar(mContext.get(), mController.get(),
mLayerView.get(), mIconFactory, mBubble, mSkipInflation);
} else {
@@ -118,7 +122,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
if (isCancelled() || viewInfo == null) {
return;
}
+
mMainExecutor.execute(() -> {
+ if (!verifyState()) {
+ return;
+ }
mBubble.setViewInfo(viewInfo);
if (mCallback != null) {
mCallback.onBubbleViewsReady(mBubble);
@@ -126,6 +134,14 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
});
}
+ private boolean verifyState() {
+ if (mController.get().isShowingAsBubbleBar()) {
+ return mLayerView.get() != null;
+ } else {
+ return mStackView.get() != null;
+ }
+ }
+
/**
* Info necessary to render a bubble.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index d6e1a82a68ff..467e9c7a116b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -375,16 +375,15 @@ public class DisplayLayout {
insetsState.getDisplayFrame(),
WindowInsets.Type.navigationBars(),
false /* ignoreVisibility */);
- outInsets.set(insets.left, insets.top, insets.right, insets.bottom);
int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
int navBarSize =
getNavigationBarSize(res, position, displayWidth > displayHeight, uiMode);
if (position == NAV_BAR_BOTTOM) {
- outInsets.bottom = Math.max(outInsets.bottom , navBarSize);
+ outInsets.bottom = Math.max(insets.bottom , navBarSize);
} else if (position == NAV_BAR_RIGHT) {
- outInsets.right = Math.max(outInsets.right , navBarSize);
+ outInsets.right = Math.max(insets.right , navBarSize);
} else if (position == NAV_BAR_LEFT) {
- outInsets.left = Math.max(outInsets.left , navBarSize);
+ outInsets.left = Math.max(insets.left , navBarSize);
}
}
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 ab8e7e63ef7f..f70d3aec9ec8 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
@@ -63,8 +63,10 @@ import com.android.internal.policy.DockedDividerUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.InteractionJankMonitorUtils;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -104,6 +106,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final Rect mWinBounds2 = new Rect();
private final SplitLayoutHandler mSplitLayoutHandler;
private final SplitWindowManager mSplitWindowManager;
+ private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
private final ImePositionProcessor mImePositionProcessor;
private final ResizingEffectPolicy mSurfaceEffectPolicy;
@@ -128,13 +131,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public SplitLayout(String windowName, Context context, Configuration configuration,
SplitLayoutHandler splitLayoutHandler,
SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks,
- DisplayImeController displayImeController, ShellTaskOrganizer taskOrganizer,
- int parallaxType) {
+ DisplayController displayController, DisplayImeController displayImeController,
+ ShellTaskOrganizer taskOrganizer, int parallaxType) {
mContext = context.createConfigurationContext(configuration);
mOrientation = configuration.orientation;
mRotation = configuration.windowConfiguration.getRotation();
mDensity = configuration.densityDpi;
mSplitLayoutHandler = splitLayoutHandler;
+ mDisplayController = displayController;
mDisplayImeController = displayImeController;
mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
parentContainerCallbacks);
@@ -145,7 +149,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
updateDividerConfig(mContext);
mRootBounds.set(configuration.windowConfiguration.getBounds());
- mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
resetDividerPosition();
mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
@@ -314,7 +318,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mRotation = rotation;
mDensity = density;
mUiMode = uiMode;
- mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
updateDividerConfig(mContext);
initDividerPosition(mTempRect);
updateInvisibleRect();
@@ -324,7 +328,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
/** Rotate the layout to specific rotation and calculate new bounds. The stable insets value
* should be calculated by display layout. */
- public void rotateTo(int newRotation, Rect stableInsets) {
+ public void rotateTo(int newRotation) {
final int rotationDelta = (newRotation - mRotation + 4) % 4;
final boolean changeOrient = (rotationDelta % 2) != 0;
@@ -337,7 +341,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// We only need new bounds here, other configuration should be update later.
mTempRect.set(mRootBounds);
mRootBounds.set(tmpRect);
- mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, stableInsets);
+ mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
initDividerPosition(mTempRect);
}
@@ -548,10 +552,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss);
}
- private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds,
- @Nullable Rect stableInsets) {
+ private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) {
final boolean isLandscape = isLandscape(rootBounds);
- final Rect insets = stableInsets != null ? stableInsets : getDisplayInsets(context);
+ final Rect insets = getDisplayStableInsets(context);
// Make split axis insets value same as the larger one to avoid bounds1 and bounds2
// have difference for avoiding size-compat mode when switching unresizable apps in
@@ -634,7 +637,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, Consumer<Rect> finishCallback) {
final boolean isLandscape = isLandscape();
- final Rect insets = getDisplayInsets(mContext);
+ final Rect insets = getDisplayStableInsets(mContext);
insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top,
isLandscape ? insets.right : 0, isLandscape ? 0 : insets.bottom);
@@ -705,13 +708,17 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return animator;
}
- private static Rect getDisplayInsets(Context context) {
- return context.getSystemService(WindowManager.class)
- .getMaximumWindowMetrics()
- .getWindowInsets()
- .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()
- | WindowInsets.Type.displayCutout())
- .toRect();
+ private Rect getDisplayStableInsets(Context context) {
+ final DisplayLayout displayLayout =
+ mDisplayController.getDisplayLayout(context.getDisplayId());
+ return displayLayout != null
+ ? displayLayout.stableInsets()
+ : context.getSystemService(WindowManager.class)
+ .getMaximumWindowMetrics()
+ .getWindowInsets()
+ .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()
+ | WindowInsets.Type.displayCutout())
+ .toRect();
}
private static boolean isLandscape(Rect bounds) {
@@ -784,7 +791,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private int getSmallestWidthDp(Rect bounds) {
mTempRect.set(bounds);
- mTempRect.inset(getDisplayInsets(mContext));
+ mTempRect.inset(getDisplayStableInsets(mContext));
final int minWidth = Math.min(mTempRect.width(), mTempRect.height());
final float density = mContext.getResources().getDisplayMetrics().density;
return (int) (minWidth / density);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 7cc2b9e64ec8..34a6e0ae406c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -103,13 +103,13 @@ import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+import java.util.Optional;
+
import dagger.BindsOptionalOf;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
-import java.util.Optional;
-
/**
* Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
* accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -556,13 +556,15 @@ public abstract class WMShellBaseModule {
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor,
- ShellCommandHandler shellCommandHandler) {
+ ShellCommandHandler shellCommandHandler,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
// TODO(b/238217847): Force override shell init if registration is disabled
shellInit = new ShellInit(mainExecutor);
}
return new Transitions(context, shellInit, shellController, organizer, pool,
- displayController, mainExecutor, mainHandler, animExecutor, shellCommandHandler);
+ displayController, mainExecutor, mainHandler, animExecutor, shellCommandHandler,
+ rootTaskDisplayAreaOrganizer);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index db7c3fc9c9ec..07d11cf9f63c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -438,6 +438,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
*/
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
mPipTransitionState.setInSwipePipToHomeTransition(true);
sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
@@ -450,6 +452,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
*/
public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
SurfaceControl overlay) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "stopSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
// do nothing if there is no startSwipePipToHome being called before
if (!mPipTransitionState.getInSwipePipToHomeTransition()) {
return;
@@ -522,23 +526,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return;
}
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "exitPip: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (isLaunchIntoPipTask()) {
exitLaunchIntoPipTask(wct);
return;
}
- if (ENABLE_SHELL_TRANSITIONS) {
- if (requestEnterSplit && mSplitScreenOptional.isPresent()) {
- mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo,
- isPipTopLeft()
- ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
- mPipTransitionController.startExitTransition(
- TRANSIT_EXIT_PIP_TO_SPLIT, wct, null /* destinationBounds */);
- return;
- }
- }
-
final Rect displayBounds = mPipBoundsState.getDisplayBounds();
final Rect destinationBounds = new Rect(displayBounds);
final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
@@ -547,10 +542,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// For exiting to fullscreen, the windowing mode of task will be changed to fullscreen
// until the animation is finished. Otherwise if the activity is resumed and focused at the
// begin of aniamtion, the app may do something too early to distub the animation.
- final boolean toFullscreen = destinationBounds.equals(displayBounds);
- if (Transitions.SHELL_TRANSITIONS_ROTATION || (Transitions.ENABLE_SHELL_TRANSITIONS
- && !toFullscreen)) {
+ if (Transitions.SHELL_TRANSITIONS_ROTATION) {
// When exit to fullscreen with Shell transition enabled, we update the Task windowing
// mode directly so that it can also trigger display rotation and visibility update in
// the same transition if there will be any.
@@ -559,6 +552,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// destinationBounds calculated above will be incorrect if this is with rotation.
wct.setBounds(mToken, null);
} else {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "exitPip: %s, dest=%s", mTaskInfo.topActivity, destinationBounds);
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds,
@@ -580,9 +575,29 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ if (requestEnterSplit && mSplitScreenOptional.isPresent()) {
+ wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+ mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo,
+ isPipToTopLeft()
+ ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ mPipTransitionController.startExitTransition(
+ TRANSIT_EXIT_PIP_TO_SPLIT, wct, destinationBounds);
+ return;
+ }
+
+ if (mSplitScreenOptional.isPresent()) {
+ // If pip activity will reparent to origin task case and if the origin task still
+ // under split root, apply exit split transaction to make it expand to fullscreen.
+ SplitScreenController split = mSplitScreenOptional.get();
+ if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
+ split.prepareExitSplitScreen(wct, split.getStageOfTask(
+ mTaskInfo.lastParentTaskIdBeforePip));
+ }
+ }
mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
return;
}
+
if (mSplitScreenOptional.isPresent()) {
// If pip activity will reparent to origin task case and if the origin task still under
// split root, just exit split screen here to ensure it could expand to fullscreen.
@@ -656,9 +671,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
animator.setInterpolator(Interpolators.ALPHA_OUT);
animator.start();
mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "removePip: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
}
private void removePipImmediately() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "removePipImmediately: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mToken, null);
@@ -723,6 +742,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
mPipUiEventLoggerLogger.log(uiEventEnum);
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onTaskAppeared: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
if (mPipTransitionState.getInSwipePipToHomeTransition()) {
if (!mWaitForFixedRotation) {
onEndOfSwipePipToHomeTransition();
@@ -774,6 +795,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
private void onTaskAppearedWithFixedRotation(int animationType) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onTaskAppearedWithFixedRotation: %s, state=%s animationType=%d",
+ mTaskInfo.topActivity, mPipTransitionState, animationType);
if (animationType == ANIM_TYPE_ALPHA) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Defer entering PiP alpha animation, fixed rotation is ongoing", TAG);
@@ -920,6 +944,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
*/
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo info) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onTaskVanished: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
return;
}
@@ -961,6 +987,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPipBoundsState.setOverrideMinSize(
mPipBoundsAlgorithm.getMinimalSize(info.topActivityInfo));
final PictureInPictureParams newParams = info.pictureInPictureParams;
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onTaskInfoChanged: %s, state=%s oldParams=%s newParams=%s",
+ mTaskInfo.topActivity, mPipTransitionState, mPictureInPictureParams, newParams);
// mPictureInPictureParams is only null if there is no PiP
if (newParams == null || mPictureInPictureParams == null) {
@@ -1001,6 +1030,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@Override
public void onFixedRotationStarted(int displayId, int newRotation) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onFixedRotationStarted: %s, state=%s", mTaskInfo, mPipTransitionState);
mNextRotation = newRotation;
mWaitForFixedRotation = true;
@@ -1022,6 +1053,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@Override
public void onFixedRotationFinished(int displayId) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onFixedRotationFinished: %s, state=%s", mTaskInfo, mPipTransitionState);
if (!mWaitForFixedRotation) {
return;
}
@@ -1057,6 +1090,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
/** Called when exiting PIP transition is finished to do the state cleanup. */
void onExitPipFinished(TaskInfo info) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onExitPipFinished: %s, state=%s leash=%s",
+ info.topActivity, mPipTransitionState, mLeash);
if (mLeash == null) {
// TODO(239461594): Remove once the double call to onExitPipFinished() is fixed
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -1108,6 +1144,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
/** Explicitly set the visibility of PiP window. */
public void setPipVisibility(boolean visible) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "setPipVisibility: %s, state=%s visible=%s",
+ mTaskInfo.topActivity, mPipTransitionState, visible);
if (!isInPip()) {
return;
}
@@ -1634,17 +1673,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
}
- private boolean isPipTopLeft() {
- if (!mSplitScreenOptional.isPresent()) {
- return false;
- }
- final Rect topLeft = new Rect();
- final Rect bottomRight = new Rect();
- mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
-
- return topLeft.contains(mPipBoundsState.getBounds());
- }
-
private boolean isPipToTopLeft() {
if (!mSplitScreenOptional.isPresent()) {
return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index f9d615ad0cf6..fb966c7de925 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -308,8 +308,9 @@ public class PipTransition extends PipTransitionController {
@Override
public void end() {
Animator animator = mPipAnimationController.getCurrentAnimator();
- if (animator == null) return;
- animator.end();
+ if (animator != null && animator.isRunning()) {
+ animator.end();
+ }
}
@Override
@@ -815,6 +816,12 @@ public class PipTransition extends PipTransitionController {
final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo();
final SurfaceControl leash = pipChange.getLeash();
final int startRotation = pipChange.getStartRotation();
+ // Check again in case some callers use startEnterAnimation directly so the flag was not
+ // set in startAnimation, e.g. from DefaultMixedHandler.
+ if (!mInFixedRotation) {
+ mEndFixedRotation = pipChange.getEndFixedRotation();
+ mInFixedRotation = mEndFixedRotation != ROTATION_UNDEFINED;
+ }
final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation();
setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
@@ -843,7 +850,7 @@ public class PipTransition extends PipTransitionController {
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
&& mPipTransitionState.getInSwipePipToHomeTransition()) {
handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash,
- sourceHintRect, destinationBounds, rotationDelta, taskInfo);
+ sourceHintRect, destinationBounds, taskInfo);
return;
}
@@ -934,8 +941,15 @@ public class PipTransition extends PipTransitionController {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull SurfaceControl leash, @Nullable Rect sourceHintRect,
- @NonNull Rect destinationBounds, int rotationDelta,
+ @NonNull Rect destinationBounds,
@NonNull ActivityManager.RunningTaskInfo pipTaskInfo) {
+ if (mInFixedRotation) {
+ // If rotation changes when returning to home, the transition should contain both the
+ // entering PiP and the display change (PipController#startSwipePipToHome has updated
+ // the display layout to new rotation). So it is not expected to see fixed rotation.
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
+ }
final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
if (swipePipToHomeOverlay != null) {
// Launcher fade in the overlay on top of the fullscreen Task. It is possible we
@@ -946,12 +960,7 @@ public class PipTransition extends PipTransitionController {
mPipOrganizer.mSwipePipToHomeOverlay = null;
}
- Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
- if (!Transitions.SHELL_TRANSITIONS_ROTATION && rotationDelta % 2 == 1) {
- // PipController#startSwipePipToHome has updated the display layout to new rotation,
- // so flip the source bounds to match the same orientation.
- sourceBounds = new Rect(0, 0, sourceBounds.height(), sourceBounds.width());
- }
+ final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
@@ -980,12 +989,7 @@ public class PipTransition extends PipTransitionController {
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback,
@NonNull TaskInfo taskInfo) {
- final int changeSize = info.getChanges().size();
- if (changeSize < 4) {
- throw new RuntimeException(
- "Got an exit-pip-to-split transition with unexpected change-list");
- }
- for (int i = changeSize - 1; i >= 0; i--) {
+ for (int i = info.getChanges().size() - 1; i >= 0; i--) {
final TransitionInfo.Change change = info.getChanges().get(i);
final int mode = change.getMode();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 2fff0e469f3d..e1bcd70c256b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -38,6 +38,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
@@ -223,6 +224,13 @@ public abstract class PipTransitionController implements Transitions.TransitionH
return false;
}
+ /** Whether a particular package is same as current pip package. */
+ public boolean isInPipPackage(String packageName) {
+ final TaskInfo inPipTask = mPipOrganizer.getTaskInfo();
+ return packageName != null && inPipTask != null
+ && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
+ }
+
/** Add PiP-related changes to `outWCT` for the given request. */
public void augmentRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
index db6138a0891f..b5f44835802f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
@@ -140,6 +140,24 @@ public class PipTransitionState {
return state == ENTERING_PIP;
}
+ private String stateToString() {
+ switch (mState) {
+ case UNDEFINED: return "undefined";
+ case TASK_APPEARED: return "task-appeared";
+ case ENTRY_SCHEDULED: return "entry-scheduled";
+ case ENTERING_PIP: return "entering-pip";
+ case ENTERED_PIP: return "entered-pip";
+ case EXITING_PIP: return "exiting-pip";
+ }
+ throw new IllegalStateException("Unknown state: " + mState);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
+ stateToString(), mInSwipePipToHomeTransition);
+ }
+
public interface OnPipTransitionStateChangedListener {
void onPipTransitionStateChanged(@TransitionState int oldState,
@TransitionState int newState);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index e7a1395f541c..5e1b6becfa45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -31,6 +31,8 @@ import android.os.RemoteException;
import android.util.Size;
import android.view.MotionEvent;
import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
import com.android.internal.protolog.common.ProtoLog;
@@ -131,6 +133,8 @@ public class PhonePipMenuController implements PipMenuController {
private PipMenuView mPipMenuView;
+ private SurfaceControl mLeash;
+
private ActionListener mMediaActionListener = new ActionListener() {
@Override
public void onMediaActionsChanged(List<RemoteAction> mediaActions) {
@@ -166,6 +170,7 @@ public class PhonePipMenuController implements PipMenuController {
*/
@Override
public void attach(SurfaceControl leash) {
+ mLeash = leash;
attachPipMenuView();
}
@@ -176,6 +181,7 @@ public class PhonePipMenuController implements PipMenuController {
public void detach() {
hideMenu();
detachPipMenuView();
+ mLeash = null;
}
void attachPipMenuView() {
@@ -185,6 +191,36 @@ public class PhonePipMenuController implements PipMenuController {
}
mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler,
mSplitScreenController, mPipUiEventLogger);
+ mPipMenuView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ v.getViewRootImpl().addSurfaceChangedCallback(
+ new ViewRootImpl.SurfaceChangedCallback() {
+ @Override
+ public void surfaceCreated(SurfaceControl.Transaction t) {
+ final SurfaceControl sc = getSurfaceControl();
+ if (sc != null) {
+ t.reparent(sc, mLeash);
+ // make menu on top of the surface
+ t.setLayer(sc, Integer.MAX_VALUE);
+ }
+ }
+
+ @Override
+ public void surfaceReplaced(SurfaceControl.Transaction t) {
+ }
+
+ @Override
+ public void surfaceDestroyed() {
+ }
+ });
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+
mSystemWindows.addView(mPipMenuView,
getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP);
@@ -321,30 +357,10 @@ public class PhonePipMenuController implements PipMenuController {
return;
}
- // If there is no pip leash supplied, that means the PiP leash is already finalized
- // resizing and the PiP menu is also resized. We then want to do a scale from the current
- // new menu bounds.
+ // TODO(b/286307861) transaction should be applied outside of PiP menu controller
if (pipLeash != null && t != null) {
- mPipMenuView.getBoundsOnScreen(mTmpSourceBounds);
- } else {
- mTmpSourceBounds.set(0, 0, destinationBounds.width(), destinationBounds.height());
+ t.apply();
}
-
- mTmpSourceRectF.set(mTmpSourceBounds);
- mTmpDestinationRectF.set(destinationBounds);
- mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
- final SurfaceControl surfaceControl = getSurfaceControl();
- if (surfaceControl == null) {
- return;
- }
- final SurfaceControl.Transaction menuTx =
- mSurfaceControlTransactionFactory.getTransaction();
- menuTx.setMatrix(surfaceControl, mMoveTransform, mTmpTransform);
- if (pipLeash != null && t != null) {
- // Merge the two transactions, vsyncId has been set on menuTx.
- menuTx.merge(t);
- }
- menuTx.apply();
}
/**
@@ -362,18 +378,10 @@ public class PhonePipMenuController implements PipMenuController {
return;
}
- final SurfaceControl surfaceControl = getSurfaceControl();
- if (surfaceControl == null) {
- return;
- }
- final SurfaceControl.Transaction menuTx =
- mSurfaceControlTransactionFactory.getTransaction();
- menuTx.setCrop(surfaceControl, destinationBounds);
+ // TODO(b/286307861) transaction should be applied outside of PiP menu controller
if (pipLeash != null && t != null) {
- // Merge the two transactions, vsyncId has been set on menuTx.
- menuTx.merge(t);
+ t.apply();
}
- menuTx.apply();
}
private boolean checkPipMenuState() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 6a861ce97431..65727b6145e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -574,6 +574,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void onActivityPinned(String packageName, int userId, int taskId,
int stackId) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onActivityPinned: %s", packageName);
mTouchHandler.onActivityPinned();
mMediaController.onActivityPinned();
mAppOpsListener.onActivityPinned(packageName);
@@ -585,6 +587,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
final Pair<ComponentName, Integer> topPipActivityInfo =
PipUtils.getTopPipActivity(mContext);
final ComponentName topActivity = topPipActivityInfo.first;
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onActivityUnpinned: %s", topActivity);
mTouchHandler.onActivityUnpinned(topActivity);
mAppOpsListener.onActivityUnpinned();
mPipInputConsumer.unregisterInputConsumer();
@@ -593,6 +597,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onActivityRestartAttempt: %s", task.topActivity);
if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 8723f9b0181d..a612f5f09e88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -595,9 +595,20 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
cancel(mWillFinishToHome, true /* withScreenshots */, "display change");
return;
}
- // Don't consider order-only changes as changing apps.
- if (!TransitionUtil.isOrderOnly(change)) {
+ // Don't consider order-only & non-leaf changes as changing apps.
+ if (!TransitionUtil.isOrderOnly(change) && isLeafTask) {
hasChangingApp = true;
+ } else if (isLeafTask && taskInfo.topActivityType == ACTIVITY_TYPE_HOME
+ && !mRecentsTask.equals(change.getContainer())) {
+ // Unless it is a 3p launcher. This means that the 3p launcher was already
+ // visible (eg. the "pausing" task is translucent over the 3p launcher).
+ // Treat it as if we are "re-opening" the 3p launcher.
+ if (openingTasks == null) {
+ openingTasks = new ArrayList<>();
+ openingTaskIsLeafs = new IntArray();
+ }
+ openingTasks.add(change);
+ openingTaskIsLeafs.add(1);
}
}
}
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 e7ef6a0d71d3..e294229038f2 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
@@ -89,6 +89,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -346,6 +347,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
}
+ /** Get the split stage of task is under it. */
+ public @StageType int getStageOfTask(int taskId) {
+ return mStageCoordinator.getStageOfTask(taskId);
+ }
+
/** Check split is foreground and task is under split or not by taskId. */
public boolean isTaskInSplitScreenForeground(int taskId) {
return isTaskInSplitScreen(taskId) && isSplitScreenVisible();
@@ -392,17 +398,35 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
}
- public void enterSplitScreen(int taskId, boolean leftOrTop) {
- enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction());
- }
-
+ /**
+ * Doing necessary window transaction for other transition handler need to enter split in
+ * transition.
+ */
public void prepareEnterSplitScreen(WindowContainerTransaction wct,
ActivityManager.RunningTaskInfo taskInfo, int startPosition) {
- mStageCoordinator.prepareEnterSplitScreen(wct, taskInfo, startPosition);
+ mStageCoordinator.prepareEnterSplitScreen(wct, taskInfo, startPosition,
+ false /* resizeAnim */);
+ }
+
+ /**
+ * Doing necessary surface transaction for other transition handler need to enter split in
+ * transition when finished.
+ */
+ public void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
+ mStageCoordinator.finishEnterSplitScreen(finishT);
}
- public void finishEnterSplitScreen(SurfaceControl.Transaction t) {
- mStageCoordinator.finishEnterSplitScreen(t);
+ /**
+ * Doing necessary window transaction for other transition handler need to exit split in
+ * transition.
+ */
+ public void prepareExitSplitScreen(WindowContainerTransaction wct,
+ @StageType int stageToTop) {
+ mStageCoordinator.prepareExitSplitScreen(stageToTop, wct);
+ }
+
+ public void enterSplitScreen(int taskId, boolean leftOrTop) {
+ enterSplitScreen(taskId, leftOrTop, new WindowContainerTransaction());
}
public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) {
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 7e82497d2c9c..4b648bf9add9 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
@@ -122,7 +122,6 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
@@ -173,7 +172,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final StageListenerImpl mMainStageListener = new StageListenerImpl();
private final SideStage mSideStage;
private final StageListenerImpl mSideStageListener = new StageListenerImpl();
- private final DisplayLayout mDisplayLayout;
@SplitPosition
private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -321,7 +319,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
this::onTransitionAnimationComplete, this);
mDisplayController.addDisplayWindowListener(this);
- mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
transitions.addHandler(this);
mSplitUnsupportedToast = Toast.makeText(mContext,
R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
@@ -359,7 +356,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mLaunchAdjacentController = launchAdjacentController;
mWindowDecorViewModel = windowDecorViewModel;
mDisplayController.addDisplayWindowListener(this);
- mDisplayLayout = new DisplayLayout();
transitions.addHandler(this);
mSplitUnsupportedToast = Toast.makeText(mContext,
R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
@@ -407,7 +403,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition,
WindowContainerTransaction wct) {
- prepareEnterSplitScreen(wct, task, stagePosition);
+ prepareEnterSplitScreen(wct, task, stagePosition, false /* resizeAnim */);
if (ENABLE_SHELL_TRANSITIONS) {
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct,
null, this,
@@ -505,20 +501,26 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
+ mSplitRequest = new SplitRequest(intent.getIntent(), position);
if (!ENABLE_SHELL_TRANSITIONS) {
startIntentLegacy(intent, fillInIntent, position, options);
return;
}
final WindowContainerTransaction wct = new WindowContainerTransaction();
-
options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
wct.sendPendingIntent(intent, fillInIntent, options);
+ // If this should be mixed, just send the intent to avoid split handle transition directly.
+ if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(intent)) {
+ mTaskOrganizer.applyTransaction(wct);
+ return;
+ }
+
// If split screen is not activated, we're expecting to open a pair of apps to split.
final int extraTransitType = mMainStage.isActive()
? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
- prepareEnterSplitScreen(wct, null /* taskInfo */, position);
+ prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering);
mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this,
extraTransitType, !mIsDropEntering);
@@ -575,7 +577,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (isEnteringSplit && mLogger.isEnterRequestedByDrag()) {
updateWindowBounds(mSplitLayout, wct);
}
- mSplitRequest = new SplitRequest(intent.getIntent(), position);
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
@@ -1463,7 +1464,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* an existing WindowContainerTransaction (rather than applying immediately). This is intended
* to be used when exiting split might be bundled with other window operations.
*/
- private void prepareExitSplitScreen(@StageType int stageToTop,
+ void prepareExitSplitScreen(@StageType int stageToTop,
@NonNull WindowContainerTransaction wct) {
if (!mMainStage.isActive()) return;
mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
@@ -1471,7 +1472,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
private void prepareEnterSplitScreen(WindowContainerTransaction wct) {
- prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED);
+ prepareEnterSplitScreen(wct, null /* taskInfo */, SPLIT_POSITION_UNDEFINED,
+ !mIsDropEntering);
}
/**
@@ -1479,17 +1481,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
* into side stage.
*/
void prepareEnterSplitScreen(WindowContainerTransaction wct,
- @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
+ @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
+ boolean resizeAnim) {
onSplitScreenEnter();
if (isSplitActive()) {
- prepareBringSplit(wct, taskInfo, startPosition);
+ prepareBringSplit(wct, taskInfo, startPosition, resizeAnim);
} else {
- prepareActiveSplit(wct, taskInfo, startPosition);
+ prepareActiveSplit(wct, taskInfo, startPosition, resizeAnim);
}
}
private void prepareBringSplit(WindowContainerTransaction wct,
- @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
+ @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
+ boolean resizeAnim) {
if (taskInfo != null) {
wct.startTask(taskInfo.taskId,
resolveStartStage(STAGE_TYPE_UNDEFINED, startPosition, null, wct));
@@ -1501,12 +1505,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// won't guarantee to put the task to the indicated new position.
mMainStage.evictAllChildren(wct);
mMainStage.reparentTopTask(wct);
- prepareSplitLayout(wct);
+ prepareSplitLayout(wct, resizeAnim);
}
}
private void prepareActiveSplit(WindowContainerTransaction wct,
- @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition) {
+ @Nullable ActivityManager.RunningTaskInfo taskInfo, @SplitPosition int startPosition,
+ boolean resizeAnim) {
if (!ENABLE_SHELL_TRANSITIONS) {
// Legacy transition we need to create divider here, shell transition case we will
// create it on #finishEnterSplitScreen
@@ -1517,17 +1522,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.addTask(taskInfo, wct);
}
mMainStage.activate(wct, true /* includingTopTask */);
- prepareSplitLayout(wct);
+ prepareSplitLayout(wct, resizeAnim);
}
- private void prepareSplitLayout(WindowContainerTransaction wct) {
- if (mIsDropEntering) {
- mSplitLayout.resetDividerPosition();
- } else {
+ private void prepareSplitLayout(WindowContainerTransaction wct, boolean resizeAnim) {
+ if (resizeAnim) {
mSplitLayout.setDividerAtBorder(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+ } else {
+ mSplitLayout.resetDividerPosition();
}
updateWindowBounds(mSplitLayout, wct);
- if (!mIsDropEntering) {
+ if (resizeAnim) {
// Reset its smallest width dp to avoid is change layout before it actually resized to
// split bounds.
wct.setSmallestScreenWidthDp(mMainStage.mRootTaskInfo.token,
@@ -1537,21 +1542,22 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setRootForceTranslucent(false, wct);
}
- void finishEnterSplitScreen(SurfaceControl.Transaction t) {
- mSplitLayout.update(t);
+ void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
+ mSplitLayout.update(finishT);
mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash,
getMainStageBounds());
mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash,
getSideStageBounds());
- setDividerVisibility(true, t);
+ setDividerVisibility(true, finishT);
// Ensure divider surface are re-parented back into the hierarchy at the end of the
// transition. See Transition#buildFinishTransaction for more detail.
- t.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
+ finishT.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
- t.show(mRootTaskLeash);
+ updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */);
+ finishT.show(mRootTaskLeash);
setSplitsVisible(true);
mIsDropEntering = false;
+ mSplitRequest = null;
updateRecentTasksSplitPair();
if (!mLogger.hasStartedSession()) {
mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
@@ -1703,7 +1709,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mSplitLayout == null) {
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mRootTaskInfo.configuration, this, mParentContainerCallbacks,
- mDisplayImeController, mTaskOrganizer,
+ mDisplayController, mDisplayImeController, mTaskOrganizer,
PARALLAX_ALIGN_CENTER /* parallaxType */);
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
}
@@ -2165,8 +2171,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (displayId != DEFAULT_DISPLAY) {
return;
}
- mDisplayLayout.set(mDisplayController.getDisplayLayout(displayId));
-
if (mSplitLayout != null && mSplitLayout.isDensityChanged(newConfig.densityDpi)
&& mMainStage.isActive()
&& mSplitLayout.updateConfiguration(newConfig)
@@ -2183,10 +2187,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
- if (!mMainStage.isActive()) return;
+ if (displayId != DEFAULT_DISPLAY || !mMainStage.isActive()) return;
- mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
- mSplitLayout.rotateTo(toRotation, mDisplayLayout.stableInsets());
+ mSplitLayout.rotateTo(toRotation);
if (newDisplayAreaInfo != null) {
mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration);
}
@@ -2511,6 +2514,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// handling to the mixed-handler to deal with splitting it up.
if (mMixedHandler.animatePendingSplitWithDisplayChange(transition, info,
startTransaction, finishTransaction, finishCallback)) {
+ mSplitLayout.update(startTransaction);
return true;
}
}
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 478083607aac..e3ed5dd73de8 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
@@ -188,12 +188,12 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
final int taskId = taskInfo.taskId;
mChildrenLeashes.put(taskId, leash);
mChildrenTaskInfo.put(taskId, taskInfo);
- updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
if (ENABLE_SHELL_TRANSITIONS) {
// Status is managed/synchronized by the transition lifecycle.
return;
}
+ updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
mCallbacks.onChildTaskAppeared(taskId);
sendStatusChanged();
} else {
@@ -423,7 +423,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
t.setCrop(leash, null);
t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
- if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {
+ if (firstAppeared) {
t.setAlpha(leash, 1f);
t.setMatrix(leash, 1, 0, 0, 1);
t.show(leash);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index f58f24be4984..4176fe800eb5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -30,6 +30,7 @@ import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.PendingIntent;
import android.os.IBinder;
import android.util.Log;
import android.util.Pair;
@@ -44,6 +45,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopTasksController;
+import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
@@ -637,6 +639,17 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
return false;
}
+ /** Use to when split use intent to enter, check if this enter transition should be mixed or
+ * not.*/
+ public boolean shouldSplitEnterMixed(PendingIntent intent) {
+ // Check if this intent package is same as pip one or not, if true we want let the pip
+ // task enter split.
+ if (mPipHandler != null) {
+ return mPipHandler.isInPipPackage(SplitScreenUtils.getPackageName(intent.getIntent()));
+ }
+ return false;
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 4c678a2bf29b..3bf278cc46cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -58,7 +58,6 @@ import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITI
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_NONE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
-import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
import static com.android.wm.shell.transition.TransitionAnimationHelper.loadAttributeAnimation;
@@ -76,6 +75,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
@@ -103,6 +103,7 @@ import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -137,6 +138,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private final Rect mInsets = new Rect(0, 0, 0, 0);
private float mTransitionAnimationScaleSetting = 1.0f;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
private final int mCurrentUserId;
private Drawable mEnterpriseThumbnailDrawable;
@@ -157,7 +159,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
@NonNull DisplayController displayController,
@NonNull TransactionPool transactionPool,
@NonNull ShellExecutor mainExecutor, @NonNull Handler mainHandler,
- @NonNull ShellExecutor animExecutor) {
+ @NonNull ShellExecutor animExecutor,
+ @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
mDisplayController = displayController;
mTransactionPool = transactionPool;
mContext = context;
@@ -168,6 +171,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
mCurrentUserId = UserHandle.myUserId();
mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
shellInit.addInitCallback(this::onInit, this);
+ mRootTDAOrganizer = rootTDAOrganizer;
}
private void onInit() {
@@ -510,10 +514,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
if (backgroundColorForTransition != 0) {
- for (int i = 0; i < info.getRootCount(); ++i) {
- addBackgroundToTransition(info.getRoot(i).getLeash(), backgroundColorForTransition,
- startTransaction, finishTransaction);
- }
+ addBackgroundColorOnTDA(info, backgroundColorForTransition, startTransaction,
+ finishTransaction);
}
if (postStartTransactionCallbacks.size() > 0) {
@@ -543,6 +545,28 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return true;
}
+ private void addBackgroundColorOnTDA(@NonNull TransitionInfo info,
+ @ColorInt int color, @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ final Color bgColor = Color.valueOf(color);
+ final float[] colorArray = new float[] { bgColor.red(), bgColor.green(), bgColor.blue() };
+
+ for (int i = 0; i < info.getRootCount(); ++i) {
+ final int displayId = info.getRoot(i).getDisplayId();
+ final SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
+ .setName("animation-background")
+ .setCallsite("DefaultTransitionHandler")
+ .setColorLayer();
+
+ mRootTDAOrganizer.attachToDisplayArea(displayId, colorLayerBuilder);
+ final SurfaceControl backgroundSurface = colorLayerBuilder.build();
+ startTransaction.setColor(backgroundSurface, colorArray)
+ .setLayer(backgroundSurface, -1)
+ .show(backgroundSurface);
+ finishTransaction.remove(backgroundSurface);
+ }
+ }
+
private static boolean isDreamTransition(@NonNull TransitionInfo info) {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
@@ -696,9 +720,10 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
if (a != null) {
if (!a.isInitialized()) {
- final int width = endBounds.width();
- final int height = endBounds.height();
- a.initialize(width, height, width, height);
+ final Rect animationRange = TransitionUtil.isClosingType(changeMode)
+ ? change.getStartAbsBounds() : change.getEndAbsBounds();
+ a.initialize(animationRange.width(), animationRange.height(),
+ endBounds.width(), endBounds.height());
}
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
@@ -873,18 +898,31 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
}
+ /**
+ * Returns {@code true} if the default transition handler can run the override animation.
+ * @see #loadAnimation(TransitionInfo, TransitionInfo.Change, int, boolean)
+ */
+ public static boolean isSupportedOverrideAnimation(
+ @NonNull TransitionInfo.AnimationOptions options) {
+ final int animType = options.getType();
+ return animType == ANIM_CUSTOM || animType == ANIM_SCALE_UP
+ || animType == ANIM_THUMBNAIL_SCALE_UP || animType == ANIM_THUMBNAIL_SCALE_DOWN
+ || animType == ANIM_CLIP_REVEAL || animType == ANIM_OPEN_CROSS_PROFILE_APPS;
+ }
+
private static void applyTransformation(long time, SurfaceControl.Transaction t,
- SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix,
+ SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix,
Point position, float cornerRadius, @Nullable Rect immutableClipRect) {
- anim.getTransformation(time, transformation);
+ tmpTransformation.clear();
+ anim.getTransformation(time, tmpTransformation);
if (position != null) {
- transformation.getMatrix().postTranslate(position.x, position.y);
+ tmpTransformation.getMatrix().postTranslate(position.x, position.y);
}
- t.setMatrix(leash, transformation.getMatrix(), matrix);
- t.setAlpha(leash, transformation.getAlpha());
+ t.setMatrix(leash, tmpTransformation.getMatrix(), matrix);
+ t.setAlpha(leash, tmpTransformation.getAlpha());
final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect);
- Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
+ Insets extensionInsets = Insets.min(tmpTransformation.getInsets(), Insets.NONE);
if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
// Clip out any overflowing edge extension
clipRect.inset(extensionInsets);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index de20c2d90066..cdc82eadcd90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -69,6 +69,7 @@ import androidx.annotation.BinderThread;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
@@ -265,7 +266,8 @@ public class Transitions implements RemoteCallable<Transitions>,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor) {
this(context, shellInit, shellController, organizer, pool, displayController, mainExecutor,
- mainHandler, animExecutor, null);
+ mainHandler, animExecutor, null,
+ new RootTaskDisplayAreaOrganizer(mainExecutor, context));
}
public Transitions(@NonNull Context context,
@@ -277,7 +279,8 @@ public class Transitions implements RemoteCallable<Transitions>,
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
- @Nullable ShellCommandHandler shellCommandHandler) {
+ @Nullable ShellCommandHandler shellCommandHandler,
+ @NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
@@ -285,7 +288,7 @@ public class Transitions implements RemoteCallable<Transitions>,
mDisplayController = displayController;
mPlayerImpl = new TransitionPlayerImpl();
mDefaultTransitionHandler = new DefaultTransitionHandler(context, shellInit,
- displayController, pool, mainExecutor, mainHandler, animExecutor);
+ displayController, pool, mainExecutor, mainHandler, animExecutor, rootTDAOrganizer);
mRemoteTransitionHandler = new RemoteTransitionHandler(mMainExecutor);
mShellController = shellController;
// The very last handler (0 in the list) should be the default one.
@@ -641,6 +644,7 @@ public class Transitions implements RemoteCallable<Transitions>,
@VisibleForTesting
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
+ info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
transitionToken, info);
final int activeIdx = findByToken(mPendingTransitions, transitionToken);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ee407c07b47c..4407f2ec3167 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -267,6 +267,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
.setColor(mTaskSurface, mTmpColor)
.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
+ .setShadowRadius(mTaskSurface, shadowRadius)
.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index 76c80f7bbe85..d293cf73a0f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -1,32 +1,35 @@
package com.android.wm.shell.windowdecor.viewholder
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.graphics.Color
import android.view.View
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+
/**
* Encapsulates the root [View] of a window decoration and its children to facilitate looking up
* children (via findViewById) and updating to the latest data from [RunningTaskInfo].
*/
internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
- val context: Context = rootView.context
+ val context: Context = rootView.context
- /**
- * A signal to the view holder that new data is available and that the views should be updated
- * to reflect it.
- */
- abstract fun bindData(taskInfo: RunningTaskInfo)
+ /**
+ * A signal to the view holder that new data is available and that the views should be updated to
+ * reflect it.
+ */
+ abstract fun bindData(taskInfo: RunningTaskInfo)
- /**
- * Whether the caption items should use the 'light' color variant so that there's good contrast
- * with the caption background color.
- */
- protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
- return if (Color.alpha(taskInfo.taskDescription.statusBarColor) != 0) {
- Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
- } else {
- taskInfo.taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
- }
+ /**
+ * Whether the caption items should use the 'light' color variant so that there's good contrast
+ * with the caption background color.
+ */
+ protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
+ return if (Color.alpha(taskInfo.taskDescription.statusBarColor) != 0 &&
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
+ } else {
+ taskInfo.taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
}
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
new file mode 100644
index 000000000000..f7ce87088040
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt
@@ -0,0 +1,200 @@
+/*
+ * 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.wm.shell.flicker.pip
+
+import android.app.Instrumentation
+import android.os.SystemClock
+import android.platform.test.annotations.Presubmit
+import android.tools.common.NavBar
+import android.tools.common.Rotation
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.helpers.WindowUtils
+import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from an app via auto-enter property when navigating to home from split screen.
+ *
+ * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ *
+ * Actions:
+ * ```
+ * Launch an app in full screen
+ * Select "Auto-enter PiP" radio button
+ * Open all apps and drag another app icon to enter split screen
+ * Press Home button or swipe up to go Home and put [pipApp] in pip mode
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. All assertions are inherited from [EnterPipTest]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.device.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: FlickerTest) :
+ AutoEnterPipOnGoToHomeTest(flicker) {
+ private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
+ /** Second app used to enter split screen mode */
+ protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation)
+ fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper =
+ SimpleAppHelper(
+ instrumentation,
+ ActivityOptions.SplitScreen.Primary.LABEL,
+ ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()
+ )
+
+ /** Defines the transition used to run the test */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ secondAppForSplitScreen.launchViaIntent(wmHelper)
+ pipApp.launchViaIntent(wmHelper)
+ tapl.goHome()
+ enterSplitScreen()
+ // wait until split screen is established
+ wmHelper
+ .StateSyncBuilder()
+ .withWindowSurfaceAppeared(pipApp)
+ .withWindowSurfaceAppeared(secondAppForSplitScreen)
+ .withSplitDividerVisible()
+ .waitForAndVerify()
+ pipApp.enableAutoEnterForPipActivity()
+ }
+ teardown {
+ // close gracefully so that onActivityUnpinned() can be called before force exit
+ pipApp.closePipWindow(wmHelper)
+ pipApp.exit(wmHelper)
+ secondAppForSplitScreen.exit(wmHelper)
+ }
+ transitions { tapl.goHome() }
+ }
+
+ // TODO(b/285400227) merge the code in a common utility - this is copied from SplitScreenUtils
+ private val TIMEOUT_MS = 3_000L
+ private val overviewSnapshotSelector: BySelector
+ get() = By.res(LAUNCHER_UI_PACKAGE_NAME, "snapshot")
+ private fun enterSplitScreen() {
+ // Note: The initial split position in landscape is different between tablet and phone.
+ // In landscape, tablet will let the first app split to right side, and phone will
+ // split to left side.
+ if (tapl.isTablet) {
+ // TAPL's currentTask on tablet is sometimes not what we expected if the overview
+ // contains more than 3 task views. We need to use uiautomator directly to find the
+ // second task to split.
+ tapl.workspace.switchToOverview().overviewActions.clickSplit()
+ val snapshots = tapl.device.wait(Until.findObjects(overviewSnapshotSelector),
+ TIMEOUT_MS)
+ if (snapshots == null || snapshots.size < 1) {
+ error("Fail to find a overview snapshot to split.")
+ }
+
+ // Find the second task in the upper right corner in split select mode by sorting
+ // 'left' in descending order and 'top' in ascending order.
+ snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+ t2.getVisibleBounds().left - t1.getVisibleBounds().left
+ }
+ snapshots.sortWith { t1: UiObject2, t2: UiObject2 ->
+ t1.getVisibleBounds().top - t2.getVisibleBounds().top
+ }
+ snapshots[0].click()
+ } else {
+ tapl.workspace
+ .switchToOverview()
+ .currentTask
+ .tapMenu()
+ .tapSplitMenuItem()
+ .currentTask
+ .open()
+ }
+ SystemClock.sleep(TIMEOUT_MS)
+ }
+
+ @Presubmit
+ @Test
+ override fun pipOverlayLayerAppearThenDisappear() {
+ // when entering from split screen we use alpha animation, without overlay
+ }
+
+ @Presubmit
+ @Test
+ override fun pipLayerOrOverlayRemainInsideVisibleBounds() {
+ // when entering from split screen we use alpha animation, without overlay
+ }
+
+ @Presubmit
+ @Test
+ override fun pipLayerReduces() {
+ // when entering from split screen we use alpha animation, so there is no size change
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipLayerReduces()
+ }
+
+ @Presubmit
+ @Test
+ override fun pipAppLayerAlwaysVisible() {
+ // pip layer in gesture nav will disappear during transition with alpha animation
+ Assume.assumeFalse(flicker.scenario.isGesturalNavigation)
+ super.pipAppLayerAlwaysVisible()
+ }
+
+ @Presubmit
+ @Test
+ override fun pipWindowRemainInsideVisibleBounds() {
+ if (tapl.isTablet) {
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
+ } else {
+ // on phones home does not rotate in landscape, PiP enters back to portrait
+ // orientation so use display bounds from that orientation for assertion
+ flicker.assertWmVisibleRegion(pipApp) { coversAtMost(portraitDisplayBounds) }
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests(
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index ef7bedf49a92..b95732e43357 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -53,7 +53,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
+open class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
override val thisTransition: FlickerBuilder.() -> Unit = {
transitions { tapl.goHome() }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index 95121def07bc..cdbdb85a9195 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -62,7 +62,7 @@ abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker)
*/
@Presubmit
@Test
- fun pipWindowRemainInsideVisibleBounds() {
+ open fun pipWindowRemainInsideVisibleBounds() {
flicker.assertWmVisibleRegion(pipApp) { coversAtMost(displayBounds) }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index 9da5ab6f71dd..145c8f0ab8af 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -37,6 +37,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import org.junit.Before;
@@ -51,6 +52,7 @@ import org.mockito.MockitoAnnotations;
public class DividerViewTest extends ShellTestCase {
private @Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
private @Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler;
+ private @Mock DisplayController mDisplayController;
private @Mock DisplayImeController mDisplayImeController;
private @Mock ShellTaskOrganizer mTaskOrganizer;
private SplitLayout mSplitLayout;
@@ -62,8 +64,8 @@ public class DividerViewTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
Configuration configuration = getConfiguration();
mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
- mSplitLayoutHandler, mCallbacks, mDisplayImeController, mTaskOrganizer,
- SplitLayout.PARALLAX_NONE);
+ mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController,
+ mTaskOrganizer, SplitLayout.PARALLAX_NONE);
SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
mContext,
configuration, mCallbacks);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 3d779481d361..443cea245a4f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
@@ -41,6 +42,7 @@ import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import org.junit.Before;
@@ -57,6 +59,7 @@ import org.mockito.MockitoAnnotations;
public class SplitLayoutTests extends ShellTestCase {
@Mock SplitLayout.SplitLayoutHandler mSplitLayoutHandler;
@Mock SplitWindowManager.ParentContainerCallbacks mCallbacks;
+ @Mock DisplayController mDisplayController;
@Mock DisplayImeController mDisplayImeController;
@Mock ShellTaskOrganizer mTaskOrganizer;
@Mock WindowContainerTransaction mWct;
@@ -72,6 +75,7 @@ public class SplitLayoutTests extends ShellTestCase {
getConfiguration(),
mSplitLayoutHandler,
mCallbacks,
+ mDisplayController,
mDisplayImeController,
mTaskOrganizer,
SplitLayout.PARALLAX_NONE));
@@ -100,6 +104,10 @@ public class SplitLayoutTests extends ShellTestCase {
// Verify updateConfiguration returns true if the density changed.
config.densityDpi = 123;
assertThat(mSplitLayout.updateConfiguration(config)).isTrue();
+
+ // Verify updateConfiguration checks the current DisplayLayout
+ verify(mDisplayController, times(5)) // init * 1 + updateConfiguration * 4
+ .getDisplayLayout(anyInt());
}
@Test
@@ -168,6 +176,14 @@ public class SplitLayoutTests extends ShellTestCase {
verify(mWct).setSmallestScreenWidthDp(eq(task2.token), anyInt());
}
+ @Test
+ public void testRoateTo_checksDisplayLayout() {
+ mSplitLayout.rotateTo(90);
+
+ verify(mDisplayController, times(2)) // init * 1 + rotateTo * 1
+ .getDisplayLayout(anyInt());
+ }
+
private void waitDividerFlingFinished() {
verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(),
mRunnableCaptor.capture());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index f1c0c145b31e..e59d09cd1ee1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -162,7 +162,7 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false));
verify(mMainStage).reparentTopTask(eq(wct));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
@@ -180,7 +180,7 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition());
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition());
}
@@ -192,7 +192,7 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task),
- eq(SPLIT_POSITION_BOTTOM_OR_RIGHT));
+ eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), eq(false));
assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 963632b1f8f6..961e3e9c5735 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -51,7 +51,6 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.after;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
@@ -93,7 +92,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.server.testutils.StubTransaction;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.TransitionInfoBuilder;
@@ -105,6 +103,7 @@ import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.sysui.ShellSharedConstants;
+import com.android.wm.shell.util.StubTransaction;
import org.junit.Before;
import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/StubTransaction.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/StubTransaction.java
new file mode 100644
index 000000000000..855f5416fd0f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/StubTransaction.java
@@ -0,0 +1,313 @@
+/*
+ * 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.wm.shell.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.HardwareBuffer;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.view.InputWindowHandle;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import java.util.HashSet;
+import java.util.concurrent.Executor;
+
+/**
+ * Stubbed {@link SurfaceControl.Transaction} class that can be used when unit
+ * testing to avoid calls to native code.
+ *
+ * Note: This is a copy of com.android.server.testutils.StubTransaction
+ */
+public class StubTransaction extends SurfaceControl.Transaction {
+
+ private HashSet<Runnable> mWindowInfosReportedListeners = new HashSet<>();
+
+ @Override
+ public void apply() {
+ for (Runnable listener : mWindowInfosReportedListeners) {
+ listener.run();
+ }
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public void apply(boolean sync) {
+ apply();
+ }
+
+ @Override
+ public SurfaceControl.Transaction setVisibility(SurfaceControl sc, boolean visible) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction show(SurfaceControl sc) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction hide(SurfaceControl sc) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setPosition(SurfaceControl sc, float x, float y) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setBufferSize(SurfaceControl sc,
+ int w, int h) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setLayer(SurfaceControl sc, int z) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setRelativeLayer(SurfaceControl sc, SurfaceControl relativeTo,
+ int z) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setTransparentRegionHint(SurfaceControl sc,
+ Region transparentRegion) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setAlpha(SurfaceControl sc, float alpha) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setInputWindowInfo(SurfaceControl sc,
+ InputWindowHandle handle) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setGeometry(SurfaceControl sc, Rect sourceCrop,
+ Rect destFrame, @Surface.Rotation int orientation) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setMatrix(SurfaceControl sc,
+ float dsdx, float dtdx, float dtdy, float dsdy) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setMatrix(SurfaceControl sc, Matrix matrix, float[] float9) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setColorTransform(SurfaceControl sc, float[] matrix,
+ float[] translation) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setWindowCrop(SurfaceControl sc, Rect crop) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setWindowCrop(SurfaceControl sc, int width, int height) {
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public SurfaceControl.Transaction setCrop(@NonNull SurfaceControl sc, @Nullable Rect crop) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setCornerRadius(SurfaceControl sc, float cornerRadius) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setBackgroundBlurRadius(SurfaceControl sc, int radius) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setLayerStack(SurfaceControl sc, int layerStack) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction reparent(SurfaceControl sc, SurfaceControl newParent) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setColor(SurfaceControl sc, float[] color) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setSecure(SurfaceControl sc, boolean isSecure) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setOpaque(SurfaceControl sc, boolean isOpaque) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setDisplaySurface(IBinder displayToken, Surface surface) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setDisplayLayerStack(IBinder displayToken, int layerStack) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setDisplayFlags(IBinder displayToken, int flags) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setDisplayProjection(IBinder displayToken,
+ int orientation, Rect layerStackRect, Rect displayRect) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setDisplaySize(IBinder displayToken, int width, int height) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setAnimationTransaction() {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setMetadata(SurfaceControl sc, int key, int data) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setMetadata(SurfaceControl sc, int key, Parcel data) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction merge(SurfaceControl.Transaction other) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction remove(SurfaceControl sc) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction addTransactionCommittedListener(Executor executor,
+ SurfaceControl.TransactionCommittedListener listener) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setColorSpaceAgnostic(SurfaceControl sc, boolean agnostic) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setFrameRateSelectionPriority(SurfaceControl sc,
+ int priority) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setFrameRate(SurfaceControl sc, float frameRate,
+ int compatibility, int changeFrameRateStrategy) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction unsetColor(SurfaceControl sc) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setFixedTransformHint(SurfaceControl sc,
+ @Surface.Rotation int transformHint) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) {
+ return this;
+ }
+
+ @Override
+ @NonNull
+ public SurfaceControl.Transaction setBuffer(@NonNull SurfaceControl sc,
+ @Nullable HardwareBuffer buffer) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setTrustedOverlay(SurfaceControl sc,
+ boolean isTrustedOverlay) {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction addWindowInfosReportedListener(@NonNull Runnable listener) {
+ mWindowInfosReportedListeners.add(listener);
+ return this;
+ }
+}
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index 0f8a85dd9e62..cec0ee7ee247 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -86,6 +86,16 @@ jlong Gainmap_createEmpty(JNIEnv*, jobject) {
return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap));
}
+jlong Gainmap_createCopy(JNIEnv*, jobject, jlong sourcePtr) {
+ Gainmap* gainmap = new Gainmap();
+ gainmap->incStrong(0);
+ if (sourcePtr) {
+ Gainmap* src = fromJava(sourcePtr);
+ gainmap->info = src->info;
+ }
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(gainmap));
+}
+
static void Gainmap_setBitmap(JNIEnv* env, jobject, jlong gainmapPtr, jobject jBitmap) {
android::Bitmap* bitmap = GraphicsJNI::getNativeBitmap(env, jBitmap);
fromJava(gainmapPtr)->bitmap = sk_ref_sp(bitmap);
@@ -237,6 +247,7 @@ static void Gainmap_readFromParcel(JNIEnv* env, jobject, jlong nativeObject, job
static const JNINativeMethod gGainmapMethods[] = {
{"nGetFinalizer", "()J", (void*)Gainmap_getNativeFinalizer},
{"nCreateEmpty", "()J", (void*)Gainmap_createEmpty},
+ {"nCreateCopy", "(J)J", (void*)Gainmap_createCopy},
{"nSetBitmap", "(JLandroid/graphics/Bitmap;)V", (void*)Gainmap_setBitmap},
{"nSetRatioMin", "(JFFF)V", (void*)Gainmap_setRatioMin},
{"nGetRatioMin", "(J[F)V", (void*)Gainmap_getRatioMin},
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 32680dae75b9..38bb447f8a16 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -359,6 +359,51 @@ import java.util.concurrent.locks.ReentrantLock;
codec to use a previously created {@linkplain #createPersistentInputSurface persistent input
surface} by calling {@link #setInputSurface}.
+ <h4 id=EncoderProfiles><a name="EncoderProfiles"></a>Encoder Profiles</h4>
+ <p>
+ When using an encoder, it is recommended to set the desired codec {@link MediaFormat#KEY_PROFILE
+ profile} during {@link #configure configure()}. (This is only meaningful for
+ {@link MediaFormat#KEY_MIME media formats} for which profiles are defined.)
+ <p>
+ If a profile is not specified during {@code configure}, the encoder will choose a profile for the
+ session based on the available information. We will call this value the <i>default profile</i>.
+ The selection of the default profile is device specific and may not be deterministic
+ (could be ad hoc or even experimental). The encoder may choose a default profile that is not
+ suitable for the intended encoding session, which may result in the encoder ultimately rejecting
+ the session.
+ <p>
+ The encoder may reject the encoding session if the configured (or default if unspecified) profile
+ does not support the codec input (mainly the {@link MediaFormat#KEY_COLOR_FORMAT color format} for
+ video/image codecs, or the {@link MediaFormat#KEY_PCM_ENCODING sample encoding} and the {@link
+ MediaFormat#KEY_CHANNEL_COUNT number of channels} for audio codecs, but also possibly
+ {@link MediaFormat#KEY_WIDTH width}, {@link MediaFormat#KEY_HEIGHT height},
+ {@link MediaFormat#KEY_FRAME_RATE frame rate}, {@link MediaFormat#KEY_BIT_RATE bitrate} or
+ {@link MediaFormat#KEY_SAMPLE_RATE sample rate}.)
+ Alternatively, the encoder may choose to (but is not required to) convert the input to support the
+ selected (or default) profile - or adjust the chosen profile based on the presumed or detected
+ input format - to ensure a successful encoding session. <b>Note</b>: Converting the input to match
+ an incompatible profile will in most cases result in decreased codec performance.
+ <p>
+ To ensure backward compatibility, the following guarantees are provided by Android:
+ <ul>
+ <li>The default video encoder profile always supports 8-bit YUV 4:2:0 color format ({@link
+ CodecCapabilities#COLOR_FormatYUV420Flexible COLOR_FormatYUV420Flexible} and equivalent
+ {@link CodecCapabilities#colorFormats supported formats}) for both Surface and ByteBuffer modes.
+ <li>The default video encoder profile always supports the default 8-bit RGBA color format in
+ Surface mode even if no such formats are enumerated in the {@link CodecCapabilities#colorFormats
+ supported formats}.
+ </ul>
+ <p class=note>
+ <b>Note</b>: the accepted profile can be queried through the {@link #getOutputFormat output
+ format} of the encoder after {@code configure} to allow applications to set up their
+ codec input to a format supported by the encoder profile.
+ <p>
+ <b>Implication:</b>
+ <ul>
+ <li>Applications that want to encode 4:2:2, 4:4:4, 10+ bit or HDR video input <b>MUST</b> configure
+ a suitable profile for encoders.
+ </ul>
+
<h4 id=CSD><a name="CSD"></a>Codec-specific Data</h4>
<p>
Some formats, notably AAC audio and MPEG4, H.264 and H.265 video formats require the actual data
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index b1b7d4000635..46db77708521 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -1136,11 +1136,14 @@ public final class MediaFormat {
* may fail if other parameters are not compatible with the desired
* profile or if the desired profile is not supported, but it may also
* fail silently (where the encoder ends up using a different, compatible profile.)
+ * <p>
+ * It is recommended that the profile is set for all encoders. For more information, see
+ * the <i>Encoder Profiles</i> section of the {@link MediaCodec} API reference.
* <p class="note">
* <strong>Note:</strong> Codecs are free to use all the available
* coding tools at the specified profile, but may ultimately choose to not do so.
* <p class="note">
- * <strong>Note:</strong> When configuring video encoders, profile must be
+ * <strong>Note:</strong> When configuring video encoders, profile (if set) must be
* set together with {@link #KEY_LEVEL level}.
*
* @see MediaCodecInfo.CodecCapabilities#profileLevels
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 2dafbcb95205..a78509d897aa 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -66,8 +66,9 @@ class CredentialManagerRepo(
)
val originName: String? = when (requestInfo?.type) {
- RequestInfo.TYPE_CREATE -> requestInfo.createCredentialRequest?.origin
- RequestInfo.TYPE_GET -> requestInfo.getCredentialRequest?.origin
+ RequestInfo.TYPE_CREATE -> processHttpsOrigin(
+ requestInfo.createCredentialRequest?.origin)
+ RequestInfo.TYPE_GET -> processHttpsOrigin(requestInfo.getCredentialRequest?.origin)
else -> null
}
@@ -247,6 +248,9 @@ class CredentialManagerRepo(
}
companion object {
+ private const val HTTPS = "https://"
+ private const val FORWARD_SLASH = "/"
+
fun sendCancellationCode(
cancelCode: Int,
requestToken: IBinder?,
@@ -266,5 +270,17 @@ class CredentialManagerRepo(
CancelUiRequest::class.java
)
}
+
+ /** Removes "https://", and the trailing slash if present for an https request. */
+ private fun processHttpsOrigin(origin: String?): String? {
+ var processed = origin
+ if (processed?.startsWith(HTTPS) == true) { // Removes "https://"
+ processed = processed.substring(HTTPS.length)
+ if (processed?.endsWith(FORWARD_SLASH) == true) { // Removes the trailing slash
+ processed = processed.substring(0, processed.length - 1)
+ }
+ }
+ return processed
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 3fcb7f398185..ffc0479f01c3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -116,10 +116,7 @@ public class InfoMediaManager extends MediaManager {
&& !TextUtils.isEmpty(mPackageName)) {
RouteListingPreference routeListingPreference =
mRouterManager.getRouteListingPreference(mPackageName);
- if (routeListingPreference != null) {
- Api34Impl.onRouteListingPreferenceUpdated(null, routeListingPreference,
- mPreferenceItemMap);
- }
+ Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap);
}
refreshDevices();
}
@@ -631,7 +628,7 @@ public class InfoMediaManager extends MediaManager {
}
/**
- * Ignore callback here since we'll also receive {@link onRequestFailed} with reason code.
+ * Ignore callback here since we'll also receive {@link #onRequestFailed} with reason code.
*/
@Override
public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {
@@ -656,9 +653,9 @@ public class InfoMediaManager extends MediaManager {
public void onRouteListingPreferenceUpdated(
String packageName,
RouteListingPreference routeListingPreference) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- Api34Impl.onRouteListingPreferenceUpdated(packageName, routeListingPreference,
- mPreferenceItemMap);
+ if (TextUtils.equals(mPackageName, packageName)) {
+ Api34Impl.onRouteListingPreferenceUpdated(
+ routeListingPreference, mPreferenceItemMap);
refreshDevices();
}
}
@@ -746,7 +743,6 @@ public class InfoMediaManager extends MediaManager {
@DoNotInline
static void onRouteListingPreferenceUpdated(
- String packageName,
RouteListingPreference routeListingPreference,
Map<String, RouteListingPreference.Item> preferenceItemMap) {
preferenceItemMap.clear();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index aa5f3df1b750..39780f3a96ed 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -73,6 +73,7 @@ import java.util.Set;
public class InfoMediaManagerTest {
private static final String TEST_PACKAGE_NAME = "com.test.packagename";
+ private static final String TEST_PACKAGE_NAME_2 = "com.test.packagename2";
private static final String TEST_ID = "test_id";
private static final String TEST_ID_1 = "test_id_1";
private static final String TEST_ID_2 = "test_id_2";
@@ -308,7 +309,54 @@ public class InfoMediaManagerTest {
}
@Test
- public void onRouteChanged_getAvailableRoutesWithPrefernceListExit_ordersRoutes() {
+ public void onRouteChanged_getAvailableRoutesWithPreferenceListExit_ordersRoutes() {
+ RouteListingPreference routeListingPreference = setUpPreferenceList(TEST_PACKAGE_NAME);
+ setUpSelectedRoutes(TEST_PACKAGE_NAME);
+
+ final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+ final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
+ routingSessionInfos.add(sessionInfo);
+
+ when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
+ when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
+
+ setAvailableRoutesList(TEST_PACKAGE_NAME);
+
+ mInfoMediaManager.mRouterManager = mRouterManager;
+ mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME,
+ routeListingPreference);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
+
+ assertThat(mInfoMediaManager.mMediaDevices).hasSize(3);
+ assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
+ assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4);
+ assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue();
+ assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3);
+ }
+
+ @Test
+ public void onRouteChanged_preferenceListUpdateWithDifferentPkg_notOrdersRoutes() {
+ RouteListingPreference routeListingPreference = setUpPreferenceList(TEST_PACKAGE_NAME_2);
+ setUpSelectedRoutes(TEST_PACKAGE_NAME);
+
+ final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+ final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
+ routingSessionInfos.add(sessionInfo);
+
+ when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
+ when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
+
+ setAvailableRoutesList(TEST_PACKAGE_NAME);
+ mInfoMediaManager.mRouterManager = mRouterManager;
+ mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME_2,
+ routeListingPreference);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
+
+ assertThat(mInfoMediaManager.mMediaDevices).hasSize(1);
+ assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
+ }
+
+ private RouteListingPreference setUpPreferenceList(String packageName) {
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
final List<RouteListingPreference.Item> preferenceItemList = new ArrayList<>();
@@ -324,57 +372,40 @@ public class InfoMediaManagerTest {
RouteListingPreference routeListingPreference =
new RouteListingPreference.Builder().setItems(
preferenceItemList).setUseSystemOrdering(false).build();
- when(mRouterManager.getRouteListingPreference(TEST_PACKAGE_NAME))
+ when(mRouterManager.getRouteListingPreference(packageName))
.thenReturn(routeListingPreference);
+ return routeListingPreference;
+ }
+ private void setUpSelectedRoutes(String packageName) {
final List<MediaRoute2Info> selectedRoutes = new ArrayList<>();
final MediaRoute2Info info = mock(MediaRoute2Info.class);
when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(info.getClientPackageName()).thenReturn(packageName);
when(info.isSystemRoute()).thenReturn(true);
selectedRoutes.add(info);
when(mRouterManager.getSelectedRoutes(any())).thenReturn(selectedRoutes);
-
- final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
- final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
- routingSessionInfos.add(sessionInfo);
-
- when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
- when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
-
- setAvailableRoutesList();
-
- mInfoMediaManager.mRouterManager = mRouterManager;
- mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME,
- routeListingPreference);
- mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
-
- assertThat(mInfoMediaManager.mMediaDevices).hasSize(3);
- assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
- assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4);
- assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue();
- assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3);
}
- private List<MediaRoute2Info> setAvailableRoutesList() {
+ private List<MediaRoute2Info> setAvailableRoutesList(String packageName) {
final List<MediaRoute2Info> availableRoutes = new ArrayList<>();
final MediaRoute2Info availableInfo1 = mock(MediaRoute2Info.class);
when(availableInfo1.getId()).thenReturn(TEST_ID_2);
- when(availableInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(availableInfo1.getClientPackageName()).thenReturn(packageName);
when(availableInfo1.getType()).thenReturn(TYPE_REMOTE_TV);
availableRoutes.add(availableInfo1);
final MediaRoute2Info availableInfo2 = mock(MediaRoute2Info.class);
when(availableInfo2.getId()).thenReturn(TEST_ID_3);
- when(availableInfo2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(availableInfo2.getClientPackageName()).thenReturn(packageName);
availableRoutes.add(availableInfo2);
final MediaRoute2Info availableInfo3 = mock(MediaRoute2Info.class);
when(availableInfo3.getId()).thenReturn(TEST_ID_4);
- when(availableInfo3.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(availableInfo3.getClientPackageName()).thenReturn(packageName);
availableRoutes.add(availableInfo3);
- when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(
+ when(mRouterManager.getAvailableRoutes(packageName)).thenReturn(
availableRoutes);
return availableRoutes;
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 675a7d053fd2..2c0d73a3b682 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -370,7 +370,7 @@
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
tools:replace="android:appComponentFactory"
- android:appComponentFactory=".SystemUIAppComponentFactory">
+ android:appComponentFactory=".PhoneSystemUIAppComponentFactory">
<!-- Keep theme in sync with SystemUIApplication.onCreate().
Setting the theme on the application does not affect views inflated by services.
The application theme is set again from onCreate to take effect for those views. -->
@@ -1005,7 +1005,6 @@
android:name=".notetask.shortcut.LaunchNoteTaskActivity"
android:exported="true"
android:excludeFromRecents="true"
- android:resizeableActivity="false"
android:theme="@android:style/Theme.NoDisplay" >
<intent-filter>
@@ -1018,7 +1017,6 @@
android:name=".notetask.LaunchNotesRoleSettingsTrampolineActivity"
android:exported="true"
android:excludeFromRecents="true"
- android:resizeableActivity="false"
android:theme="@android:style/Theme.NoDisplay" >
<intent-filter>
<action android:name="com.android.systemui.action.MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" />
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 0e20444347fd..8cba2ab0b70b 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -48,6 +48,18 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
private val KEY_TIMESTAMP = "appliedTimestamp"
+private val KNOWN_PLUGINS =
+ mapOf<String, List<ClockMetadata>>(
+ "com.android.systemui.falcon.one" to listOf(ClockMetadata("ANALOG_CLOCK_BIGNUM")),
+ "com.android.systemui.falcon.two" to listOf(ClockMetadata("DIGITAL_CLOCK_CALLIGRAPHY")),
+ "com.android.systemui.falcon.three" to listOf(ClockMetadata("DIGITAL_CLOCK_FLEX")),
+ "com.android.systemui.falcon.four" to listOf(ClockMetadata("DIGITAL_CLOCK_GROWTH")),
+ "com.android.systemui.falcon.five" to listOf(ClockMetadata("DIGITAL_CLOCK_HANDWRITTEN")),
+ "com.android.systemui.falcon.six" to listOf(ClockMetadata("DIGITAL_CLOCK_INFLATE")),
+ "com.android.systemui.falcon.seven" to listOf(ClockMetadata("DIGITAL_CLOCK_METRO")),
+ "com.android.systemui.falcon.eight" to listOf(ClockMetadata("DIGITAL_CLOCK_NUMBEROVERLAP")),
+ "com.android.systemui.falcon.nine" to listOf(ClockMetadata("DIGITAL_CLOCK_WEATHER")),
+ )
private fun <TKey, TVal> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut(
key: TKey,
@@ -127,8 +139,61 @@ open class ClockRegistry(
private val pluginListener =
object : PluginListener<ClockProviderPlugin> {
- override fun onPluginAttached(manager: PluginLifecycleManager<ClockProviderPlugin>) {
- manager.loadPlugin()
+ override fun onPluginAttached(
+ manager: PluginLifecycleManager<ClockProviderPlugin>
+ ): Boolean {
+ if (keepAllLoaded) {
+ // Always load new plugins if requested
+ return true
+ }
+
+ val knownClocks = KNOWN_PLUGINS.get(manager.getPackage())
+ if (knownClocks == null) {
+ logBuffer.tryLog(
+ TAG,
+ LogLevel.WARNING,
+ { str1 = manager.getPackage() },
+ { "Loading unrecognized clock package: $str1" }
+ )
+ return true
+ }
+
+ logBuffer.tryLog(
+ TAG,
+ LogLevel.INFO,
+ { str1 = manager.getPackage() },
+ { "Skipping initial load of known clock package package: $str1" }
+ )
+
+ var isClockListChanged = false
+ for (metadata in knownClocks) {
+ val id = metadata.clockId
+ val info =
+ availableClocks.concurrentGetOrPut(id, ClockInfo(metadata, null, manager)) {
+ isClockListChanged = true
+ onConnected(id)
+ }
+
+ if (manager != info.manager) {
+ logBuffer.tryLog(
+ TAG,
+ LogLevel.ERROR,
+ { str1 = id },
+ { "Clock Id conflict on known attach: $str1 is double registered" }
+ )
+ continue
+ }
+
+ info.provider = null
+ }
+
+ if (isClockListChanged) {
+ triggerOnAvailableClocksChanged()
+ }
+ verifyLoadedProviders()
+
+ // Load executed via verifyLoadedProviders
+ return false
}
override fun onPluginLoaded(
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
index 519ae0a59702..e0506046ee54 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
@@ -148,11 +148,11 @@ interface CustomizationProviderClient {
*/
val isEnabled: Boolean = true,
/**
- * If the affordance is disabled, this is a set of instruction messages to be shown to the
- * user when the disabled affordance is selected. The instructions should help the user
- * figure out what to do in order to re-neable this affordance.
+ * If the affordance is disabled, this is the explanation to be shown to the user when the
+ * disabled affordance is selected. The instructions should help the user figure out what to
+ * do in order to re-neable this affordance.
*/
- val enablementInstructions: List<String>? = null,
+ val enablementExplanation: String? = null,
/**
* If the affordance is disabled, this is a label for a button shown together with the set
* of instruction messages when the disabled affordance is selected. The button should help
@@ -326,10 +326,10 @@ class CustomizationProviderClientImpl(
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
.IS_ENABLED
)
- val enablementInstructionsColumnIndex =
+ val enablementExplanationColumnIndex =
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
- .ENABLEMENT_INSTRUCTIONS
+ .ENABLEMENT_EXPLANATION
)
val enablementActionTextColumnIndex =
cursor.getColumnIndex(
@@ -351,7 +351,7 @@ class CustomizationProviderClientImpl(
nameColumnIndex == -1 ||
iconColumnIndex == -1 ||
isEnabledColumnIndex == -1 ||
- enablementInstructionsColumnIndex == -1 ||
+ enablementExplanationColumnIndex == -1 ||
enablementActionTextColumnIndex == -1 ||
enablementActionIntentColumnIndex == -1 ||
configureIntentColumnIndex == -1
@@ -367,13 +367,8 @@ class CustomizationProviderClientImpl(
name = cursor.getString(nameColumnIndex),
iconResourceId = cursor.getInt(iconColumnIndex),
isEnabled = cursor.getInt(isEnabledColumnIndex) == 1,
- enablementInstructions =
- cursor
- .getString(enablementInstructionsColumnIndex)
- ?.split(
- Contract.LockScreenQuickAffordances.AffordanceTable
- .ENABLEMENT_INSTRUCTIONS_DELIMITER
- ),
+ enablementExplanation =
+ cursor.getString(enablementExplanationColumnIndex),
enablementActionText =
cursor.getString(enablementActionTextColumnIndex),
enablementActionIntent =
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index 7f5fb2580a5b..b6d5ef3720f5 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -81,7 +81,6 @@ object CustomizationProviderContract {
const val TABLE_NAME = "affordances"
val URI: Uri =
LOCK_SCREEN_QUICK_AFFORDANCE_BASE_URI.buildUpon().appendPath(TABLE_NAME).build()
- const val ENABLEMENT_INSTRUCTIONS_DELIMITER = "]["
object Columns {
/** String. Unique ID for this affordance. */
@@ -96,11 +95,10 @@ object CustomizationProviderContract {
/** Integer. `1` if the affordance is enabled or `0` if it disabled. */
const val IS_ENABLED = "is_enabled"
/**
- * String. List of strings, delimited by [ENABLEMENT_INSTRUCTIONS_DELIMITER] to be
- * shown to the user if the affordance is disabled and the user selects the
- * affordance.
+ * String. Text to be shown to the user if the affordance is disabled and the user
+ * selects the affordance.
*/
- const val ENABLEMENT_INSTRUCTIONS = "enablement_instructions"
+ const val ENABLEMENT_EXPLANATION = "enablement_explanation"
/**
* String. Optional label for a button that, when clicked, opens a destination
* activity where the user can re-enable the disabled affordance.
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 3ae328e67e3d..537b7a41a898 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -190,7 +190,9 @@ enum class ClockTickRate(val value: Int) {
data class ClockMetadata(
val clockId: ClockId,
val name: String,
-)
+) {
+ constructor(clockId: ClockId) : this(clockId, clockId) {}
+}
/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
data class ClockConfig(
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
index cc6a46fa7d6b..56c3f93d49b4 100644
--- a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginLifecycleManager.java
@@ -16,12 +16,20 @@
package com.android.systemui.plugins;
+import android.content.ComponentName;
+
/**
* Provides the ability for consumers to control plugin lifecycle.
*
* @param <T> is the target plugin type
*/
public interface PluginLifecycleManager<T extends Plugin> {
+ /** Returns the ComponentName of the target plugin. Maybe be called when not loaded. */
+ ComponentName getComponentName();
+
+ /** Returns the package name of the target plugin. May be called when not loaded. */
+ String getPackage();
+
/** Returns the currently loaded plugin instance (if plugin is loaded) */
T getPlugin();
diff --git a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java
index c5f503216101..bd0bd8942d5e 100644
--- a/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java
+++ b/packages/SystemUI/plugin_core/src/com/android/systemui/plugins/PluginListener.java
@@ -60,13 +60,18 @@ public interface PluginListener<T extends Plugin> {
/**
* Called when the plugin is first attached to the host application. {@link #onPluginLoaded}
- * will be automatically called as well when first attached. This may be called multiple times
- * if multiple plugins are allowed. It may also be called in the future if the plugin package
- * changes and needs to be reloaded. Each call to {@link #onPluginAttached} will provide a new
- * or different {@link PluginLifecycleManager}.
+ * will be automatically called as well when first attached if true is returned. This may be
+ * called multiple times if multiple plugins are allowed. It may also be called in the future
+ * if the plugin package changes and needs to be reloaded. Each call to
+ * {@link #onPluginAttached} will provide a new or different {@link PluginLifecycleManager}.
+ *
+ * @return returning true will immediately load the plugin and call onPluginLoaded with the
+ * created object. false will skip loading, but the listener can load it at any time using the
+ * provided PluginLifecycleManager. Loading plugins immediately is the default behavior.
*/
- default void onPluginAttached(PluginLifecycleManager<T> manager) {
+ default boolean onPluginAttached(PluginLifecycleManager<T> manager) {
// Optional
+ return true;
}
/**
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index a8ed84393cfb..b534fcec2a85 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -1,13 +1,7 @@
-include proguard_common.flags
--keep class com.android.systemui.statusbar.tv.TvStatusBar
-keep class com.android.systemui.SystemUIInitializerImpl {
*;
}
--keep class com.android.systemui.tv.TvSystemUIInitializer {
- *;
-}
-
--keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.DaggerReferenceGlobalRootComponent** { !synthetic *; }
--keep,allowoptimization,allowaccessmodification class com.android.systemui.tv.DaggerTvGlobalRootComponent** { !synthetic *; } \ No newline at end of file
+-keep,allowoptimization,allowaccessmodification class com.android.systemui.dagger.DaggerReferenceGlobalRootComponent** { !synthetic *; } \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 3fc0965f4a81..fc9c917c152b 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -43,7 +43,7 @@
<!-- Not quite optimal but needed to translate these items as a group. The
NotificationIconContainer has its own logic for translation. -->
- <LinearLayout
+ <com.android.keyguard.KeyguardStatusAreaView
android:id="@+id/keyguard_status_area"
android:orientation="vertical"
android:layout_width="match_parent"
@@ -63,5 +63,5 @@
android:paddingStart="@dimen/below_clock_padding_start_icons"
android:visibility="invisible"
/>
- </LinearLayout>
+ </com.android.keyguard.KeyguardStatusAreaView>
</com.android.keyguard.KeyguardClockSwitch>
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/integers.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/integers.xml
index 2a8092010a37..3d1cb5ee6177 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp-land/integers.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/integers.xml
@@ -16,5 +16,5 @@
-->
<resources>
<!-- Invisibility to use for the date & weather view when it is disabled by a clock -->
- <integer name="keyguard_date_weather_view_invisibility">8</integer>
+ <integer name="keyguard_date_weather_view_invisibility">4</integer>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 4b7968953420..39dd90e5ed60 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -148,4 +148,9 @@
<dimen name="default_dot_diameter">34dp</dimen>
<dimen name="default_dot_spacing">0dp</dimen>
+ <!-- Weather clock smartspace scaling to apply for the weather clock -->
+ <item name="weather_clock_smartspace_scale" type="dimen" format="float">1.0</item>
+ <dimen name="weather_clock_smartspace_translateX">0dp</dimen>
+ <dimen name="weather_clock_smartspace_translateY">0dp</dimen>
+
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/ids.xml b/packages/SystemUI/res-keyguard/values/ids.xml
index 0dff4ffa3866..1435907eaef6 100644
--- a/packages/SystemUI/res-keyguard/values/ids.xml
+++ b/packages/SystemUI/res-keyguard/values/ids.xml
@@ -17,4 +17,18 @@
<resources>
<item type="id" name="header_footer_views_added_tag_key" />
+
+ <!-- animation channels for keyguard status area -->
+ <item type="id" name="translate_x_clock_design_animator_tag" />
+ <item type="id" name="translate_x_clock_design_animator_start_tag" />
+ <item type="id" name="translate_x_clock_design_animator_end_tag" />
+ <item type="id" name="translate_x_aod_animator_tag" />
+ <item type="id" name="translate_x_aod_animator_start_tag" />
+ <item type="id" name="translate_x_aod_animator_end_tag" />
+ <item type="id" name="translate_y_clock_size_animator_tag" />
+ <item type="id" name="translate_y_clock_size_animator_start_tag" />
+ <item type="id" name="translate_y_clock_size_animator_end_tag" />
+ <item type="id" name="translate_y_clock_design_animator_tag" />
+ <item type="id" name="translate_y_clock_design_animator_start_tag" />
+ <item type="id" name="translate_y_clock_design_animator_end_tag" />
</resources>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 0ca154e06787..2ace86f919aa 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -20,11 +20,6 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources>
- <!-- SystemUIFactory component -->
- <string name="config_systemUIFactoryComponent" translatable="false">
- com.android.systemui.tv.TvSystemUIInitializer
- </string>
-
<!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
<integer name="recents_svelte_level">3</integer>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 166bd2ac4439..421f41f60d85 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -302,9 +302,6 @@
<!-- Determines whether the shell features all run on another thread. -->
<bool name="config_enableShellMainThread">true</bool>
- <!-- SystemUIFactory component -->
- <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIInitializerImpl</string>
-
<!-- QS tile shape store width. negative implies fill configuration instead of stroke-->
<dimen name="config_qsTileStrokeWidthActive">-1dp</dimen>
<dimen name="config_qsTileStrokeWidthInactive">-1dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 956d3d37bab1..26b824a4c811 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3020,52 +3020,54 @@
-->
<string name="keyguard_affordance_enablement_dialog_action_template">Open <xliff:g id="appName" example="Wallet">%1$s</xliff:g></string>
- <!--
- Requirement for the wallet app to be available for the user to use. This is shown as part of a
- bulleted list of requirements. When all requirements are met, the app can be accessed through a
- shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ <!---
+ Explains that the wallet app is not available because it is not installed. This is shown as part
+ of a dialog that explains to the user why they cannot select this shortcut for their lock screen
+ right now.
+ [CHAR LIMIT=NONE].
-->
- <string name="keyguard_affordance_enablement_dialog_wallet_instruction_1">&#8226; The app is set up</string>
+ <string name="wallet_quick_affordance_unavailable_install_the_app">To add the Wallet app as a shortcut, make sure the app is installed</string>
- <!--
- Requirement for the wallet app to be available for the user to use. This is shown as part of a
- bulleted list of requirements. When all requirements are met, the app can be accessed through a
- shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ <!---
+ Explains that the wallet app is not available because it is not installed. This is shown as part
+ of a dialog that explains to the user why they cannot select this shortcut for their lock screen
+ right now.
+ [CHAR LIMIT=NONE].
-->
- <string name="keyguard_affordance_enablement_dialog_wallet_instruction_2">&#8226; At least one card has been added to Wallet</string>
+ <string name="wallet_quick_affordance_unavailable_configure_the_app">To add the Wallet app as a shortcut, make sure at least one card has been added</string>
<!--
Requirement for the QR code scanner functionality to be available for the user to use. This is
shown as part of a bulleted list of requirements. When all requirements are met, the piece of
functionality can be accessed through a shortcut button on the lock screen. [CHAR LIMIT=NONE].
-->
- <string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction">&#8226; Install a camera app</string>
+ <string name="qr_scanner_quick_affordance_unavailable_explanation">To add the QR code scanner as a shortcut, make sure a camera app is installed</string>
<!--
- Requirement for the home app to be available for the user to use. This is shown as part of a
- bulleted list of requirements. When all requirements are met, the app can be accessed through a
- shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ Explains that the lock screen shortcut for the "home" app is not available because the app isn't
+ installed. This is shown as part of a dialog that explains to the user why they cannot select
+ this shortcut for their lock screen right now. [CHAR LIMIT=NONE].
-->
- <string name="keyguard_affordance_enablement_dialog_home_instruction_1">&#8226; The app is set up</string>
+ <string name="home_quick_affordance_unavailable_install_the_app">To add the Home app as a shortcut, make sure the app is installed</string>
<!--
- Requirement for the home app to be available for the user to use. This is shown as part of a
- bulleted list of requirements. When all requirements are met, the app can be accessed through a
- shortcut button on the lock screen. [CHAR LIMIT=NONE].
+ Explains that the lock screen shortcut for the "home" app is not available because the app isn't
+ configured. This is shown as part of a dialog that explains to the user why they cannot select
+ this shortcut for their lock screen right now. [CHAR LIMIT=NONE].
-->
- <string name="keyguard_affordance_enablement_dialog_home_instruction_2">&#8226; At least one device is available</string>
+ <string name="home_quick_affordance_unavailable_configure_the_app">&#8226; At least one device is available</string>
<!---
- Requirement for the notes app to be available for the user to use. This is shown as part of a
- bulleted list of requirements. When all requirements are met, the app can be accessed through a
- shortcut button on the lock screen. [CHAR LIMIT=NONE] -->
- <string name="keyguard_affordance_enablement_dialog_notes_app_instruction">Select a default notes app to use the notetaking shortcut</string>
+ Explains that the notes app is not available. This is shown as part of a dialog that explains to
+ the user why they cannot select this shortcut for their lock screen right now.
+ [CHAR LIMIT=NONE].
+ -->
+ <string name="notes_app_quick_affordance_unavailable_explanation">Select a default notes app to use the notetaking shortcut</string>
<!---
The action to make the lock screen shortcut for the notes app to be available for the user to
- use. This is shown as the action button in the dialog listing the requirements. When all
- requirements are met, the app can be accessed through a shortcut button on the lock screen.
- [CHAR LIMIT=NONE] -->
+ use. This is shown as the action button in the dialog explaining why the shortcut isn't
+ available. [CHAR LIMIT=NONE] -->
<string name="keyguard_affordance_enablement_dialog_notes_app_action">Select app</string>
<!--
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
index c5979cc50c3c..7a8c82cee32a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimator.kt
@@ -65,8 +65,8 @@ class UnfoldConstantTranslateAnimator(
} else {
1
}
- viewsToTranslate.forEach { (view, direction) ->
- view.get()?.translationX = xTrans * direction.multiplier * rtlMultiplier
+ viewsToTranslate.forEach { (view, direction, func) ->
+ view.get()?.let { func(it, xTrans * direction.multiplier * rtlMultiplier) }
}
}
@@ -77,7 +77,7 @@ class UnfoldConstantTranslateAnimator(
.filter { it.shouldBeAnimated() }
.mapNotNull {
parent.findViewById<View>(it.viewId)?.let { view ->
- ViewToTranslate(WeakReference(view), it.direction)
+ ViewToTranslate(WeakReference(view), it.direction, it.translateFunc)
}
}
.toList()
@@ -91,14 +91,19 @@ class UnfoldConstantTranslateAnimator(
data class ViewIdToTranslate(
val viewId: Int,
val direction: Direction,
- val shouldBeAnimated: () -> Boolean = { true }
+ val shouldBeAnimated: () -> Boolean = { true },
+ val translateFunc: (View, Float) -> Unit = { view, value -> view.translationX = value },
)
/**
* Represents a view whose animation process is in-progress. It should be immutable because the
* started animation should be completed.
*/
- private data class ViewToTranslate(val view: WeakReference<View>, val direction: Direction)
+ private data class ViewToTranslate(
+ val view: WeakReference<View>,
+ val direction: Direction,
+ val translateFunc: (View, Float) -> Unit,
+ )
/** Direction of the animation. */
enum class Direction(val multiplier: Float) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
index 9a9a2426507f..6b67c092890d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginInstance.java
@@ -79,17 +79,26 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
/** Alerts listener and plugin that the plugin has been created. */
public void onCreate() {
- mListener.onPluginAttached(this);
+ boolean loadPlugin = mListener.onPluginAttached(this);
+ if (!loadPlugin) {
+ if (mPlugin != null) {
+ unloadPlugin();
+ }
+ return;
+ }
+
if (mPlugin == null) {
loadPlugin();
- } else {
- if (!(mPlugin instanceof PluginFragment)) {
- // Only call onCreate for plugins that aren't fragments, as fragments
- // will get the onCreate as part of the fragment lifecycle.
- mPlugin.onCreate(mAppContext, mPluginContext);
- }
- mListener.onPluginLoaded(mPlugin, mPluginContext, this);
+ return;
}
+
+ mPluginFactory.checkVersion(mPlugin);
+ if (!(mPlugin instanceof PluginFragment)) {
+ // Only call onCreate for plugins that aren't fragments, as fragments
+ // will get the onCreate as part of the fragment lifecycle.
+ mPlugin.onCreate(mAppContext, mPluginContext);
+ }
+ mListener.onPluginLoaded(mPlugin, mPluginContext, this);
}
/** Alerts listener and plugin that the plugin is being shutdown. */
@@ -118,6 +127,7 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
return;
}
+ mPluginFactory.checkVersion(mPlugin);
if (!(mPlugin instanceof PluginFragment)) {
// Only call onCreate for plugins that aren't fragments, as fragments
// will get the onCreate as part of the fragment lifecycle.
@@ -205,12 +215,8 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
PluginFactory<T> pluginFactory = new PluginFactory<T>(
context, mInstanceFactory, appInfo, componentName, mVersionChecker, pluginClass,
() -> getClassLoader(appInfo, mBaseClassLoader));
- // TODO: Only create the plugin before version check if we need it for
- // legacy version check.
- T instance = pluginFactory.createPlugin();
- pluginFactory.checkVersion(instance);
return new PluginInstance<T>(
- context, listener, componentName, pluginFactory, instance);
+ context, listener, componentName, pluginFactory, null);
}
private boolean isPluginPackagePrivileged(String packageName) {
@@ -332,7 +338,9 @@ public class PluginInstance<T extends Plugin> implements PluginLifecycleManager
ClassLoader loader = mClassLoaderFactory.get();
Class<T> instanceClass = (Class<T>) Class.forName(
mComponentName.getClassName(), true, loader);
- return (T) mInstanceFactory.create(instanceClass);
+ T result = (T) mInstanceFactory.create(instanceClass);
+ Log.v(TAG, "Created plugin: " + result);
+ return result;
} catch (ClassNotFoundException ex) {
Log.e(TAG, "Failed to load plugin", ex);
} catch (IllegalAccessException ex) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index a90a34c560cf..360f755623f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,8 +1,14 @@
package com.android.keyguard;
import static android.view.View.ALPHA;
+import static android.view.View.SCALE_X;
+import static android.view.View.SCALE_Y;
import static android.view.View.TRANSLATION_Y;
+import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_X_CLOCK_DESIGN;
+import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_DESIGN;
+import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_SIZE;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -17,6 +23,7 @@ import android.widget.RelativeLayout;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
+import androidx.core.content.res.ResourcesCompat;
import com.android.app.animation.Interpolators;
import com.android.keyguard.dagger.KeyguardStatusViewScope;
@@ -44,6 +51,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
private static final long STATUS_AREA_START_DELAY_MILLIS = 0;
private static final long STATUS_AREA_MOVE_UP_MILLIS = 967;
private static final long STATUS_AREA_MOVE_DOWN_MILLIS = 467;
+ private static final float SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER = 1.4f;
@IntDef({LARGE, SMALL})
@Retention(RetentionPolicy.SOURCE)
@@ -88,14 +96,18 @@ public class KeyguardClockSwitch extends RelativeLayout {
private KeyguardClockFrame mLargeClockFrame;
private ClockController mClock;
- private View mStatusArea;
+ private KeyguardStatusAreaView mStatusArea;
private int mSmartspaceTopOffset;
+ private float mWeatherClockSmartspaceScaling = 1f;
+ private int mWeatherClockSmartspaceTranslateX = 0;
+ private int mWeatherClockSmartspaceTranslateY = 0;
private int mDrawAlpha = 255;
/**
* Maintain state so that a newly connected plugin can be initialized.
*/
private float mDarkAmount;
+ private boolean mSplitShadeCentered = false;
/**
* Indicates which clock is currently displayed - should be one of {@link ClockSize}.
@@ -105,7 +117,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
@VisibleForTesting AnimatorSet mClockInAnim = null;
@VisibleForTesting AnimatorSet mClockOutAnim = null;
- private AnimatorSet mStatusAreaAnim = null;
+ @VisibleForTesting AnimatorSet mStatusAreaAnim = null;
private int mClockSwitchYAmount;
@VisibleForTesting boolean mChildrenAreLaidOut = false;
@@ -117,13 +129,30 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
/**
- * Apply dp changes on font/scale change
+ * Apply dp changes on configuration change
*/
- public void onDensityOrFontScaleChanged() {
+ public void onConfigChanged() {
mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_clock_switch_y_shift);
mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_smartspace_top_offset);
+ mWeatherClockSmartspaceScaling = ResourcesCompat.getFloat(
+ mContext.getResources(), R.dimen.weather_clock_smartspace_scale);
+ mWeatherClockSmartspaceTranslateX = mContext.getResources().getDimensionPixelSize(
+ R.dimen.weather_clock_smartspace_translateX);
+ mWeatherClockSmartspaceTranslateY = mContext.getResources().getDimensionPixelSize(
+ R.dimen.weather_clock_smartspace_translateY);
+ updateStatusArea(/* animate= */false);
+ }
+
+ /**
+ * Enable or disable split shade specific positioning
+ */
+ public void setSplitShadeCentered(boolean splitShadeCentered) {
+ if (mSplitShadeCentered != splitShadeCentered) {
+ mSplitShadeCentered = splitShadeCentered;
+ updateStatusArea(/* animate= */true);
+ }
}
@Override
@@ -134,7 +163,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
mStatusArea = findViewById(R.id.keyguard_status_area);
- onDensityOrFontScaleChanged();
+ onConfigChanged();
}
@Override
@@ -182,6 +211,13 @@ public class KeyguardClockSwitch extends RelativeLayout {
mSmallClockFrame.addView(clock.getSmallClock().getView());
mLargeClockFrame.addView(clock.getLargeClock().getView());
updateClockTargetRegions();
+ updateStatusArea(/* animate= */false);
+ }
+
+ private void updateStatusArea(boolean animate) {
+ if (mDisplayedClockSize != null && mChildrenAreLaidOut) {
+ updateClockViews(mDisplayedClockSize == LARGE, animate);
+ }
}
void updateClockTargetRegions() {
@@ -230,13 +266,25 @@ public class KeyguardClockSwitch extends RelativeLayout {
mStatusAreaAnim = null;
View in, out;
- float statusAreaYTranslation, clockInYTranslation, clockOutYTranslation;
+ float statusAreaYTranslation, statusAreaClockScale = 1f;
+ float statusAreaClockTranslateX = 0f, statusAreaClockTranslateY = 0f;
+ float clockInYTranslation, clockOutYTranslation;
if (useLargeClock) {
out = mSmallClockFrame;
in = mLargeClockFrame;
if (indexOfChild(in) == -1) addView(in, 0);
statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop()
+ mSmartspaceTopOffset;
+ // TODO: Load from clock config when less risky
+ if (mClock != null
+ && mClock.getLargeClock().getConfig().getHasCustomWeatherDataDisplay()) {
+ statusAreaClockScale = mWeatherClockSmartspaceScaling;
+ statusAreaClockTranslateX = mWeatherClockSmartspaceTranslateX;
+ statusAreaClockTranslateY = mWeatherClockSmartspaceTranslateY;
+ if (mSplitShadeCentered) {
+ statusAreaClockTranslateX *= SMARTSPACE_TRANSLATION_CENTER_MULTIPLIER;
+ }
+ }
clockInYTranslation = 0;
clockOutYTranslation = 0; // Small clock translation is handled with statusArea
} else {
@@ -259,7 +307,12 @@ public class KeyguardClockSwitch extends RelativeLayout {
in.setAlpha(1f);
in.setTranslationY(clockInYTranslation);
in.setVisibility(VISIBLE);
- mStatusArea.setTranslationY(statusAreaYTranslation);
+ mStatusArea.setScaleX(statusAreaClockScale);
+ mStatusArea.setScaleY(statusAreaClockScale);
+ mStatusArea.setTranslateXFromClockDesign(statusAreaClockTranslateX);
+ mStatusArea.setTranslateYFromClockDesign(statusAreaClockTranslateY);
+ mStatusArea.setTranslateYFromClockSize(statusAreaYTranslation);
+ mSmallClockFrame.setTranslationY(statusAreaYTranslation);
return;
}
@@ -301,8 +354,15 @@ public class KeyguardClockSwitch extends RelativeLayout {
useLargeClock ? STATUS_AREA_MOVE_UP_MILLIS : STATUS_AREA_MOVE_DOWN_MILLIS);
mStatusAreaAnim.setInterpolator(Interpolators.EMPHASIZED);
mStatusAreaAnim.playTogether(
- ObjectAnimator.ofFloat(mStatusArea, TRANSLATION_Y, statusAreaYTranslation),
- ObjectAnimator.ofFloat(mSmallClockFrame, TRANSLATION_Y, statusAreaYTranslation));
+ ObjectAnimator.ofFloat(mStatusArea, TRANSLATE_Y_CLOCK_SIZE.getProperty(),
+ statusAreaYTranslation),
+ ObjectAnimator.ofFloat(mSmallClockFrame, TRANSLATION_Y, statusAreaYTranslation),
+ ObjectAnimator.ofFloat(mStatusArea, SCALE_X, statusAreaClockScale),
+ ObjectAnimator.ofFloat(mStatusArea, SCALE_Y, statusAreaClockScale),
+ ObjectAnimator.ofFloat(mStatusArea, TRANSLATE_X_CLOCK_DESIGN.getProperty(),
+ statusAreaClockTranslateX),
+ ObjectAnimator.ofFloat(mStatusArea, TRANSLATE_Y_CLOCK_DESIGN.getProperty(),
+ statusAreaClockTranslateY));
mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
if (mStatusAreaAnim == animation) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 25ad3af52d04..62a2f90a0378 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -330,10 +330,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
/**
- * Apply dp changes on font/scale change
+ * Apply dp changes on configuration change
*/
- public void onDensityOrFontScaleChanged() {
- mView.onDensityOrFontScaleChanged();
+ public void onConfigChanged() {
+ mView.onConfigChanged();
mKeyguardSmallClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
mKeyguardLargeClockTopMargin =
@@ -344,6 +344,12 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
setDateWeatherVisibility();
}
+ /**
+ * Enable or disable split shade center specific positioning
+ */
+ public void setSplitShadeCentered(boolean splitShadeCentered) {
+ mView.setSplitShadeCentered(splitShadeCentered);
+ }
/**
* Set which clock should be displayed on the keyguard. The other one will be automatically
@@ -407,7 +413,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
scale, props, animate);
if (mStatusArea != null) {
- PropertyAnimator.setProperty(mStatusArea, AnimatableProperty.TRANSLATION_X,
+ PropertyAnimator.setProperty(mStatusArea, KeyguardStatusAreaView.TRANSLATE_X_AOD,
x, props, animate);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusAreaView.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusAreaView.kt
new file mode 100644
index 000000000000..e7da2b977379
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusAreaView.kt
@@ -0,0 +1,118 @@
+package com.android.keyguard
+
+import android.content.Context
+import android.util.AttributeSet
+import android.util.FloatProperty
+import android.widget.LinearLayout
+import com.android.systemui.R
+import com.android.systemui.statusbar.notification.AnimatableProperty
+
+class KeyguardStatusAreaView(
+ context: Context,
+ attrs: AttributeSet? = null,
+) : LinearLayout(context, attrs) {
+ var translateXFromClockDesign = 0f
+ get() = field
+ set(value) {
+ field = value
+ translationX = translateXFromAod + translateXFromClockDesign + translateXFromUnfold
+ }
+
+ var translateXFromAod = 0f
+ get() = field
+ set(value) {
+ field = value
+ translationX = translateXFromAod + translateXFromClockDesign + translateXFromUnfold
+ }
+
+ var translateXFromUnfold = 0F
+ get() = field
+ set(value) {
+ field = value
+ translationX = translateXFromAod + translateXFromClockDesign + translateXFromUnfold
+ }
+
+ var translateYFromClockSize = 0f
+ get() = field
+ set(value) {
+ field = value
+ translationY = value + translateYFromClockDesign
+ }
+
+ var translateYFromClockDesign = 0f
+ get() = field
+ set(value) {
+ field = value
+ translationY = value + translateYFromClockSize
+ }
+
+ companion object {
+ @JvmField
+ val TRANSLATE_X_CLOCK_DESIGN =
+ AnimatableProperty.from(
+ object : FloatProperty<KeyguardStatusAreaView>("TranslateXClockDesign") {
+ override fun setValue(view: KeyguardStatusAreaView, value: Float) {
+ view.translateXFromClockDesign = value
+ }
+
+ override fun get(view: KeyguardStatusAreaView): Float {
+ return view.translateXFromClockDesign
+ }
+ },
+ R.id.translate_x_clock_design_animator_tag,
+ R.id.translate_x_clock_design_animator_start_tag,
+ R.id.translate_x_clock_design_animator_end_tag
+ )
+
+ @JvmField
+ val TRANSLATE_X_AOD =
+ AnimatableProperty.from(
+ object : FloatProperty<KeyguardStatusAreaView>("TranslateXAod") {
+ override fun setValue(view: KeyguardStatusAreaView, value: Float) {
+ view.translateXFromAod = value
+ }
+
+ override fun get(view: KeyguardStatusAreaView): Float {
+ return view.translateXFromAod
+ }
+ },
+ R.id.translate_x_aod_animator_tag,
+ R.id.translate_x_aod_animator_start_tag,
+ R.id.translate_x_aod_animator_end_tag
+ )
+
+ @JvmField
+ val TRANSLATE_Y_CLOCK_SIZE =
+ AnimatableProperty.from(
+ object : FloatProperty<KeyguardStatusAreaView>("TranslateYClockSize") {
+ override fun setValue(view: KeyguardStatusAreaView, value: Float) {
+ view.translateYFromClockSize = value
+ }
+
+ override fun get(view: KeyguardStatusAreaView): Float {
+ return view.translateYFromClockSize
+ }
+ },
+ R.id.translate_y_clock_size_animator_tag,
+ R.id.translate_y_clock_size_animator_start_tag,
+ R.id.translate_y_clock_size_animator_end_tag
+ )
+
+ @JvmField
+ val TRANSLATE_Y_CLOCK_DESIGN =
+ AnimatableProperty.from(
+ object : FloatProperty<KeyguardStatusAreaView>("TranslateYClockDesign") {
+ override fun setValue(view: KeyguardStatusAreaView, value: Float) {
+ view.translateYFromClockDesign = value
+ }
+
+ override fun get(view: KeyguardStatusAreaView): Float {
+ return view.translateYFromClockDesign
+ }
+ },
+ R.id.translate_y_clock_design_animator_tag,
+ R.id.translate_y_clock_design_animator_start_tag,
+ R.id.translate_y_clock_design_animator_end_tag
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 835cc13bebb1..00500d617766 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -24,6 +24,7 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CL
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
+import android.content.res.Configuration;
import android.graphics.Rect;
import android.transition.ChangeBounds;
import android.transition.Transition;
@@ -280,8 +281,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
@Override
- public void onDensityOrFontScaleChanged() {
- mKeyguardClockSwitchController.onDensityOrFontScaleChanged();
+ public void onConfigChanged(Configuration newConfig) {
+ mKeyguardClockSwitchController.onConfigChanged();
}
};
@@ -329,6 +330,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
boolean splitShadeEnabled,
boolean shouldBeCentered,
boolean animate) {
+ mKeyguardClockSwitchController.setSplitShadeCentered(splitShadeEnabled && shouldBeCentered);
if (mStatusViewCentered == shouldBeCentered) {
return;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index edd150c293f6..ca64ae059093 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -53,7 +53,10 @@ constructor(
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
setOf(
- ViewIdToTranslate(R.id.keyguard_status_area, START, filterKeyguard),
+ ViewIdToTranslate(R.id.keyguard_status_area, START, filterKeyguard,
+ { view, value ->
+ (view as? KeyguardStatusAreaView)?.translateXFromUnfold = value
+ }),
ViewIdToTranslate(
R.id.lockscreen_clock_view_large, START, filterKeyguardAndSplitShadeOnly),
ViewIdToTranslate(R.id.lockscreen_clock_view, START, filterKeyguard),
diff --git a/packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt b/packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt
new file mode 100644
index 000000000000..f06cb4104e1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/PhoneSystemUIAppComponentFactory.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.content.Context
+
+class PhoneSystemUIAppComponentFactory : SystemUIAppComponentFactoryBase() {
+ override fun createSystemUIInitializer(context: Context) = SystemUIInitializerImpl(context)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
deleted file mode 100644
index 527ce127820e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ /dev/null
@@ -1,37 +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;
-
-import android.content.Context;
-
-/**
- * Starts up SystemUI using the AOSP {@link SystemUIInitializerImpl}.
- *
- * This initializer relies on reflection to start everything up and should be considered deprecated.
- * Instead, create your own {@link SystemUIAppComponentFactoryBase}, specify it in your
- * AndroidManifest.xml and construct your own {@link SystemUIInitializer} directly.
- *
- * @deprecated Define your own SystemUIAppComponentFactoryBase implementation and use that. This
- * implementation may be changed or removed in future releases.
- */
-@Deprecated
-public class SystemUIAppComponentFactory extends SystemUIAppComponentFactoryBase {
- @Override
- protected SystemUIInitializer createSystemUIInitializer(Context context) {
- return SystemUIInitializerFactory.createWithContext(context);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 70c39df2a610..453d1d117d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -54,7 +54,7 @@ import javax.inject.Provider;
* Application class for SystemUI.
*/
public class SystemUIApplication extends Application implements
- SystemUIAppComponentFactory.ContextInitializer {
+ SystemUIAppComponentFactoryBase.ContextInitializer {
public static final String TAG = "SystemUIService";
private static final boolean DEBUG = false;
@@ -66,7 +66,7 @@ public class SystemUIApplication extends Application implements
*/
private CoreStartable[] mServices;
private boolean mServicesStarted;
- private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
+ private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
private SysUIComponent mSysUIComponent;
private SystemUIInitializer mInitializer;
@@ -366,7 +366,7 @@ public class SystemUIApplication extends Application implements
@Override
public void setContextAvailableCallback(
- SystemUIAppComponentFactory.ContextAvailableCallback callback) {
+ SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) {
mContextAvailableCallback = callback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt
deleted file mode 100644
index b9454e8c3be8..000000000000
--- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt
+++ /dev/null
@@ -1,72 +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
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.util.Log
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.util.Assert
-
-/**
- * Factory to reflectively lookup a [SystemUIInitializer] to start SystemUI with.
- */
-@Deprecated("Provide your own {@link SystemUIAppComponentFactoryBase} that doesn't need this.")
-object SystemUIInitializerFactory {
- private const val TAG = "SysUIInitializerFactory"
- @SuppressLint("StaticFieldLeak")
- private var initializer: SystemUIInitializer? = null
-
- /**
- * Instantiate a [SystemUIInitializer] reflectively.
- */
- @JvmStatic
- fun createWithContext(context: Context): SystemUIInitializer {
- return createFromConfig(context)
- }
-
- /**
- * Instantiate a [SystemUIInitializer] reflectively.
- */
- @JvmStatic
- private fun createFromConfig(context: Context): SystemUIInitializer {
- Assert.isMainThread()
-
- return createFromConfigNoAssert(context)
- }
-
- @JvmStatic
- @VisibleForTesting
- fun createFromConfigNoAssert(context: Context): SystemUIInitializer {
-
- return initializer ?: run {
- val className = context.getString(R.string.config_systemUIFactoryComponent)
- if (className.isEmpty()) {
- throw RuntimeException("No SystemUIFactory component configured")
- }
- try {
- val cls = context.classLoader.loadClass(className)
- val constructor = cls.getConstructor(Context::class.java)
- (constructor.newInstance(context) as SystemUIInitializer).apply {
- initializer = this
- }
- } catch (t: Throwable) {
- Log.w(TAG, "Error creating SystemUIInitializer component: $className", t)
- throw t
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index d976ead1f16f..e58876a2632c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -242,15 +242,17 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
private boolean isOwnerInForeground() {
- final String clientPackage = mCurrentDialog.getOpPackageName();
- final List<ActivityManager.RunningTaskInfo> runningTasks =
- mActivityTaskManager.getTasks(1);
- if (!runningTasks.isEmpty()) {
- final String topPackage = runningTasks.get(0).topActivity.getPackageName();
- if (!topPackage.contentEquals(clientPackage)
- && !Utils.isSystem(mContext, clientPackage)) {
- Log.w(TAG, "Evicting client due to: " + topPackage);
- return false;
+ if (mCurrentDialog != null) {
+ final String clientPackage = mCurrentDialog.getOpPackageName();
+ final List<ActivityManager.RunningTaskInfo> runningTasks =
+ mActivityTaskManager.getTasks(1);
+ if (!runningTasks.isEmpty()) {
+ final String topPackage = runningTasks.get(0).topActivity.getPackageName();
+ if (!topPackage.contentEquals(clientPackage)
+ && !Utils.isSystem(mContext, clientPackage)) {
+ Log.w(TAG, "Evicting client due to: " + topPackage);
+ return false;
+ }
}
}
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
index a2840fc1dc8c..056d692a3641 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
@@ -103,6 +103,13 @@ public class UdfpsKeyguardViewLegacy extends UdfpsAnimationView {
}
@Override
+ void onSensorRectUpdated(RectF bounds) {
+ super.onSensorRectUpdated(bounds);
+ bounds.round(this.mSensorBounds);
+ postInvalidate();
+ }
+
+ @Override
void onDisplayConfiguring() {
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1dffa80a084f..1a286cf57982 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -28,6 +28,7 @@ import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import androidx.core.animation.addListener
import androidx.core.view.doOnLayout
+import androidx.core.view.isGone
import androidx.lifecycle.lifecycleScope
import com.android.systemui.R
import com.android.systemui.biometrics.AuthDialog
@@ -78,9 +79,11 @@ object BiometricViewSizeBinder {
// cache the original position of the icon view (as done in legacy view)
// this must happen before any size changes can be made
- var iconHolderOriginalY = 0f
view.doOnLayout {
- iconHolderOriginalY = iconHolderView.y
+ // TODO(b/251476085): this old way of positioning has proven itself unreliable
+ // remove this and associated thing like (UdfpsDialogMeasureAdapter) and
+ // pin to the physical sensor
+ val iconHolderOriginalY = iconHolderView.y
// bind to prompt
// TODO(b/251476085): migrate the legacy panel controller and simplify this
@@ -141,7 +144,11 @@ object BiometricViewSizeBinder {
listOf(
iconHolderView.asVerticalAnimator(
duration = duration.toLong(),
- toY = iconHolderOriginalY,
+ toY =
+ iconHolderOriginalY -
+ viewsToHideWhenSmall
+ .filter { it.isGone }
+ .sumOf { it.height },
),
viewsToFadeInOnSizeChange.asFadeInAnimator(
duration = duration.toLong(),
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 18bd46756660..ccbde011334d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -312,6 +312,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
Log.d(TAG, "Canceling loadSubscribtion")
it.invoke()
}
+ callback.error("Load cancelled")
}
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index b71871ebdb55..d70c57fda2e1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,12 +16,11 @@
package com.android.systemui.dagger;
-import android.content.Context;
import android.os.HandlerThread;
import androidx.annotation.Nullable;
-import com.android.systemui.SystemUIInitializerFactory;
+import com.android.systemui.SystemUIInitializer;
import com.android.systemui.tv.TvWMComponent;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
@@ -49,7 +48,7 @@ import java.util.Optional;
/**
* Dagger Subcomponent for WindowManager. This class explicitly describes the interfaces exported
* from the WM component into the SysUI component (in
- * {@link SystemUIInitializerFactory#init(Context, boolean)}), and references the specific dependencies
+ * {@link SystemUIInitializer#init(boolean)}), and references the specific dependencies
* provided by its particular device/form-factor SystemUI implementation.
*
* ie. {@link WMComponent} includes {@link WMShellModule}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index e5bc4c9e1b23..951e104e6f9b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -598,7 +598,7 @@ object Flags {
// 1300 - screenshots
// TODO(b/264916608): Tracking Bug
- @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata", teamfood = true)
+ @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata")
// TODO(b/266955521): Tracking bug
@JvmField val SCREENSHOT_DETECTION = releasedFlag(1303, "screenshot_detection")
@@ -713,15 +713,10 @@ object Flags {
val KEYBOARD_EDUCATION =
unreleasedFlag(2603, "keyboard_education", teamfood = false)
- // TODO(b/272036292): Tracking Bug
- @JvmField
- val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
- releasedFlag(2602, "large_shade_granular_alpha_interpolation")
-
// TODO(b/277201412): Tracking Bug
@JvmField
val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION =
- unreleasedFlag(2805, "split_shade_subpixel_optimization", teamfood = true)
+ releasedFlag(2805, "split_shade_subpixel_optimization")
// TODO(b/278761837): Tracking Bug
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index 064a44a29d31..bc0713952ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -283,7 +283,7 @@ class CustomizationProvider :
Contract.LockScreenQuickAffordances.AffordanceTable.Columns.ICON,
Contract.LockScreenQuickAffordances.AffordanceTable.Columns.IS_ENABLED,
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
- .ENABLEMENT_INSTRUCTIONS,
+ .ENABLEMENT_EXPLANATION,
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
.ENABLEMENT_ACTION_TEXT,
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
@@ -299,10 +299,7 @@ class CustomizationProvider :
representation.name,
representation.iconResourceId,
if (representation.isEnabled) 1 else 0,
- representation.instructions?.joinToString(
- Contract.LockScreenQuickAffordances.AffordanceTable
- .ENABLEMENT_INSTRUCTIONS_DELIMITER
- ),
+ representation.explanation,
representation.actionText,
representation.actionIntent?.toUri(Intent.URI_INTENT_SCHEME),
representation.configureIntent?.toUri(Intent.URI_INTENT_SCHEME),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 573de9737eb8..051131433143 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -49,7 +49,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.R;
-import com.android.systemui.SystemUIAppComponentFactory;
+import com.android.systemui.SystemUIAppComponentFactoryBase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -72,13 +72,13 @@ import javax.inject.Inject;
/**
* Simple Slice provider that shows the current date.
*
- * Injection is handled by {@link SystemUIAppComponentFactory} +
+ * Injection is handled by {@link SystemUIAppComponentFactoryBase} +
* {@link com.android.systemui.dagger.GlobalRootComponent#inject(KeyguardSliceProvider)}.
*/
public class KeyguardSliceProvider extends SliceProvider implements
NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback,
NotificationMediaManager.MediaListener, StatusBarStateController.StateListener,
- SystemUIAppComponentFactory.ContextInitializer {
+ SystemUIAppComponentFactoryBase.ContextInitializer {
private static final String TAG = "KgdSliceProvider";
@@ -148,7 +148,7 @@ public class KeyguardSliceProvider extends SliceProvider implements
protected boolean mDozing;
private int mStatusBarState;
private boolean mMediaIsVisible;
- private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
+ private SystemUIAppComponentFactoryBase.ContextAvailableCallback mContextAvailableCallback;
@Inject
WakeLockLogger mWakeLockLogger;
@@ -533,7 +533,7 @@ public class KeyguardSliceProvider extends SliceProvider implements
@Override
public void setContextAvailableCallback(
- SystemUIAppComponentFactory.ContextAvailableCallback callback) {
+ SystemUIAppComponentFactoryBase.ContextAvailableCallback callback) {
mContextAvailableCallback = callback;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index 0991b5f149db..f3fc8096f5d7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -83,18 +83,31 @@ constructor(
currentServices.isNullOrEmpty() && !componentPackageName.isNullOrEmpty() -> {
// No home app installed but we know which app we want to install.
return disabledPickerState(
+ explanation =
+ context.getString(
+ R.string.home_quick_affordance_unavailable_install_the_app
+ ),
actionText = context.getString(R.string.install_app),
actionIntent = appStoreIntent(context, componentPackageName),
)
}
currentServices.isNullOrEmpty() && componentPackageName.isNullOrEmpty() -> {
// No home app installed and we don't know which app we want to install.
- return disabledPickerState()
+ return disabledPickerState(
+ explanation =
+ context.getString(
+ R.string.home_quick_affordance_unavailable_install_the_app
+ ),
+ )
}
!hasFavorites -> {
// Home app installed but no favorites selected.
val activityClass = component.getControlsUiController().get().resolveActivity()
return disabledPickerState(
+ explanation =
+ context.getString(
+ R.string.home_quick_affordance_unavailable_configure_the_app
+ ),
actionText = context.getString(R.string.controls_open_app),
actionIntent =
Intent().apply {
@@ -188,21 +201,14 @@ constructor(
}
private fun disabledPickerState(
+ explanation: String,
actionText: String? = null,
actionIntent: Intent? = null,
): KeyguardQuickAffordanceConfig.PickerScreenState.Disabled {
check(actionIntent == null || actionText != null)
return KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
- instructions =
- listOf(
- context.getString(
- R.string.keyguard_affordance_enablement_dialog_home_instruction_1
- ),
- context.getString(
- R.string.keyguard_affordance_enablement_dialog_home_instruction_2
- ),
- ),
+ explanation = explanation,
actionText = actionText,
actionIntent = actionIntent,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 42fe9bcf8ec4..320d158fb13e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -87,8 +87,8 @@ interface KeyguardQuickAffordanceConfig {
* described in the instructions.
*/
data class Disabled(
- /** List of human-readable instructions for setting up the quick affordance. */
- val instructions: List<String>,
+ /** Human-readable explanation as to why the quick affordance is current disabled. */
+ val explanation: String,
/**
* Optional text to display on a button that the user can click to start a flow to go
* and set up the quick affordance and make it enabled.
@@ -101,7 +101,7 @@ interface KeyguardQuickAffordanceConfig {
val actionIntent: Intent? = null,
) : PickerScreenState() {
init {
- check(instructions.isNotEmpty()) { "Instructions must not be empty!" }
+ check(explanation.isNotEmpty()) { "Explanation must not be empty!" }
check(
(actionText.isNullOrEmpty() && actionIntent == null) ||
(!actionText.isNullOrEmpty() && actionIntent != null)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index 0d54ab9adb4a..20ed549e42d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -80,16 +80,14 @@ constructor(
return when {
!controller.isAvailableOnDevice ->
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
- !controller.isAbleToOpenCameraApp ->
+ !controller.isAbleToOpenCameraApp -> {
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
- instructions =
- listOf(
- context.getString(
- R.string
- .keyguard_affordance_enablement_dialog_qr_scanner_instruction
- ),
+ explanation =
+ context.getString(
+ R.string.qr_scanner_quick_affordance_unavailable_explanation
),
)
+ }
else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 9db3c22dff84..c019d21e00ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -100,19 +100,20 @@ constructor(
return when {
!walletController.walletClient.isWalletServiceAvailable ->
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
- !isWalletAvailable() || queryCards().isEmpty() -> {
+ !isWalletAvailable() ->
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
- instructions =
- listOf(
- context.getString(
- R.string.keyguard_affordance_enablement_dialog_wallet_instruction_1
- ),
- context.getString(
- R.string.keyguard_affordance_enablement_dialog_wallet_instruction_2
- ),
+ explanation =
+ context.getString(
+ R.string.wallet_quick_affordance_unavailable_install_the_app
+ ),
+ )
+ queryCards().isEmpty() ->
+ KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
+ explanation =
+ context.getString(
+ R.string.wallet_quick_affordance_unavailable_configure_the_app
),
)
- }
else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default()
}
}
@@ -147,9 +148,7 @@ constructor(
}
private fun isWalletAvailable() =
- with(walletController.walletClient) {
- isWalletServiceAvailable && isWalletFeatureAvailable
- }
+ with(walletController.walletClient) { isWalletServiceAvailable && isWalletFeatureAvailable }
private fun state(
isFeatureEnabled: Boolean,
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 bd3b83c00f57..34f6b4d74f2b 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
@@ -175,7 +175,7 @@ constructor(
iconResourceId = config.pickerIconResourceId,
isEnabled =
pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.Default,
- instructions = disabledPickerState?.instructions,
+ explanation = disabledPickerState?.explanation,
actionText = disabledPickerState?.actionText,
actionIntent =
disabledPickerState?.actionIntent?.apply {
@@ -191,6 +191,7 @@ constructor(
},
)
}
+ .sortedBy { it.name }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
index 3c96eafe867a..c6320dee030a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
@@ -32,8 +32,8 @@ data class KeyguardQuickAffordancePickerRepresentation(
/** Whether this quick affordance is enabled. */
val isEnabled: Boolean = true,
- /** If not enabled, the list of user-visible steps to re-enable it. */
- val instructions: List<String>? = null,
+ /** If not enabled, a user-visible explanation as to why. */
+ val explanation: String? = null,
/**
* If not enabled, an optional label for a button that takes the user to a destination where
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 14386c1c0fd6..0819d0d36e9a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -248,9 +248,9 @@ public class MediaControlPanel {
private final FeatureFlags mFeatureFlags;
private final GlobalSettings mGlobalSettings;
- // TODO(b/281032715): Consider making this as a final variable. For now having a null check
- // due to unit test failure. (Perhaps missing some setup)
private TurbulenceNoiseAnimationConfig mTurbulenceNoiseAnimationConfig;
+ private boolean mWasPlaying = false;
+ private boolean mButtonClicked = false;
private ContentObserver mAnimationScaleObserver = new ContentObserver(null) {
@Override
@@ -582,6 +582,25 @@ public class MediaControlPanel {
if (!mMetadataAnimationHandler.isRunning()) {
mMediaViewController.refreshState();
}
+
+ // Turbulence noise
+ if (shouldPlayTurbulenceNoise()) {
+ if (mTurbulenceNoiseAnimationConfig == null) {
+ mTurbulenceNoiseAnimationConfig =
+ createTurbulenceNoiseAnimation();
+ }
+ // Color will be correctly updated in ColorSchemeTransition.
+ mTurbulenceNoiseController.play(
+ mTurbulenceNoiseAnimationConfig
+ );
+ mMainExecutor.executeDelayed(
+ mTurbulenceNoiseController::finish,
+ TURBULENCE_NOISE_PLAY_DURATION
+ );
+ }
+ mButtonClicked = false;
+ mWasPlaying = isPlaying();
+
Trace.endSection();
}
@@ -1155,21 +1174,14 @@ public class MediaControlPanel {
if (!mFalsingManager.isFalseTap(FalsingManager.MODERATE_PENALTY)) {
mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
+ // Used to determine whether to play turbulence noise.
+ mWasPlaying = isPlaying();
+ mButtonClicked = true;
+
action.run();
+
if (mFeatureFlags.isEnabled(Flags.UMO_SURFACE_RIPPLE)) {
mMultiRippleController.play(createTouchRippleAnimation(button));
- if (mFeatureFlags.isEnabled(Flags.UMO_TURBULENCE_NOISE)) {
- if (mTurbulenceNoiseAnimationConfig == null) {
- mTurbulenceNoiseAnimationConfig =
- createTurbulenceNoiseAnimation();
- }
- // Color will be correctly updated in ColorSchemeTransition.
- mTurbulenceNoiseController.play(mTurbulenceNoiseAnimationConfig);
- mMainExecutor.executeDelayed(
- mTurbulenceNoiseController::finish,
- TURBULENCE_NOISE_PLAY_DURATION
- );
- }
}
if (icon instanceof Animatable) {
@@ -1208,6 +1220,11 @@ public class MediaControlPanel {
);
}
+ private boolean shouldPlayTurbulenceNoise() {
+ return mFeatureFlags.isEnabled(Flags.UMO_TURBULENCE_NOISE) && mButtonClicked && !mWasPlaying
+ && isPlaying();
+ }
+
private TurbulenceNoiseAnimationConfig createTurbulenceNoiseAnimation() {
return new TurbulenceNoiseAnimationConfig(
/* gridCount= */ 2.14f,
@@ -1218,12 +1235,12 @@ public class MediaControlPanel {
/* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
/* backgroundColor= */ Color.BLACK,
/* opacity= */ 51,
- /* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
- /* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
+ /* width= */ mMediaViewHolder.getTurbulenceNoiseView().getWidth(),
+ /* height= */ mMediaViewHolder.getTurbulenceNoiseView().getHeight(),
TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
/* easeInDuration= */ 1350f,
/* easeOutDuration= */ 1350f,
- this.getContext().getResources().getDisplayMetrics().density,
+ getContext().getResources().getDisplayMetrics().density,
BlendMode.SCREEN,
/* onAnimationEnd= */ null,
/* lumaMatteBlendFactor= */ 0.26f,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index efbec29bcff1..48790c23e688 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -46,7 +46,6 @@ import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolder
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
-import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.settings.SecureSettings
import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
@@ -219,7 +218,7 @@ constructor(
debugLog { "onShowNoteTask - opened as app bubble: $info" }
}
is NoteTaskLaunchMode.Activity -> {
- if (activityManager.isInForeground(info.packageName)) {
+ if (info.isKeyguardLocked && activityManager.isInForeground(info.packageName)) {
// Force note task into background by calling home.
val intent = createHomeIntent()
context.startActivityAsUser(intent, user)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
index fae325cc3147..442000281862 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
@@ -25,12 +25,14 @@ import com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity
* An entry point represents where the note task has ben called from. In rare cases, it may
* represent a "re-entry" (i.e., [APP_CLIPS]).
*/
-enum class
-NoteTaskEntryPoint {
+enum class NoteTaskEntryPoint {
/** @see [LaunchNoteTaskActivity] */
WIDGET_PICKER_SHORTCUT,
+ /** @see [LaunchNoteTaskActivity] */
+ WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE,
+
/** @see [NoteTaskQuickAffordanceConfig] */
QUICK_AFFORDANCE,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
index 48a5933a6030..a79057e5464b 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt
@@ -22,6 +22,8 @@ import com.android.systemui.notetask.NoteTaskEntryPoint.KEYBOARD_SHORTCUT
import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
+import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
+import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent
import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_SHORTCUT
import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
@@ -41,40 +43,45 @@ class NoteTaskEventLogger @Inject constructor(private val uiEventLogger: UiEvent
/** Logs a [NoteTaskInfo] as an **open** [NoteTaskUiEvent], including package name and uid. */
fun logNoteTaskOpened(info: NoteTaskInfo) {
val event =
- when (info.entryPoint) {
- TAIL_BUTTON -> {
- if (info.isKeyguardLocked) {
- NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
- } else {
- NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+ when (info.entryPoint) {
+ TAIL_BUTTON -> {
+ if (info.isKeyguardLocked) {
+ NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+ } else {
+ NOTE_OPENED_VIA_STYLUS_TAIL_BUTTON
+ }
}
+
+ WIDGET_PICKER_SHORTCUT,
+ WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE -> NOTE_OPENED_VIA_SHORTCUT
+
+ QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
+ APP_CLIPS,
+ KEYBOARD_SHORTCUT,
+ null -> return
}
- WIDGET_PICKER_SHORTCUT -> NOTE_OPENED_VIA_SHORTCUT
- QUICK_AFFORDANCE -> NOTE_OPENED_VIA_KEYGUARD_QUICK_AFFORDANCE
- APP_CLIPS -> return
- KEYBOARD_SHORTCUT -> return
- null -> return
- }
uiEventLogger.log(event, info.uid, info.packageName)
}
/** Logs a [NoteTaskInfo] as a **closed** [NoteTaskUiEvent], including package name and uid. */
fun logNoteTaskClosed(info: NoteTaskInfo) {
val event =
- when (info.entryPoint) {
- TAIL_BUTTON -> {
- if (info.isKeyguardLocked) {
- NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
- } else {
- NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
+ when (info.entryPoint) {
+ TAIL_BUTTON -> {
+ if (info.isKeyguardLocked) {
+ NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON_LOCKED
+ } else {
+ NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON
+ }
}
+
+ WIDGET_PICKER_SHORTCUT,
+ WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE,
+ QUICK_AFFORDANCE,
+ APP_CLIPS,
+ KEYBOARD_SHORTCUT,
+ null -> return
}
- WIDGET_PICKER_SHORTCUT -> return
- QUICK_AFFORDANCE -> return
- APP_CLIPS -> return
- KEYBOARD_SHORTCUT -> return
- null -> return
- }
uiEventLogger.log(event, info.uid, info.packageName)
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
index a75834760d30..269eb870686c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfo.kt
@@ -16,6 +16,7 @@
package com.android.systemui.notetask
import android.os.UserHandle
+import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
/** Contextual information required to launch a Note Task by [NoteTaskController]. */
data class NoteTaskInfo(
@@ -27,7 +28,7 @@ data class NoteTaskInfo(
) {
val launchMode: NoteTaskLaunchMode =
- if (isKeyguardLocked) {
+ if (isKeyguardLocked || entryPoint == WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE) {
NoteTaskLaunchMode.Activity
} else {
NoteTaskLaunchMode.AppBubble
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
index a2ebeadf28b2..4d30634cda38 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -67,7 +67,8 @@ internal object NoteTaskRoleManagerExt {
val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
return ShortcutInfo.Builder(context, NoteTaskController.SHORTCUT_ID)
- .setIntent(LaunchNoteTaskActivity.newIntent(context = context))
+ .setIntent(LaunchNoteTaskActivity.createIntent(context))
+ .setActivity(LaunchNoteTaskActivity.createComponent(context))
.setShortLabel(shortLabel)
.setLongLabel(longLabel)
.setLongLived(true)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index 8da7cec41dd7..f02d362501d5 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -122,17 +122,18 @@ constructor(
isEnabled && isDefaultNotesAppSet -> PickerScreenState.Default()
isEnabled -> {
PickerScreenState.Disabled(
- listOf(
+ explanation =
context.getString(
- R.string.keyguard_affordance_enablement_dialog_notes_app_instruction
- )
- ),
- context.getString(
- R.string.keyguard_affordance_enablement_dialog_notes_app_action
- ),
- Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE).apply {
- setPackage(context.packageName)
- }
+ R.string.notes_app_quick_affordance_unavailable_explanation
+ ),
+ actionText =
+ context.getString(
+ R.string.keyguard_affordance_enablement_dialog_notes_app_action
+ ),
+ actionIntent =
+ Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE).apply {
+ setPackage(context.packageName)
+ },
)
}
else -> PickerScreenState.UnavailableOnDevice
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 493330af56ce..d00a79e6d2bc 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -16,6 +16,7 @@
package com.android.systemui.notetask.shortcut
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
@@ -30,18 +31,27 @@ class LaunchNoteTaskActivity @Inject constructor(private val controller: NoteTas
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- controller.showNoteTaskAsUser(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT, user)
+ val entryPoint =
+ if (isInMultiWindowMode) {
+ NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
+ } else {
+ NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
+ }
+ controller.showNoteTaskAsUser(entryPoint, user)
finish()
}
companion object {
/** Creates a new [Intent] set to start [LaunchNoteTaskActivity]. */
- fun newIntent(context: Context): Intent {
- return Intent(context, LaunchNoteTaskActivity::class.java).apply {
+ fun createIntent(context: Context): Intent =
+ Intent(context, LaunchNoteTaskActivity::class.java).apply {
// Intent's action must be set in shortcuts, or an exception will be thrown.
action = Intent.ACTION_CREATE_NOTE
}
- }
+
+ /** Creates a new [ComponentName] for [LaunchNoteTaskActivity]. */
+ fun createComponent(context: Context): ComponentName =
+ ComponentName(context, LaunchNoteTaskActivity::class.java)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
index b2e04bb4f26f..69cb6119580b 100644
--- a/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/data/repository/PowerRepository.kt
@@ -26,6 +26,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher
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.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -34,13 +36,18 @@ import kotlinx.coroutines.flow.Flow
interface PowerRepository {
/** Whether the device is interactive. Starts with the current state. */
val isInteractive: Flow<Boolean>
+
+ /** Wakes up the device. */
+ fun wakeUp(why: String, @PowerManager.WakeReason wakeReason: Int)
}
@SysUISingleton
class PowerRepositoryImpl
@Inject
constructor(
- manager: PowerManager,
+ private val manager: PowerManager,
+ @Application private val applicationContext: Context,
+ private val systemClock: SystemClock,
dispatcher: BroadcastDispatcher,
) : PowerRepository {
@@ -68,6 +75,14 @@ constructor(
awaitClose { dispatcher.unregisterReceiver(receiver) }
}
+ override fun wakeUp(why: String, wakeReason: Int) {
+ manager.wakeUp(
+ systemClock.uptimeMillis(),
+ wakeReason,
+ "${applicationContext.packageName}:$why",
+ )
+ }
+
companion object {
private const val TAG = "PowerRepository"
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index 3f799f724fe7..c13476fbbe08 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -17,8 +17,12 @@
package com.android.systemui.power.domain.interactor
+import android.os.PowerManager
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.PowerRepository
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -27,8 +31,27 @@ import kotlinx.coroutines.flow.Flow
class PowerInteractor
@Inject
constructor(
- repository: PowerRepository,
+ private val repository: PowerRepository,
+ private val falsingCollector: FalsingCollector,
+ private val screenOffAnimationController: ScreenOffAnimationController,
+ private val statusBarStateController: StatusBarStateController,
) {
/** Whether the screen is on or off. */
val isInteractive: Flow<Boolean> = repository.isInteractive
+
+ /**
+ * Wakes up the device if the device was dozing.
+ *
+ * @param why a string explaining why we're waking the device for debugging purposes. Should be
+ * in SCREAMING_SNAKE_CASE.
+ * @param wakeReason the PowerManager-based reason why we're waking the device.
+ */
+ fun wakeUpIfDozing(why: String, @PowerManager.WakeReason wakeReason: Int) {
+ if (
+ statusBarStateController.isDozing && screenOffAnimationController.allowWakeUpIfDozing()
+ ) {
+ repository.wakeUp(why, wakeReason)
+ falsingCollector.onScreenOnFromTouch()
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index b8c2fad8c10d..8d9475d9a53e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -52,7 +52,6 @@ import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
@@ -755,8 +754,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
// Alpha progress should be linear on lockscreen shade expansion.
return progress;
}
- if (mIsSmallScreen || !mFeatureFlags.isEnabled(
- Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ if (mIsSmallScreen) {
return ShadeInterpolation.getContentAlpha(progress);
} else {
return mLargeScreenShadeInterpolator.getQsAlpha(progress);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b1dadf0768cb..72941372fc1c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2906,9 +2906,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
float scrimMinFraction;
if (mSplitShadeEnabled) {
boolean highHun = mHeadsUpStartHeight * 2.5
- >
- (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
- ? mSplitShadeFullTransitionDistance : mSplitShadeScrimTransitionDistance);
+ > mSplitShadeFullTransitionDistance;
// if HUN height is higher than 40% of predefined transition distance, it means HUN
// is too high for regular transition. In that case we need to calculate transition
// distance - here we take scrim transition distance as equal to shade transition
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index fd82e2fc01fc..9b797a7e2571 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -18,7 +18,6 @@ package com.android.systemui.shade
import android.hardware.display.AmbientDisplayConfiguration
import android.os.PowerManager
-import android.os.SystemClock
import android.provider.Settings
import android.view.GestureDetector
import android.view.MotionEvent
@@ -28,8 +27,8 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
import com.android.systemui.tuner.TunerService
import com.android.systemui.tuner.TunerService.Tunable
@@ -50,7 +49,7 @@ class PulsingGestureListener @Inject constructor(
private val notificationShadeWindowView: NotificationShadeWindowView,
private val falsingManager: FalsingManager,
private val dockManager: DockManager,
- private val centralSurfaces: CentralSurfaces,
+ private val powerInteractor: PowerInteractor,
private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
private val statusBarStateController: StatusBarStateController,
private val shadeLogger: ShadeLogger,
@@ -88,11 +87,7 @@ class PulsingGestureListener @Inject constructor(
shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap)
if (proximityIsNotNear && isNotAFalseTap) {
shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing")
- centralSurfaces.wakeUpIfDozing(
- SystemClock.uptimeMillis(),
- "PULSING_SINGLE_TAP",
- PowerManager.WAKE_REASON_TAP
- )
+ powerInteractor.wakeUpIfDozing("PULSING_SINGLE_TAP", PowerManager.WAKE_REASON_TAP)
}
return true
}
@@ -113,11 +108,7 @@ class PulsingGestureListener @Inject constructor(
!falsingManager.isProximityNear &&
!falsingManager.isFalseDoubleTap
) {
- centralSurfaces.wakeUpIfDozing(
- SystemClock.uptimeMillis(),
- "PULSING_DOUBLE_TAP",
- PowerManager.WAKE_REASON_TAP
- )
+ powerInteractor.wakeUpIfDozing("PULSING_DOUBLE_TAP", PowerManager.WAKE_REASON_TAP)
return true
}
return false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index 4e1c272ead99..abb69f691c47 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -16,24 +16,11 @@
package com.android.systemui.shade.transition
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.util.MathUtils.constrain
-import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.shade.PanelState
-import com.android.systemui.shade.STATE_OPENING
import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.util.LargeScreenUtils
import java.io.PrintWriter
import javax.inject.Inject
@@ -42,37 +29,19 @@ import javax.inject.Inject
class ScrimShadeTransitionController
@Inject
constructor(
- configurationController: ConfigurationController,
dumpManager: DumpManager,
private val scrimController: ScrimController,
- @Main private val resources: Resources,
- private val statusBarStateController: SysuiStatusBarStateController,
- private val headsUpManager: HeadsUpManager,
- private val featureFlags: FeatureFlags,
) {
- private var inSplitShade = false
- private var splitShadeScrimTransitionDistance = 0
private var lastExpansionFraction: Float? = null
private var lastExpansionEvent: ShadeExpansionChangeEvent? = null
private var currentPanelState: Int? = null
init {
- updateResources()
- configurationController.addCallback(
- object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- updateResources()
- }
- })
dumpManager.registerDumpable(
- ScrimShadeTransitionController::class.java.simpleName, this::dump)
- }
-
- private fun updateResources() {
- inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources)
- splitShadeScrimTransitionDistance =
- resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
+ ScrimShadeTransitionController::class.java.simpleName,
+ this::dump
+ )
}
fun onPanelStateChanged(@PanelState state: Int) {
@@ -87,46 +56,25 @@ constructor(
private fun onStateChanged() {
val expansionEvent = lastExpansionEvent ?: return
- val panelState = currentPanelState
- val expansionFraction = calculateScrimExpansionFraction(expansionEvent, panelState)
+ val expansionFraction = calculateScrimExpansionFraction(expansionEvent)
scrimController.setRawPanelExpansionFraction(expansionFraction)
lastExpansionFraction = expansionFraction
}
- private fun calculateScrimExpansionFraction(
- expansionEvent: ShadeExpansionChangeEvent,
- @PanelState panelState: Int?
- ): Float {
- return if (canUseCustomFraction(panelState)) {
- constrain(expansionEvent.dragDownPxAmount / splitShadeScrimTransitionDistance, 0f, 1f)
- } else {
- expansionEvent.fraction
- }
+ private fun calculateScrimExpansionFraction(expansionEvent: ShadeExpansionChangeEvent): Float {
+ return expansionEvent.fraction
}
- private fun canUseCustomFraction(panelState: Int?) =
- inSplitShade && isScreenUnlocked() && panelState == STATE_OPENING &&
- // in case of HUN we can't always use predefined distances to manage scrim
- // transition because dragDownPxAmount can start from value bigger than
- // splitShadeScrimTransitionDistance
- !headsUpManager.isTrackingHeadsUp &&
- !featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
-
- private fun isScreenUnlocked() =
- statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
-
private fun dump(printWriter: PrintWriter, args: Array<String>) {
printWriter.println(
"""
ScrimShadeTransitionController:
- Resources:
- inSplitShade: $inSplitShade
- isScreenUnlocked: ${isScreenUnlocked()}
- splitShadeScrimTransitionDistance: $splitShadeScrimTransitionDistance
State:
currentPanelState: $currentPanelState
lastExpansionFraction: $lastExpansionFraction
lastExpansionEvent: $lastExpansionEvent
- """.trimIndent())
+ """
+ .trimIndent()
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 9d7f3be4dfe7..edf37ef71450 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -40,8 +40,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -226,9 +224,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
if (ambientState.isBouncerInTransit()) {
viewState.setAlpha(aboutToShowBouncerProgress(expansion));
} else {
- FeatureFlags flags = ambientState.getFeatureFlags();
- if (ambientState.isSmallScreen() || !flags.isEnabled(
- Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ if (ambientState.isSmallScreen()) {
viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
} else {
LargeScreenShadeInterpolator interpolator =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 8aeefeeac211..b116246586b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -274,10 +274,6 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
Assert.isMainThread();
checkForReentrantCall();
- // TODO (b/206842750): This method is called from (silent) clear all and non-clear all
- // contexts and should be checking the NO_CLEAR flag, rather than depending on NSSL
- // to pass in a properly filtered list of notifications
-
final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
for (int i = 0; i < entriesToDismiss.size(); i++) {
NotificationEntry entry = entriesToDismiss.get(i).first;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 23b5241b79ef..314566eaf8d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -633,12 +633,17 @@ class HeadsUpCoordinator @Inject constructor(
mFSIUpdateCandidates.removeAll(toRemoveForFSI)
}
- /** When an action is pressed on a notification, end HeadsUp lifetime extension. */
+ /**
+ * When an action is pressed on a notification, make sure we don't lifetime-extend it in the
+ * future by informing the HeadsUpManager, and make sure we don't keep lifetime-extending it if
+ * we already are.
+ *
+ * @see HeadsUpManager.setUserActionMayIndirectlyRemove
+ * @see HeadsUpManager.canRemoveImmediately
+ */
private val mActionPressListener = Consumer<NotificationEntry> { entry ->
- if (mNotifsExtendingLifetime.contains(entry)) {
- val removeInMillis = mHeadsUpManager.getEarliestRemovalTime(entry.key)
- mExecutor.executeDelayed({ endNotifLifetimeExtensionIfExtended(entry) }, removeInMillis)
- }
+ mHeadsUpManager.setUserActionMayIndirectlyRemove(entry)
+ mExecutor.execute { endNotifLifetimeExtensionIfExtended(entry) }
}
private val mLifetimeExtender = object : NotifLifetimeExtender {
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 6f1c378f429d..ce2658dc2fcc 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
@@ -29,8 +29,6 @@ import com.android.internal.policy.SystemBarUtils;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.R;
import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
@@ -189,9 +187,7 @@ public class StackScrollAlgorithm {
private float interpolateFooterAlpha(AmbientState ambientState) {
float expansion = ambientState.getExpansionFraction();
- FeatureFlags flags = ambientState.getFeatureFlags();
- if (ambientState.isSmallScreen()
- || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ if (ambientState.isSmallScreen()) {
return ShadeInterpolation.getContentAlpha(expansion);
}
LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
@@ -200,9 +196,7 @@ public class StackScrollAlgorithm {
private float interpolateNotificationContentAlpha(AmbientState ambientState) {
float expansion = ambientState.getExpansionFraction();
- FeatureFlags flags = ambientState.getFeatureFlags();
- if (ambientState.isSmallScreen()
- || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ if (ambientState.isSmallScreen()) {
return ShadeInterpolation.getContentAlpha(expansion);
}
LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
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 796ffd375f7c..18d8050f9a36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -43,6 +43,7 @@ import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -197,7 +198,10 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
/**
* Wakes up the device if the device was dozing.
+ *
+ * @deprecated Use {@link PowerInteractor#wakeUpIfDozing(String, int)} instead.
*/
+ @Deprecated
void wakeUpIfDozing(long time, String why, @PowerManager.WakeReason int wakeReason);
/** */
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 2cf9a219716c..81048d60c6d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -136,6 +136,7 @@ import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenu
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.camera.CameraIntents;
import com.android.systemui.charging.WiredChargingRippleController;
@@ -157,7 +158,6 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
import com.android.systemui.navigationbar.NavigationBarController;
@@ -1587,10 +1587,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
/**
* Ask the display to wake up if currently dozing, else do nothing
*
+ * @deprecated Use {@link PowerInteractor#wakeUpIfDozing(String, int)} instead.
+ *
* @param time when to wake up
* @param why the reason for the wake up
*/
@Override
+ @Deprecated
public void wakeUpIfDozing(long time, String why, @PowerManager.WakeReason int wakeReason) {
if (mDozing && mScreenOffAnimationController.allowWakeUpIfDozing()) {
mPowerManager.wakeUp(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index a42e360ca7ac..47c4023ca8aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -881,24 +881,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mBehindAlpha = 1;
mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
} else {
- if (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
- mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
- mPanelExpansionFraction * mDefaultScrimAlpha);
- mNotificationsAlpha =
- mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
- mPanelExpansionFraction);
- } else {
- // Behind scrim will finish fading in at 30% expansion.
- float behindFraction = MathUtils
- .constrainedMap(0f, 1f, 0f, 0.3f, mPanelExpansionFraction);
- mBehindAlpha = behindFraction * mDefaultScrimAlpha;
- // Delay fade-in of notification scrim a bit further, to coincide with the
- // behind scrim finishing fading in.
- // Also to coincide with the view starting to fade in, otherwise the empty
- // panel can be quite jarring.
- mNotificationsAlpha = MathUtils
- .constrainedMap(0f, 1f, 0.3f, 0.75f, mPanelExpansionFraction);
- }
+ mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
+ mPanelExpansionFraction * mDefaultScrimAlpha);
+ mNotificationsAlpha =
+ mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
+ mPanelExpansionFraction);
}
mBehindTint = mState.getBehindTint();
mInFrontAlpha = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index ed8050a031d8..92a78541d744 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -393,6 +393,31 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
}
}
+ /**
+ * Notes that the user took an action on an entry that might indirectly cause the system or the
+ * app to remove the notification.
+ *
+ * @param entry the entry that might be indirectly removed by the user's action
+ *
+ * @see com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator#mActionPressListener
+ * @see #canRemoveImmediately(String)
+ */
+ public void setUserActionMayIndirectlyRemove(@NonNull NotificationEntry entry) {
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
+ if (headsUpEntry != null) {
+ headsUpEntry.userActionMayIndirectlyRemove = true;
+ }
+ }
+
+ @Override
+ public boolean canRemoveImmediately(@NonNull String key) {
+ HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
+ if (headsUpEntry != null && headsUpEntry.userActionMayIndirectlyRemove) {
+ return true;
+ }
+ return super.canRemoveImmediately(key);
+ }
+
@NonNull
@Override
protected HeadsUpEntry createAlertEntry() {
@@ -421,6 +446,8 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
*/
protected class HeadsUpEntry extends AlertEntry {
public boolean remoteInputActive;
+ public boolean userActionMayIndirectlyRemove;
+
protected boolean expanded;
protected boolean wasUnpinned;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index f5cd0ca7ab3b..319a02d911ed 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -50,6 +50,7 @@ import com.android.systemui.SysuiTestCase;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -59,6 +60,7 @@ import org.mockito.MockitoAnnotations;
@RunWithLooper
@RunWith(AndroidTestingRunner.class)
@SmallTest
+@Ignore("b/286245842")
public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
private static final int TARGET_USER_ID = KeyguardUpdateMonitor.getCurrentUser();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 8dc1e8fba600..65c677ea7e5a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -73,6 +73,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
+ private KeyguardStatusAreaView mStatusArea;
KeyguardClockSwitch mKeyguardClockSwitch;
@@ -110,6 +111,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
(KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
mSmallClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view_large);
+ mStatusArea = mKeyguardClockSwitch.findViewById(R.id.keyguard_status_area);
mKeyguardClockSwitch.mChildrenAreLaidOut = true;
}
@@ -186,6 +188,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
mKeyguardClockSwitch.mClockInAnim.end();
mKeyguardClockSwitch.mClockOutAnim.end();
+ mKeyguardClockSwitch.mStatusAreaAnim.end();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
@@ -209,6 +212,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
mKeyguardClockSwitch.mClockInAnim.end();
mKeyguardClockSwitch.mClockOutAnim.end();
+ mKeyguardClockSwitch.mStatusAreaAnim.end();
assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
@@ -231,6 +235,31 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
}
@Test
+ public void switchingToSmallClockAnimation_resetsStatusArea() {
+ mKeyguardClockSwitch.switchToClock(SMALL, true);
+
+ mKeyguardClockSwitch.mClockInAnim.end();
+ mKeyguardClockSwitch.mClockOutAnim.end();
+ mKeyguardClockSwitch.mStatusAreaAnim.end();
+
+ assertThat(mStatusArea.getTranslationX()).isEqualTo(0);
+ assertThat(mStatusArea.getTranslationY()).isEqualTo(0);
+ assertThat(mStatusArea.getScaleX()).isEqualTo(1);
+ assertThat(mStatusArea.getScaleY()).isEqualTo(1);
+ }
+
+ @Test
+ public void switchingToSmallClockNoAnimation_resetsStatusArea() {
+ mKeyguardClockSwitch.switchToClock(SMALL, false);
+
+ assertThat(mStatusArea.getTranslationX()).isEqualTo(0);
+ assertThat(mStatusArea.getTranslationY()).isEqualTo(0);
+ assertThat(mStatusArea.getScaleX()).isEqualTo(1);
+ assertThat(mStatusArea.getScaleY()).isEqualTo(1);
+ }
+
+
+ @Test
public void switchingToBigClock_returnsTrueOnlyWhenItWasNotVisibleBefore() {
assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isTrue();
assertThat(mKeyguardClockSwitch.switchToClock(LARGE, /* animate */ true)).isFalse();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
new file mode 100644
index 000000000000..e6b696454d42
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
@@ -0,0 +1,44 @@
+package com.android.keyguard
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+class KeyguardStatusAreaViewTest : SysuiTestCase() {
+
+ private lateinit var view: KeyguardStatusAreaView
+
+ @Before
+ fun setUp() {
+ view = KeyguardStatusAreaView(context)
+ }
+
+ @Test
+ fun checkTranslationX_AddedTotals() {
+ view.translateXFromClockDesign = 10f
+ assertEquals(10f, view.translationX)
+
+ view.translateXFromAod = 20f
+ assertEquals(30f, view.translationX)
+
+ view.translateXFromUnfold = 30f
+ assertEquals(60f, view.translationX)
+ }
+
+ @Test
+ fun checkTranslationY_AddedTotals() {
+ view.translateYFromClockSize = 10f
+ assertEquals(10f, view.translationY)
+
+ view.translateYFromClockDesign = 20f
+ assertEquals(30f, view.translationY)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index a0fdc8f1555e..24a5e8072796 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -48,8 +48,7 @@ public class DependencyTest extends SysuiTestCase {
@Test
public void testInitDependency() throws ExecutionException, InterruptedException {
Dependency.clearDependencies();
- SystemUIInitializer initializer =
- SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+ SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
initializer.init(true);
Dependency dependency = initializer.getSysUIComponent().createDependency();
dependency.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
index 26cbd7703075..724c9d1dfc42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -116,11 +116,7 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
@Test
fun testBindAndLoad_cancel() {
- val callback = object : ControlsBindingController.LoadCallback {
- override fun error(message: String) {}
-
- override fun accept(t: List<Control>) {}
- }
+ val callback = mock(ControlsBindingController.LoadCallback::class.java)
val subscription = mock(IControlsSubscription::class.java)
val canceller = controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
@@ -130,6 +126,7 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
canceller.run()
verify(providers[0]).cancelSubscription(subscription)
+ verify(callback).error(any())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 26c0ea4bd11a..7510373ed582 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -53,7 +53,7 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(controller.intent).thenReturn(INTENT_1)
- underTest = QrCodeScannerKeyguardQuickAffordanceConfig(mock(), controller)
+ underTest = QrCodeScannerKeyguardQuickAffordanceConfig(context, controller)
}
@Test
@@ -77,22 +77,21 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
- fun affordance_scannerActivityChanged_deliversModelWithUpdatedIntent() =
- runBlockingTest {
- whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
- var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
- val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
- val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
- verify(controller).addCallback(callbackCaptor.capture())
-
- whenever(controller.intent).thenReturn(INTENT_2)
- callbackCaptor.value.onQRCodeScannerActivityChanged()
-
- assertVisibleState(latest)
-
- job.cancel()
- verify(controller).removeCallback(callbackCaptor.value)
- }
+ fun affordance_scannerActivityChanged_deliversModelWithUpdatedIntent() = runBlockingTest {
+ whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
+ var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+ val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
+ val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
+ verify(controller).addCallback(callbackCaptor.capture())
+
+ whenever(controller.intent).thenReturn(INTENT_2)
+ callbackCaptor.value.onQRCodeScannerActivityChanged()
+
+ assertVisibleState(latest)
+
+ job.cancel()
+ verify(controller).removeCallback(callbackCaptor.value)
+ }
@Test
fun affordance_scannerPreferenceChanged_deliversVisibleModel() = runBlockingTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 7b673bc2b31e..f6075add6afe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -2420,7 +2420,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun playTurbulenceNoise_finishesAfterDuration() {
- fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
fakeFeatureFlag.set(Flags.UMO_TURBULENCE_NOISE, true)
val semanticActions =
@@ -2452,6 +2451,29 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
+ fun playTurbulenceNoise_whenPlaybackStateIsNotPlaying_doesNotPlayTurbulence() {
+ fakeFeatureFlag.set(Flags.UMO_TURBULENCE_NOISE, true)
+
+ val semanticActions =
+ MediaButton(
+ custom0 =
+ MediaAction(
+ icon = null,
+ action = {},
+ contentDescription = "custom0",
+ background = null
+ ),
+ )
+ val data = mediaData.copy(semanticActions = semanticActions)
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(data, KEY)
+
+ viewHolder.action0.callOnClick()
+
+ assertThat(turbulenceNoiseView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
fun outputSwitcher_hasCustomIntent_openOverLockscreen() {
// When the device for a media player has an intent that opens over lockscreen
val pendingIntent = mock(PendingIntent::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 204077bd927b..a76af8e83248 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -244,39 +244,11 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
// endregion
// region showNoteTask
- @Test
- fun showNoteTask_keyguardIsLocked_shouldStartActivityAndLogUiEvent() {
- val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true)
- whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
- whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
-
- createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
-
- val intentCaptor = argumentCaptor<Intent>()
- val userCaptor = argumentCaptor<UserHandle>()
- verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
- assertThat(intentCaptor.value).run {
- hasAction(ACTION_CREATE_NOTE)
- hasPackage(NOTE_TASK_PACKAGE_NAME)
- hasFlags(FLAG_ACTIVITY_NEW_TASK)
- hasFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
- hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
- extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
- }
- assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
- verify(eventLogger).logNoteTaskOpened(expectedInfo)
- verifyZeroInteractions(bubbles)
- }
-
- @Test
- fun showNoteTask_defaultUserSet_shouldStartActivityWithExpectedUserAndLogUiEvent() {
- whenever(secureSettings.getInt(eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE), any()))
- .thenReturn(10)
+ fun showNoteTaskAsUser_keyguardIsLocked_shouldStartActivityWithExpectedUserAndLogUiEvent() {
val user10 = UserHandle.of(/* userId= */ 10)
-
val expectedInfo =
NOTE_TASK_INFO.copy(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+ entryPoint = TAIL_BUTTON,
isKeyguardLocked = true,
user = user10,
)
@@ -284,22 +256,18 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
createNoteTaskController()
- .showNoteTask(
- entryPoint = expectedInfo.entryPoint!!,
- )
+ .showNoteTaskAsUser(entryPoint = expectedInfo.entryPoint!!, user = user10)
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
- intentCaptor.value.let { intent ->
- assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
- assertThat(intent.`package`).isEqualTo(NOTE_TASK_PACKAGE_NAME)
- assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
- assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
- .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
- assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
- .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
- assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
+ assertThat(intentCaptor.value).run {
+ hasAction(ACTION_CREATE_NOTE)
+ hasPackage(NOTE_TASK_PACKAGE_NAME)
+ hasFlags(FLAG_ACTIVITY_NEW_TASK)
+ hasFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
+ hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
}
assertThat(userCaptor.value).isEqualTo(user10)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
@@ -307,15 +275,12 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
@Test
- fun showNoteTaskWithUser_keyguardIsLocked_shouldStartActivityWithExpectedUserAndLogUiEvent() {
- val user10 = UserHandle.of(/* userId= */ 10)
- val expectedInfo =
- NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true, user = user10)
+ fun showNoteTask_keyguardIsLocked_notesIsClosed_shouldStartActivityAndLogUiEvent() {
+ val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = true)
whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
- createNoteTaskController()
- .showNoteTaskAsUser(entryPoint = expectedInfo.entryPoint!!, user = user10)
+ createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
val intentCaptor = argumentCaptor<Intent>()
val userCaptor = argumentCaptor<UserHandle>()
@@ -328,7 +293,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
hasFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
}
- assertThat(userCaptor.value).isEqualTo(user10)
+ assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
verifyZeroInteractions(bubbles)
}
@@ -357,10 +322,26 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
@Test
- fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesWithoutLoggingUiEvent() {
+ fun showNoteTask_keyguardIsUnlocked_noteIsClosed_shouldStartBubblesWithoutLoggingUiEvent() {
+ val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = false)
+ whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+
+ createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
+
+ // Context package name used to create bubble icon from drawable resource id
+ verify(context, atLeastOnce()).packageName
+ verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
+ verifyZeroInteractions(eventLogger)
+ }
+
+ @Test
+ fun showNoteTask_keyguardIsUnlocked_noteIsOpen_shouldStartBubblesWithoutLoggingUiEvent() {
val expectedInfo = NOTE_TASK_INFO.copy(entryPoint = TAIL_BUTTON, isKeyguardLocked = false)
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+ whenever(activityManager.getRunningTasks(anyInt()))
+ .thenReturn(listOf(NOTE_RUNNING_TASK_INFO))
createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
@@ -371,6 +352,44 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
@Test
+ fun showNoteTask_defaultUserSet_shouldStartActivityWithExpectedUserAndLogUiEvent() {
+ whenever(secureSettings.getInt(eq(Settings.Secure.DEFAULT_NOTE_TASK_PROFILE), any()))
+ .thenReturn(10)
+ val user10 = UserHandle.of(/* userId= */ 10)
+
+ val expectedInfo =
+ NOTE_TASK_INFO.copy(
+ entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
+ isKeyguardLocked = true,
+ user = user10,
+ )
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked)
+ whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(expectedInfo)
+
+ createNoteTaskController()
+ .showNoteTask(
+ entryPoint = expectedInfo.entryPoint!!,
+ )
+
+ val intentCaptor = argumentCaptor<Intent>()
+ val userCaptor = argumentCaptor<UserHandle>()
+ verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+ intentCaptor.value.let { intent ->
+ assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
+ assertThat(intent.`package`).isEqualTo(NOTE_TASK_PACKAGE_NAME)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
+ .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
+ .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
+ assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
+ }
+ assertThat(userCaptor.value).isEqualTo(user10)
+ verify(eventLogger).logNoteTaskOpened(expectedInfo)
+ verifyZeroInteractions(bubbles)
+ }
+
+ @Test
fun showNoteTask_bubblesIsNull_shouldDoNothing() {
createNoteTaskController(bubbles = null).showNoteTask(entryPoint = TAIL_BUTTON)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
index 34354504abf0..24f39d187f88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt
@@ -16,37 +16,47 @@
package com.android.systemui.notetask
import android.os.UserHandle
-import android.test.suitebuilder.annotation.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
/** atest SystemUITests:NoteTaskInfoTest */
@SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(AndroidTestingRunner::class)
internal class NoteTaskInfoTest : SysuiTestCase() {
- private fun createNoteTaskInfo(): NoteTaskInfo =
- NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID, UserHandle.of(0))
-
@Test
fun launchMode_keyguardLocked_launchModeActivity() {
- val underTest = createNoteTaskInfo().copy(isKeyguardLocked = true)
+ val underTest = DEFAULT_INFO.copy(isKeyguardLocked = true)
assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity)
}
@Test
- fun launchMode_keyguardUnlocked_launchModeActivity() {
- val underTest = createNoteTaskInfo().copy(isKeyguardLocked = false)
+ fun launchMode_multiWindowMode_launchModeActivity() {
+ val underTest = DEFAULT_INFO.copy(entryPoint = WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE)
+
+ assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.Activity)
+ }
+
+ @Test
+ fun launchMode_keyguardUnlocked_launchModeAppBubble() {
+ val underTest = DEFAULT_INFO.copy(isKeyguardLocked = false)
assertThat(underTest.launchMode).isEqualTo(NoteTaskLaunchMode.AppBubble)
}
private companion object {
- const val NOTES_PACKAGE_NAME = "com.android.note.app"
- const val NOTES_UID = 123456
+
+ val DEFAULT_INFO =
+ NoteTaskInfo(
+ packageName = "com.android.note.app",
+ uid = 123456,
+ user = UserHandle.of(0),
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index cc64cc270b97..58c762e3cc99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -296,8 +296,8 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
assertThat(pickerScreenState is KeyguardQuickAffordanceConfig.PickerScreenState.Disabled)
.isTrue()
val disabled = pickerScreenState as KeyguardQuickAffordanceConfig.PickerScreenState.Disabled
- assertThat(disabled.instructions)
- .isEqualTo(listOf("Select a default notes app to use the notetaking shortcut"))
+ assertThat(disabled.explanation)
+ .isEqualTo("Select a default notes app to use the notetaking shortcut")
assertThat(disabled.actionText).isEqualTo("Select app")
assertThat(disabled.actionIntent?.action)
.isEqualTo(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
index bb3b3f709a7b..a01394f027cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt
@@ -24,7 +24,11 @@ import android.os.PowerManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
@@ -47,6 +51,8 @@ import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
class PowerRepositoryImplTest : SysuiTestCase() {
+ private val systemClock = FakeSystemClock()
+
@Mock private lateinit var manager: PowerManager
@Mock private lateinit var dispatcher: BroadcastDispatcher
@Captor private lateinit var receiverCaptor: ArgumentCaptor<BroadcastReceiver>
@@ -62,7 +68,13 @@ class PowerRepositoryImplTest : SysuiTestCase() {
isInteractive = true
whenever(manager.isInteractive).then { isInteractive }
- underTest = PowerRepositoryImpl(manager = manager, dispatcher = dispatcher)
+ underTest =
+ PowerRepositoryImpl(
+ manager,
+ context.applicationContext,
+ systemClock,
+ dispatcher,
+ )
}
@Test
@@ -160,6 +172,27 @@ class PowerRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun wakeUp_notifiesPowerManager() {
+ systemClock.setUptimeMillis(345000)
+
+ underTest.wakeUp("fakeWhy", PowerManager.WAKE_REASON_GESTURE)
+
+ val reasonCaptor = argumentCaptor<String>()
+ verify(manager)
+ .wakeUp(eq(345000L), eq(PowerManager.WAKE_REASON_GESTURE), capture(reasonCaptor))
+ assertThat(reasonCaptor.value).contains("fakeWhy")
+ }
+
+ @Test
+ fun wakeUp_usesApplicationPackageName() {
+ underTest.wakeUp("fakeWhy", PowerManager.WAKE_REASON_GESTURE)
+
+ val reasonCaptor = argumentCaptor<String>()
+ verify(manager).wakeUp(any(), any(), capture(reasonCaptor))
+ assertThat(reasonCaptor.value).contains(context.applicationContext.packageName)
+ }
+
private fun verifyRegistered() {
// We must verify with all arguments, even those that are optional because they have default
// values because Mockito is forcing us to. Once we can use mockito-kotlin, we should be
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
index 31d451227a9b..023ed061c642 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt
@@ -17,9 +17,14 @@
package com.android.systemui.power.domain.interactor
+import android.os.PowerManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
@@ -29,6 +34,9 @@ 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)
@@ -36,14 +44,25 @@ class PowerInteractorTest : SysuiTestCase() {
private lateinit var underTest: PowerInteractor
private lateinit var repository: FakePowerRepository
+ @Mock private lateinit var falsingCollector: FalsingCollector
+ @Mock private lateinit var screenOffAnimationController: ScreenOffAnimationController
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
repository =
FakePowerRepository(
initialInteractive = true,
)
- underTest = PowerInteractor(repository = repository)
+ underTest =
+ PowerInteractor(
+ repository,
+ falsingCollector,
+ screenOffAnimationController,
+ statusBarStateController,
+ )
}
@Test
@@ -72,6 +91,40 @@ class PowerInteractorTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun wakeUpIfDozing_notDozing_notWoken() {
+ whenever(statusBarStateController.isDozing).thenReturn(false)
+ whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
+
+ underTest.wakeUpIfDozing("why", PowerManager.WAKE_REASON_TAP)
+
+ assertThat(repository.lastWakeWhy).isNull()
+ assertThat(repository.lastWakeReason).isNull()
+ }
+
+ @Test
+ fun wakeUpIfDozing_notAllowed_notWoken() {
+ whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(false)
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+
+ underTest.wakeUpIfDozing("why", PowerManager.WAKE_REASON_TAP)
+
+ assertThat(repository.lastWakeWhy).isNull()
+ assertThat(repository.lastWakeReason).isNull()
+ }
+
+ @Test
+ fun wakeUpIfDozing_dozingAndAllowed_wokenAndFalsingNotified() {
+ whenever(statusBarStateController.isDozing).thenReturn(true)
+ whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
+
+ underTest.wakeUpIfDozing("testReason", PowerManager.WAKE_REASON_GESTURE)
+
+ assertThat(repository.lastWakeWhy).isEqualTo("testReason")
+ assertThat(repository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_GESTURE)
+ verify(falsingCollector).onScreenOnFromTouch()
+ }
+
companion object {
private val IMMEDIATE = Dispatchers.Main.immediate
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 87ca9dfdae11..fcda5f50cef2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -50,10 +50,8 @@ import androidx.test.filters.SmallTest;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
-import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
@@ -169,9 +167,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- public void transitionToFullShade_largeScreen_flagEnabled_alphaLargeScreenShadeInterpolator() {
- when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(true);
+ public void transitionToFullShade_largeScreen_alphaLargeScreenShadeInterpolator() {
QSFragment fragment = resumeAndGetFragment();
setIsLargeScreen();
setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
@@ -188,25 +184,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- public void transitionToFullShade_largeScreen_flagDisabled_alphaStandardInterpolator() {
- when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(false);
- QSFragment fragment = resumeAndGetFragment();
- setIsLargeScreen();
- setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
- boolean isTransitioningToFullShade = true;
- float transitionProgress = 0.5f;
- float squishinessFraction = 0.5f;
- when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
-
- fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
- squishinessFraction);
-
- assertThat(mQsFragmentView.getAlpha())
- .isEqualTo(ShadeInterpolation.getContentAlpha(transitionProgress));
- }
-
- @Test
public void
transitionToFullShade_onKeyguard_noBouncer_setsAlphaUsingLinearInterpolator() {
QSFragment fragment = resumeAndGetFragment();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index d7c06a76bf0f..f1d56f9e3480 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -25,22 +25,24 @@ import android.testing.TestableLooper.RunWithLooper
import android.view.MotionEvent
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.tuner.TunerService
import com.android.systemui.tuner.TunerService.Tunable
import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyLong
-import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@@ -54,12 +56,12 @@ class PulsingGestureListenerTest : SysuiTestCase() {
@Mock
private lateinit var view: NotificationShadeWindowView
@Mock
- private lateinit var centralSurfaces: CentralSurfaces
- @Mock
private lateinit var dockManager: DockManager
@Mock
private lateinit var falsingManager: FalsingManager
@Mock
+ private lateinit var falsingCollector: FalsingCollector
+ @Mock
private lateinit var ambientDisplayConfiguration: AmbientDisplayConfiguration
@Mock
private lateinit var tunerService: TunerService
@@ -71,7 +73,10 @@ class PulsingGestureListenerTest : SysuiTestCase() {
private lateinit var shadeLogger: ShadeLogger
@Mock
private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var screenOffAnimationController: ScreenOffAnimationController
+ private lateinit var powerRepository: FakePowerRepository
private lateinit var tunableCaptor: ArgumentCaptor<Tunable>
private lateinit var underTest: PulsingGestureListener
@@ -79,11 +84,18 @@ class PulsingGestureListenerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
+ powerRepository = FakePowerRepository()
+
underTest = PulsingGestureListener(
view,
falsingManager,
dockManager,
- centralSurfaces,
+ PowerInteractor(
+ powerRepository,
+ falsingCollector,
+ screenOffAnimationController,
+ statusBarStateController,
+ ),
ambientDisplayConfiguration,
statusBarStateController,
shadeLogger,
@@ -92,6 +104,7 @@ class PulsingGestureListenerTest : SysuiTestCase() {
dumpManager
)
whenever(dockManager.isDocked).thenReturn(false)
+ whenever(screenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true)
}
@Test
@@ -110,8 +123,8 @@ class PulsingGestureListenerTest : SysuiTestCase() {
underTest.onSingleTapUp(upEv)
// THEN wake up device if dozing
- verify(centralSurfaces).wakeUpIfDozing(
- anyLong(), anyString(), eq(PowerManager.WAKE_REASON_TAP))
+ assertThat(powerRepository.lastWakeWhy).isNotNull()
+ assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
}
@Test
@@ -130,8 +143,8 @@ class PulsingGestureListenerTest : SysuiTestCase() {
underTest.onDoubleTapEvent(upEv)
// THEN wake up device if dozing
- verify(centralSurfaces).wakeUpIfDozing(
- anyLong(), anyString(), eq(PowerManager.WAKE_REASON_TAP))
+ assertThat(powerRepository.lastWakeWhy).isNotNull()
+ assertThat(powerRepository.lastWakeReason).isEqualTo(PowerManager.WAKE_REASON_TAP)
}
@Test
@@ -162,8 +175,8 @@ class PulsingGestureListenerTest : SysuiTestCase() {
underTest.onSingleTapUp(upEv)
// THEN the device doesn't wake up
- verify(centralSurfaces, never()).wakeUpIfDozing(
- anyLong(), anyString(), anyInt())
+ assertThat(powerRepository.lastWakeWhy).isNull()
+ assertThat(powerRepository.lastWakeReason).isNull()
}
@Test
@@ -210,8 +223,8 @@ class PulsingGestureListenerTest : SysuiTestCase() {
underTest.onDoubleTapEvent(upEv)
// THEN the device doesn't wake up
- verify(centralSurfaces, never()).wakeUpIfDozing(
- anyLong(), anyString(), anyInt())
+ assertThat(powerRepository.lastWakeWhy).isNull()
+ assertThat(powerRepository.lastWakeReason).isNull()
}
@Test
@@ -230,8 +243,8 @@ class PulsingGestureListenerTest : SysuiTestCase() {
underTest.onSingleTapUp(upEv)
// THEN the device doesn't wake up
- verify(centralSurfaces, never()).wakeUpIfDozing(
- anyLong(), anyString(), anyInt())
+ assertThat(powerRepository.lastWakeWhy).isNull()
+ assertThat(powerRepository.lastWakeReason).isNull()
}
@Test
@@ -250,8 +263,8 @@ class PulsingGestureListenerTest : SysuiTestCase() {
underTest.onDoubleTapEvent(upEv)
// THEN the device doesn't wake up
- verify(centralSurfaces, never()).wakeUpIfDozing(
- anyLong(), anyString(), anyInt())
+ assertThat(powerRepository.lastWakeWhy).isNull()
+ assertThat(powerRepository.lastWakeReason).isNull()
}
fun updateSettings() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index cbf54854759b..a5bd2aea2d95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -2,26 +2,16 @@ package com.android.systemui.shade.transition
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.shade.STATE_CLOSED
-import com.android.systemui.shade.STATE_OPEN
import com.android.systemui.shade.STATE_OPENING
import com.android.systemui.shade.ShadeExpansionChangeEvent
-import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.ScrimController
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -30,10 +20,6 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
@Mock private lateinit var scrimController: ScrimController
@Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock private lateinit var headsUpManager: HeadsUpManager
- @Mock private lateinit var featureFlags: FeatureFlags
- private val configurationController = FakeConfigurationController()
private lateinit var controller: ScrimShadeTransitionController
@@ -41,131 +27,25 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
context.ensureTestableResources()
- controller =
- ScrimShadeTransitionController(
- configurationController,
- dumpManager,
- scrimController,
- context.resources,
- statusBarStateController,
- headsUpManager,
- featureFlags)
+ controller = ScrimShadeTransitionController(dumpManager, scrimController)
controller.onPanelStateChanged(STATE_OPENING)
}
@Test
- fun onPanelExpansionChanged_inSingleShade_setsFractionEqualToEventFraction() {
- setSplitShadeEnabled(false)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
- verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_unlockedShade_setsFractionBasedOnDragDownAmount() {
- whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
- val scrimShadeTransitionDistance =
- context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
- val expectedFraction = EXPANSION_EVENT.dragDownPxAmount / scrimShadeTransitionDistance
- verify(scrimController).setRawPanelExpansionFraction(expectedFraction)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_largeDragDownAmount_fractionIsNotGreaterThan1() {
- whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
- val scrimShadeTransitionDistance =
- context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(
- EXPANSION_EVENT.copy(dragDownPxAmount = 100f * scrimShadeTransitionDistance))
-
- verify(scrimController).setRawPanelExpansionFraction(1f)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_negativeDragDownAmount_fractionIsNotLessThan0() {
- whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT.copy(dragDownPxAmount = -100f))
-
- verify(scrimController).setRawPanelExpansionFraction(0f)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_onLockedShade_setsFractionEqualToEventFraction() {
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.SHADE_LOCKED)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
- verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_flagTrue_setsFractionEqualToEventFraction() {
- whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(true)
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.SHADE)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
- verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_onKeyguard_setsFractionEqualToEventFraction() {
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.KEYGUARD)
- setSplitShadeEnabled(true)
-
+ fun onPanelExpansionChanged_setsFractionEqualToEventFraction() {
controller.onPanelExpansionChanged(EXPANSION_EVENT)
verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
}
- @Test
- fun onPanelExpansionChanged_inSplitShade_panelOpen_setsFractionEqualToEventFraction() {
- controller.onPanelStateChanged(STATE_OPEN)
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.KEYGUARD)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
- verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
- }
-
- @Test
- fun onPanelExpansionChanged_inSplitShade_panelClosed_setsFractionEqualToEventFraction() {
- controller.onPanelStateChanged(STATE_CLOSED)
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.KEYGUARD)
- setSplitShadeEnabled(true)
-
- controller.onPanelExpansionChanged(EXPANSION_EVENT)
-
- verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
- }
-
- private fun setSplitShadeEnabled(enabled: Boolean) {
- overrideResource(R.bool.config_use_split_notification_shade, enabled)
- configurationController.notifyConfigurationChanged()
- }
-
companion object {
val EXPANSION_EVENT =
ShadeExpansionChangeEvent(
- fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 10f)
+ fraction = 0.5f,
+ expanded = true,
+ tracking = true,
+ dragDownPxAmount = 10f
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index eef4470c48cd..04c93cb71e42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -298,6 +298,54 @@ class ClockRegistryTest : SysuiTestCase() {
}
@Test
+ fun unknownPluginAttached_clockAndListUnchanged_loadRequested() {
+ val mockPluginLifecycle = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+ whenever(mockPluginLifecycle.getPackage()).thenReturn("some.other.package")
+
+ var changeCallCount = 0
+ var listChangeCallCount = 0
+ registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener {
+ override fun onCurrentClockChanged() { changeCallCount++ }
+ override fun onAvailableClocksChanged() { listChangeCallCount++ }
+ })
+
+ assertEquals(true, pluginListener.onPluginAttached(mockPluginLifecycle))
+ scheduler.runCurrent()
+ assertEquals(0, changeCallCount)
+ assertEquals(0, listChangeCallCount)
+ }
+
+ @Test
+ fun knownPluginAttached_clockAndListChanged_notLoaded() {
+ val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+ whenever(mockPluginLifecycle1.getPackage()).thenReturn("com.android.systemui.falcon.one")
+ val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
+ whenever(mockPluginLifecycle2.getPackage()).thenReturn("com.android.systemui.falcon.two")
+
+ var changeCallCount = 0
+ var listChangeCallCount = 0
+ registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener {
+ override fun onCurrentClockChanged() { changeCallCount++ }
+ override fun onAvailableClocksChanged() { listChangeCallCount++ }
+ })
+
+ registry.applySettings(ClockSettings("DIGITAL_CLOCK_CALLIGRAPHY", null))
+ scheduler.runCurrent()
+ assertEquals(1, changeCallCount)
+ assertEquals(0, listChangeCallCount)
+
+ assertEquals(false, pluginListener.onPluginAttached(mockPluginLifecycle1))
+ scheduler.runCurrent()
+ assertEquals(1, changeCallCount)
+ assertEquals(1, listChangeCallCount)
+
+ assertEquals(false, pluginListener.onPluginAttached(mockPluginLifecycle2))
+ scheduler.runCurrent()
+ assertEquals(1, changeCallCount)
+ assertEquals(2, listChangeCallCount)
+ }
+
+ @Test
fun pluginAddRemove_concurrentModification() {
val mockPluginLifecycle1 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
val mockPluginLifecycle2 = mock<PluginLifecycleManager<ClockProviderPlugin>>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
index d5e904c636d5..88d853e244e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginInstanceTest.java
@@ -122,6 +122,7 @@ public class PluginInstanceTest extends SysuiTestCase {
mPluginInstanceFactory.create(
mContext, mAppInfo, wrongVersionTestPluginComponentName,
TestPlugin.class, mPluginListener);
+ mPluginInstance.onCreate();
}
@Test
@@ -135,11 +136,12 @@ public class PluginInstanceTest extends SysuiTestCase {
@Test
public void testOnDestroy() {
+ mPluginInstance.onCreate();
mPluginInstance.onDestroy();
assertEquals(1, mPluginListener.mDetachedCount);
assertEquals(1, mPluginListener.mUnloadCount);
assertNull(mPluginInstance.getPlugin());
- assertInstances(0, -1); // Destroyed but never created
+ assertInstances(0, 0); // Destroyed but never created
}
@Test
@@ -161,6 +163,16 @@ public class PluginInstanceTest extends SysuiTestCase {
assertInstances(0, 0);
}
+ @Test
+ public void testOnAttach_SkipLoad() {
+ mPluginListener.mAttachReturn = false;
+ mPluginInstance.onCreate();
+ assertEquals(1, mPluginListener.mAttachedCount);
+ assertEquals(0, mPluginListener.mLoadCount);
+ assertEquals(null, mPluginInstance.getPlugin());
+ assertInstances(0, 0);
+ }
+
// This target class doesn't matter, it just needs to have a Requires to hit the flow where
// the mock version info is called.
@ProvidesInterface(action = TestPlugin.ACTION, version = TestPlugin.VERSION)
@@ -220,15 +232,17 @@ public class PluginInstanceTest extends SysuiTestCase {
}
public class FakeListener implements PluginListener<TestPlugin> {
+ public boolean mAttachReturn = true;
public int mAttachedCount = 0;
public int mDetachedCount = 0;
public int mLoadCount = 0;
public int mUnloadCount = 0;
@Override
- public void onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
+ public boolean onPluginAttached(PluginLifecycleManager<TestPlugin> manager) {
mAttachedCount++;
assertEquals(PluginInstanceTest.this.mPluginInstance, manager);
+ return mAttachReturn;
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 283efe263f04..257cc5b1b85c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -221,16 +221,35 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
}
@Test
- fun hunExtensionCancelledWhenHunActionPressed() {
+ fun actionPressCancelsExistingLifetimeExtension() {
whenever(headsUpManager.isSticky(anyString())).thenReturn(true)
addHUN(entry)
+
whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false)
whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
- assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+ assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* reason = */ 0))
+
actionPressListener.accept(entry)
- executor.advanceClockToLast()
executor.runAllReady()
- verify(headsUpManager, times(1)).removeNotification(eq(entry.key), eq(true))
+ verify(endLifetimeExtension, times(1)).onEndLifetimeExtension(notifLifetimeExtender, entry)
+
+ collectionListener.onEntryRemoved(entry, /* reason = */ 0)
+ verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any())
+ }
+
+ @Test
+ fun actionPressPreventsFutureLifetimeExtension() {
+ whenever(headsUpManager.isSticky(anyString())).thenReturn(true)
+ addHUN(entry)
+
+ actionPressListener.accept(entry)
+ verify(headsUpManager, times(1)).setUserActionMayIndirectlyRemove(entry)
+
+ whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(true)
+ assertFalse(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
+
+ collectionListener.onEntryRemoved(entry, /* reason = */ 0)
+ verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any())
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 05b70ebfbb3e..8c27a7dd1590 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -8,7 +8,6 @@ import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerPr
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
@@ -311,9 +310,8 @@ class NotificationShelfTest : SysuiTestCase() {
}
@Test
- fun updateState_flagTrue_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() {
+ fun updateState_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() {
val expansionFraction = 0.6f
- whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(true)
whenever(ambientState.isSmallScreen).thenReturn(false)
whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
.thenReturn(0.123f)
@@ -325,20 +323,6 @@ class NotificationShelfTest : SysuiTestCase() {
}
@Test
- fun updateState_flagFalse_largeScreen_expansionChanging_shelfAlphaUpdated_standardValue() {
- val expansionFraction = 0.6f
- whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(false)
- whenever(ambientState.isSmallScreen).thenReturn(false)
- whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
- .thenReturn(0.123f)
-
- updateState_expansionChanging_shelfAlphaUpdated(
- expansionFraction = expansionFraction,
- expectedAlpha = ShadeInterpolation.getContentAlpha(expansionFraction)
- )
- }
-
- @Test
fun updateState_expansionChangingWhileBouncerInTransit_shelfAlphaUpdated() {
whenever(ambientState.isBouncerInTransit).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index e12d179c5aa5..7b1565edac40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -9,7 +9,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.EmptyShadeView
import com.android.systemui.statusbar.NotificationShelf
@@ -160,12 +159,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
@Test
- fun resetViewStates_flagTrue_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
+ fun resetViewStates_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
val expansionFraction = 0.6f
val surfaceAlpha = 123f
ambientState.isSmallScreen = false
- whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(true)
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
.thenReturn(surfaceAlpha)
@@ -177,23 +174,6 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
@Test
- fun resetViewStates_flagFalse_largeScreen_expansionChanging_alphaUpdated_standardValue() {
- val expansionFraction = 0.6f
- val surfaceAlpha = 123f
- ambientState.isSmallScreen = false
- whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(false)
- whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
- whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
- .thenReturn(surfaceAlpha)
-
- resetViewStates_expansionChanging_notificationAlphaUpdated(
- expansionFraction = expansionFraction,
- expectedAlpha = getContentAlpha(expansionFraction),
- )
- }
-
- @Test
fun expansionChanging_largeScreen_bouncerInTransit_alphaUpdated_bouncerValues() {
ambientState.isSmallScreen = false
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 044dd582192e..ab801588575d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -61,7 +61,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
@@ -697,9 +696,7 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- public void transitionToUnlocked_nonClippedQs_flagTrue_followsLargeScreensInterpolator() {
- when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(true);
+ public void transitionToUnlocked_nonClippedQs_followsLargeScreensInterpolator() {
mScrimController.setClipsQsScrim(false);
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -737,48 +734,6 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimBehind, OPAQUE));
}
-
- @Test
- public void transitionToUnlocked_nonClippedQs_flagFalse() {
- when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
- .thenReturn(false);
- mScrimController.setClipsQsScrim(false);
- mScrimController.setRawPanelExpansionFraction(0f);
- mScrimController.transitionTo(ScrimState.UNLOCKED);
- finishAnimationsImmediately();
- assertScrimAlpha(Map.of(
- mScrimInFront, TRANSPARENT,
- mNotificationsScrim, TRANSPARENT,
- mScrimBehind, TRANSPARENT));
-
- assertScrimTinted(Map.of(
- mNotificationsScrim, false,
- mScrimInFront, false,
- mScrimBehind, true
- ));
-
- // Back scrim should be visible after start dragging
- mScrimController.setRawPanelExpansionFraction(0.29f);
- assertScrimAlpha(Map.of(
- mScrimInFront, TRANSPARENT,
- mNotificationsScrim, TRANSPARENT,
- mScrimBehind, SEMI_TRANSPARENT));
-
- // Back scrim should be opaque at 30%
- mScrimController.setRawPanelExpansionFraction(0.3f);
- assertScrimAlpha(Map.of(
- mScrimInFront, TRANSPARENT,
- mNotificationsScrim, TRANSPARENT,
- mScrimBehind, OPAQUE));
-
- // Then, notification scrim should fade in
- mScrimController.setRawPanelExpansionFraction(0.31f);
- assertScrimAlpha(Map.of(
- mScrimInFront, TRANSPARENT,
- mNotificationsScrim, SEMI_TRANSPARENT,
- mScrimBehind, OPAQUE));
- }
-
@Test
public void scrimStateCallback() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index 487d26d21824..a797e032a353 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -346,4 +345,17 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest {
assertEquals(HeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
mUiEventLoggerFake.eventId(0));
}
+
+ @Test
+ public void testSetUserActionMayIndirectlyRemove() {
+ NotificationEntry notifEntry = new NotificationEntryBuilder()
+ .setSbn(createNewNotification(/* id= */ 0))
+ .build();
+
+ mHeadsUpManager.showNotification(notifEntry);
+ assertFalse(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+
+ mHeadsUpManager.setUserActionMayIndirectlyRemove(notifEntry);
+ assertTrue(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
index 9179efc9f39f..e470406499b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiBaseFragmentTest.java
@@ -57,8 +57,7 @@ public abstract class SysuiBaseFragmentTest extends BaseFragmentTest {
@Before
public void sysuiSetup() throws ExecutionException, InterruptedException {
- SystemUIInitializer initializer =
- SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+ SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
initializer.init(true);
mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
Dependency.setInstance(mDependency);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 8bbd58dc8fe1..de177168e20f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -90,8 +90,7 @@ public abstract class SysuiTestCase {
if (isRobolectricTest()) {
mContext = mContext.createDefaultDisplayContext();
}
- SystemUIInitializer initializer =
- SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+ SystemUIInitializer initializer = new SystemUIInitializerImpl(mContext);
initializer.init(true);
mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
Dependency.setInstance(mDependency);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
index 15465f4d40fe..3334f3e82c59 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/power/data/repository/FakePowerRepository.kt
@@ -17,6 +17,7 @@
package com.android.systemui.power.data.repository
+import android.os.PowerManager
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -28,7 +29,15 @@ class FakePowerRepository(
private val _isInteractive = MutableStateFlow(initialInteractive)
override val isInteractive: Flow<Boolean> = _isInteractive.asStateFlow()
+ var lastWakeWhy: String? = null
+ var lastWakeReason: Int? = null
+
fun setInteractive(value: Boolean) {
_isInteractive.value = value
}
+
+ override fun wakeUp(why: String, @PowerManager.WakeReason wakeReason: Int) {
+ lastWakeWhy = why
+ lastWakeReason = wakeReason
+ }
}
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 6b55d7ed4d56..cd2f844294e3 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -987,8 +987,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
"Virtual device doesn't have a virtual display with ID " + displayId);
}
- releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
-
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ releaseOwnedVirtualDisplayResources(virtualDisplayWrapper);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
/**
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index cfd9f16541f2..ca1ab9bbfebc 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -805,20 +805,7 @@ public class ContentCaptureManagerService extends
@Nullable
private ComponentName getContentProtectionServiceComponentName() {
String flatComponentName = getContentProtectionServiceFlatComponentName();
- ComponentName componentName = ComponentName.unflattenFromString(flatComponentName);
- if (componentName == null) {
- return null;
- }
-
- // Check permissions by trying to construct {@link ContentCaptureServiceInfo}
- try {
- createContentProtectionServiceInfo(componentName);
- } catch (Exception ex) {
- // Swallow, exception was already logged
- return null;
- }
-
- return componentName;
+ return ComponentName.unflattenFromString(flatComponentName);
}
/** @hide */
@@ -853,6 +840,15 @@ public class ContentCaptureManagerService extends
return null;
}
}
+
+ // Check permissions by trying to construct {@link ContentCaptureServiceInfo}
+ try {
+ createContentProtectionServiceInfo(mContentProtectionServiceComponentName);
+ } catch (Exception ex) {
+ // Swallow, exception was already logged
+ return null;
+ }
+
return createRemoteContentProtectionService(mContentProtectionServiceComponentName);
}
diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
index 2812233815a6..99178920cc52 100644
--- a/services/core/java/com/android/server/WallpaperUpdateReceiver.java
+++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java
@@ -88,7 +88,7 @@ public class WallpaperUpdateReceiver extends BroadcastReceiver {
} else {
//live wallpaper
ComponentName currCN = info.getComponent();
- ComponentName defaultCN = WallpaperManager.getCmfDefaultWallpaperComponent(context);
+ ComponentName defaultCN = WallpaperManager.getDefaultWallpaperComponent(context);
if (!currCN.equals(defaultCN)) {
return true;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ec5512f57a9d..6d55058837d3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1616,6 +1616,8 @@ public class ActivityManagerService extends IActivityManager.Stub
static final int SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG = 77;
static final int SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG = 78;
static final int UPDATE_CACHED_APP_HIGH_WATERMARK = 79;
+ static final int ADD_UID_TO_OBSERVER_MSG = 80;
+ static final int REMOVE_UID_FROM_OBSERVER_MSG = 81;
static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1774,6 +1776,12 @@ public class ActivityManagerService extends IActivityManager.Stub
case PUSH_TEMP_ALLOWLIST_UI_MSG: {
pushTempAllowlist();
} break;
+ case ADD_UID_TO_OBSERVER_MSG: {
+ mUidObserverController.addUidToObserverImpl((IBinder) msg.obj, msg.arg1);
+ } break;
+ case REMOVE_UID_FROM_OBSERVER_MSG: {
+ mUidObserverController.removeUidFromObserverImpl((IBinder) msg.obj, msg.arg1);
+ } break;
}
}
}
@@ -13785,7 +13793,8 @@ public class ActivityManagerService extends IActivityManager.Stub
if (action.startsWith("android.intent.action.USER_")
|| action.startsWith("android.intent.action.PACKAGE_")
|| action.startsWith("android.intent.action.UID_")
- || action.startsWith("android.intent.action.EXTERNAL_")) {
+ || action.startsWith("android.intent.action.EXTERNAL_")
+ || action.startsWith("android.bluetooth.")) {
if (DEBUG_BROADCAST) {
Slog.wtf(TAG,
"System internals registering for " + filter.toLongString()
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index a6677a5185ca..7eeec32cf24a 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -30,6 +30,7 @@ import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -104,40 +105,62 @@ public class UidObserverController {
}
}
- void addUidToObserver(@NonNull IBinder observerToken, int uid) {
- synchronized (mLock) {
- int i = mUidObservers.beginBroadcast();
- while (i-- > 0) {
- var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
- if (reg.getToken().equals(observerToken)) {
- reg.addUid(uid);
- break;
- }
+ final void addUidToObserver(@NonNull IBinder observerToken, int uid) {
+ Message msg = Message.obtain(mHandler, ActivityManagerService.ADD_UID_TO_OBSERVER_MSG,
+ uid, /*arg2*/ 0, observerToken);
+ mHandler.sendMessage(msg);
+ }
- if (i == 0) {
- Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
- }
+ /**
+ * Add a uid to the list of uids an observer is interested in. Must be run on the same thread
+ * as mDispatchRunnable.
+ *
+ * @param observerToken The token identifier for a UidObserver
+ * @param uid The uid to add to the list of watched uids
+ */
+ public final void addUidToObserverImpl(@NonNull IBinder observerToken, int uid) {
+ int i = mUidObservers.beginBroadcast();
+ while (i-- > 0) {
+ var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
+ if (reg.getToken().equals(observerToken)) {
+ reg.addUid(uid);
+ break;
+ }
+
+ if (i == 0) {
+ Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
}
- mUidObservers.finishBroadcast();
}
+ mUidObservers.finishBroadcast();
}
- void removeUidFromObserver(@NonNull IBinder observerToken, int uid) {
- synchronized (mLock) {
- int i = mUidObservers.beginBroadcast();
- while (i-- > 0) {
- var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
- if (reg.getToken().equals(observerToken)) {
- reg.removeUid(uid);
- break;
- }
+ final void removeUidFromObserver(@NonNull IBinder observerToken, int uid) {
+ Message msg = Message.obtain(mHandler, ActivityManagerService.REMOVE_UID_FROM_OBSERVER_MSG,
+ uid, /*arg2*/ 0, observerToken);
+ mHandler.sendMessage(msg);
+ }
- if (i == 0) {
- Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
- }
+ /**
+ * Remove a uid from the list of uids an observer is interested in. Must be run on the same
+ * thread as mDispatchRunnable.
+ *
+ * @param observerToken The token identifier for a UidObserver
+ * @param uid The uid to remove from the list of watched uids
+ */
+ public final void removeUidFromObserverImpl(@NonNull IBinder observerToken, int uid) {
+ int i = mUidObservers.beginBroadcast();
+ while (i-- > 0) {
+ var reg = (UidObserverRegistration) mUidObservers.getBroadcastCookie(i);
+ if (reg.getToken().equals(observerToken)) {
+ reg.removeUid(uid);
+ break;
+ }
+
+ if (i == 0) {
+ Slog.e(TAG_UID_OBSERVERS, "Unable to find UidObserver by token");
}
- mUidObservers.finishBroadcast();
}
+ mUidObservers.finishBroadcast();
}
int enqueueUidChange(@Nullable ChangeRecord currentRecord, int uid, int change, int procState,
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 1f8ff73b2d5d..fc72a771fa9c 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2214,6 +2214,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
mDeviceInventory.removePreferredDevicesForStrategyInt(mAccessibilityStrategyId);
}
mDeviceInventory.applyConnectedDevicesRoles();
+ mDeviceInventory.reapplyExternalDevicesRoles();
} else {
mDeviceInventory.setPreferredDevicesForStrategyInt(
mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index d1cae490a31d..219dda398ebf 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -361,23 +361,34 @@ public class AudioDeviceInventory {
AudioSystem.DEVICE_STATE_AVAILABLE,
di.mDeviceCodecFormat);
}
- mAppliedStrategyRoles.clear();
mAppliedStrategyRolesInt.clear();
- mAppliedPresetRoles.clear();
mAppliedPresetRolesInt.clear();
applyConnectedDevicesRoles_l();
}
+ reapplyExternalDevicesRoles();
+ }
+
+ /*package*/ void reapplyExternalDevicesRoles() {
+ synchronized (mDevicesLock) {
+ mAppliedStrategyRoles.clear();
+ mAppliedPresetRoles.clear();
+ }
synchronized (mPreferredDevices) {
mPreferredDevices.forEach((strategy, devices) -> {
- setPreferredDevicesForStrategy(strategy, devices); });
+ setPreferredDevicesForStrategy(strategy, devices);
+ });
}
synchronized (mNonDefaultDevices) {
mNonDefaultDevices.forEach((strategy, devices) -> {
addDevicesRoleForStrategy(strategy, AudioSystem.DEVICE_ROLE_DISABLED,
- devices, false /* internal */); });
+ devices, false /* internal */);
+ });
}
synchronized (mPreferredDevicesForCapturePreset) {
- // TODO: call audiosystem to restore
+ mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
+ setDevicesRoleForCapturePreset(
+ capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices);
+ });
}
}
@@ -1163,6 +1174,7 @@ public class AudioDeviceInventory {
return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); });
purgeRoles(mAppliedPresetRolesInt, (p, r, d) -> {
return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); });
+ reapplyExternalDevicesRoles();
}
@GuardedBy("mDevicesLock")
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 8ef2a1bd26c2..cb5e7f1be571 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -21,7 +21,10 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
+import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_CONVENIENCE;
+import static com.android.server.biometrics.BiometricSensor.STATE_CANCELING;
+import static com.android.server.biometrics.BiometricSensor.STATE_UNKNOWN;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE;
@@ -439,6 +442,13 @@ public final class AuthSession implements IBinder.DeathRecipient {
return false;
}
+ final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
+ || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
+ if (errorLockout) {
+ cancelAllSensors(sensor -> Utils.isAtLeastStrength(sensorIdToStrength(sensorId),
+ sensor.getCurrentStrength()));
+ }
+
mErrorEscrow = error;
mVendorCodeEscrow = vendorCode;
@@ -477,8 +487,6 @@ public final class AuthSession implements IBinder.DeathRecipient {
case STATE_AUTH_STARTED:
case STATE_AUTH_STARTED_UI_SHOWING: {
- final boolean errorLockout = error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT
- || error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
if (isAllowDeviceCredential() && errorLockout) {
// SystemUI handles transition from biometric to device credential.
mState = STATE_SHOWING_DEVICE_CREDENTIAL;
@@ -675,7 +683,9 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
private boolean pauseSensorIfSupported(int sensorId) {
- if (sensorIdToModality(sensorId) == TYPE_FACE) {
+ boolean isSensorCancelling = sensorIdToState(sensorId) == STATE_CANCELING;
+ // If the sensor is locked out, canceling sensors operation is handled in onErrorReceived()
+ if (sensorIdToModality(sensorId) == TYPE_FACE && !isSensorCancelling) {
cancelAllSensors(sensor -> sensor.id == sensorId);
return true;
}
@@ -948,6 +958,27 @@ public final class AuthSession implements IBinder.DeathRecipient {
return TYPE_NONE;
}
+ private @BiometricSensor.SensorState int sensorIdToState(int sensorId) {
+ for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+ if (sensorId == sensor.id) {
+ return sensor.getSensorState();
+ }
+ }
+ Slog.e(TAG, "Unknown sensor: " + sensorId);
+ return STATE_UNKNOWN;
+ }
+
+ @BiometricManager.Authenticators.Types
+ private int sensorIdToStrength(int sensorId) {
+ for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
+ if (sensorId == sensor.id) {
+ return sensor.getCurrentStrength();
+ }
+ }
+ Slog.e(TAG, "Unknown sensor: " + sensorId);
+ return BIOMETRIC_CONVENIENCE;
+ }
+
private String getAcquiredMessageForSensor(int sensorId, int acquiredInfo, int vendorCode) {
final @Modality int modality = sensorIdToModality(sensorId);
switch (modality) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
new file mode 100644
index 000000000000..6727fbcdec66
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacy.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+/**
+ * Interface for biometric operations to get camera privacy state.
+ */
+public interface BiometricSensorPrivacy {
+ /* Returns true if privacy is enabled and camera access is disabled. */
+ boolean isCameraPrivacyEnabled();
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java
new file mode 100644
index 000000000000..b6701da1d348
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/BiometricSensorPrivacyImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import android.annotation.Nullable;
+import android.hardware.SensorPrivacyManager;
+
+public class BiometricSensorPrivacyImpl implements
+ BiometricSensorPrivacy {
+ private final SensorPrivacyManager mSensorPrivacyManager;
+
+ public BiometricSensorPrivacyImpl(@Nullable SensorPrivacyManager sensorPrivacyManager) {
+ mSensorPrivacyManager = sensorPrivacyManager;
+ }
+
+ @Override
+ public boolean isCameraPrivacyEnabled() {
+ return mSensorPrivacyManager != null && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 0942d8527565..1fa97a3cb97f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -33,6 +33,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
@@ -124,6 +125,8 @@ public class BiometricService extends SystemService {
AuthSession mAuthSession;
private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final BiometricSensorPrivacy mBiometricSensorPrivacy;
+
/**
* Tracks authenticatorId invalidation. For more details, see
* {@link com.android.server.biometrics.sensors.InvalidationRequesterClient}.
@@ -933,7 +936,7 @@ public class BiometricService extends SystemService {
return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
- getContext());
+ getContext(), mBiometricSensorPrivacy);
}
/**
@@ -1026,6 +1029,11 @@ public class BiometricService extends SystemService {
public UserManager getUserManager(Context context) {
return context.getSystemService(UserManager.class);
}
+
+ public BiometricSensorPrivacy getBiometricSensorPrivacy(Context context) {
+ return new BiometricSensorPrivacyImpl(context.getSystemService(
+ SensorPrivacyManager.class));
+ }
}
/**
@@ -1054,6 +1062,7 @@ public class BiometricService extends SystemService {
mRequestCounter = mInjector.getRequestGenerator();
mBiometricContext = injector.getBiometricContext(context);
mUserManager = injector.getUserManager(context);
+ mBiometricSensorPrivacy = injector.getBiometricSensorPrivacy(context);
try {
injector.getActivityManagerService().registerUserSwitchObserver(
@@ -1290,7 +1299,7 @@ public class BiometricService extends SystemService {
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
- getContext());
+ getContext(), mBiometricSensorPrivacy);
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
@@ -1300,9 +1309,7 @@ public class BiometricService extends SystemService {
+ promptInfo.isIgnoreEnrollmentState());
// BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can
// be shown for this case.
- if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS
- || preAuthStatus.second
- == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) {
+ if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
// If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
// CREDENTIAL is requested and available, set the bundle to only request
// CREDENTIAL.
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 3813fd1971a6..44e3d1ede19c 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -27,7 +27,6 @@ import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.Context;
-import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.PromptInfo;
@@ -73,13 +72,16 @@ class PreAuthInfo {
final Context context;
private final boolean mBiometricRequested;
private final int mBiometricStrengthRequested;
+ private final BiometricSensorPrivacy mBiometricSensorPrivacy;
+
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
- Context context) {
+ Context context, BiometricSensorPrivacy biometricSensorPrivacy) {
mBiometricRequested = biometricRequested;
mBiometricStrengthRequested = biometricStrengthRequested;
+ mBiometricSensorPrivacy = biometricSensorPrivacy;
this.credentialRequested = credentialRequested;
this.eligibleSensors = eligibleSensors;
@@ -96,7 +98,8 @@ class PreAuthInfo {
BiometricService.SettingObserver settingObserver,
List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName,
- boolean checkDevicePolicyManager, Context context)
+ boolean checkDevicePolicyManager, Context context,
+ BiometricSensorPrivacy biometricSensorPrivacy)
throws RemoteException {
final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -124,7 +127,7 @@ class PreAuthInfo {
checkDevicePolicyManager, requestedStrength,
promptInfo.getAllowedSensorIds(),
promptInfo.isIgnoreEnrollmentState(),
- context);
+ biometricSensorPrivacy);
Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id
@@ -138,7 +141,7 @@ class PreAuthInfo {
//
// Note: if only a certain sensor is required and the privacy is enabled,
// canAuthenticate() will return false.
- if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) {
+ if (status == AUTHENTICATOR_OK) {
eligibleSensors.add(sensor);
} else {
ineligibleSensors.add(new Pair<>(sensor, status));
@@ -148,7 +151,7 @@ class PreAuthInfo {
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
- promptInfo.isIgnoreEnrollmentState(), userId, context);
+ promptInfo.isIgnoreEnrollmentState(), userId, context, biometricSensorPrivacy);
}
/**
@@ -165,7 +168,7 @@ class PreAuthInfo {
BiometricSensor sensor, int userId, String opPackageName,
boolean checkDevicePolicyManager, int requestedStrength,
@NonNull List<Integer> requestedSensorIds,
- boolean ignoreEnrollmentState, Context context) {
+ boolean ignoreEnrollmentState, BiometricSensorPrivacy biometricSensorPrivacy) {
if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
return BIOMETRIC_NO_HARDWARE;
@@ -191,12 +194,10 @@ class PreAuthInfo {
&& !ignoreEnrollmentState) {
return BIOMETRIC_NOT_ENROLLED;
}
- final SensorPrivacyManager sensorPrivacyManager = context
- .getSystemService(SensorPrivacyManager.class);
- if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) {
- if (sensorPrivacyManager
- .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) {
+ if (biometricSensorPrivacy != null && sensor.modality == TYPE_FACE) {
+ if (biometricSensorPrivacy.isCameraPrivacyEnabled()) {
+ //Camera privacy is enabled as the access is disabled
return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
}
}
@@ -292,13 +293,9 @@ class PreAuthInfo {
@AuthenticatorStatus final int status;
@BiometricAuthenticator.Modality int modality = TYPE_NONE;
- final SensorPrivacyManager sensorPrivacyManager = context
- .getSystemService(SensorPrivacyManager.class);
-
boolean cameraPrivacyEnabled = false;
- if (sensorPrivacyManager != null) {
- cameraPrivacyEnabled = sensorPrivacyManager
- .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId);
+ if (mBiometricSensorPrivacy != null) {
+ cameraPrivacyEnabled = mBiometricSensorPrivacy.isCameraPrivacyEnabled();
}
if (mBiometricRequested && credentialRequested) {
@@ -315,7 +312,7 @@ class PreAuthInfo {
// and the face sensor privacy is enabled then return
// BIOMETRIC_SENSOR_PRIVACY_ENABLED.
//
- // Note: This sensor will still be eligible for calls to authenticate.
+ // Note: This sensor will not be eligible for calls to authenticate.
status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
} else {
status = AUTHENTICATOR_OK;
@@ -340,7 +337,7 @@ class PreAuthInfo {
// If the only modality requested is face and the privacy is enabled
// then return BIOMETRIC_SENSOR_PRIVACY_ENABLED.
//
- // Note: This sensor will still be eligible for calls to authenticate.
+ // Note: This sensor will not be eligible for calls to authenticate.
status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
} else {
status = AUTHENTICATOR_OK;
diff --git a/services/core/java/com/android/server/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java
index 92bbfab63b42..984ad1dd7288 100644
--- a/services/core/java/com/android/server/cpu/CpuInfoReader.java
+++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java
@@ -593,9 +593,12 @@ public final class CpuInfoReader {
List<String> lines = Files.readAllLines(file.toPath());
IntArray cpuCores = new IntArray(0);
for (int i = 0; i < lines.size(); i++) {
- String line = lines.get(i);
- String[] pairs = line.contains(",") ? line.trim().split(",")
- : line.trim().split(" ");
+ String line = lines.get(i).trim();
+ if (line.isEmpty()) {
+ continue;
+ }
+ String[] pairs = line.contains(",") ? line.split(",")
+ : line.split(" ");
for (int j = 0; j < pairs.length; j++) {
String[] minMaxPairs = pairs[j].split("-");
if (minMaxPairs.length >= 2) {
@@ -615,6 +618,9 @@ public final class CpuInfoReader {
}
}
return cpuCores;
+ } catch (NumberFormatException e) {
+ Slogf.e(TAG, e, "Failed to read CPU cores from %s due to incorrect file format",
+ file.getAbsolutePath());
} catch (Exception e) {
Slogf.e(TAG, e, "Failed to read CPU cores from %s", file.getAbsolutePath());
}
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index ff9ce6f16075..38a0d37c5679 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -46,7 +46,6 @@ import android.view.InputDevice;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -102,8 +101,9 @@ final class BatteryController {
@GuardedBy("mLock")
private BluetoothBatteryManager.BluetoothBatteryListener mBluetoothBatteryListener;
- BatteryController(Context context, NativeInputManagerService nativeService, Looper looper) {
- this(context, nativeService, looper, new UEventManager() {},
+ BatteryController(Context context, NativeInputManagerService nativeService, Looper looper,
+ UEventManager uEventManager) {
+ this(context, nativeService, looper, uEventManager,
new LocalBluetoothBatteryManager(context, looper));
}
@@ -567,7 +567,7 @@ final class BatteryController {
private BluetoothAdapter.OnMetadataChangedListener mBluetoothMetadataListener;
@Nullable
- private UEventBatteryListener mUEventBatteryListener;
+ private BatteryController.UEventBatteryListener mUEventBatteryListener;
DeviceMonitor(int deviceId) {
mState = new State(deviceId);
@@ -630,7 +630,7 @@ final class BatteryController {
return;
}
final int deviceId = mState.deviceId;
- mUEventBatteryListener = new UEventBatteryListener() {
+ mUEventBatteryListener = new BatteryController.UEventBatteryListener() {
@Override
public void onBatteryUEvent(long eventTime) {
handleUEventNotification(deviceId, eventTime);
@@ -898,40 +898,25 @@ final class BatteryController {
}
}
- // An interface used to change the API of UEventObserver to a more test-friendly format.
@VisibleForTesting
- interface UEventManager {
-
- @VisibleForTesting
- abstract class UEventBatteryListener {
- private final UEventObserver mObserver = new UEventObserver() {
- @Override
- public void onUEvent(UEvent event) {
- final long eventTime = SystemClock.uptimeMillis();
- if (DEBUG) {
- Slog.d(TAG,
- "UEventListener: Received UEvent: "
- + event + " eventTime: " + eventTime);
- }
- if (!"CHANGE".equalsIgnoreCase(event.get("ACTION"))
- || !"POWER_SUPPLY".equalsIgnoreCase(event.get("SUBSYSTEM"))) {
- // Disregard any UEvents that do not correspond to battery changes.
- return;
- }
- UEventBatteryListener.this.onBatteryUEvent(eventTime);
- }
- };
-
- public abstract void onBatteryUEvent(long eventTime);
- }
-
- default void addListener(UEventBatteryListener listener, String match) {
- listener.mObserver.startObserving(match);
+ abstract static class UEventBatteryListener extends UEventManager.UEventListener {
+ @Override
+ public void onUEvent(UEventObserver.UEvent event) {
+ final long eventTime = SystemClock.uptimeMillis();
+ if (DEBUG) {
+ Slog.d(TAG,
+ "UEventListener: Received UEvent: "
+ + event + " eventTime: " + eventTime);
+ }
+ if (!"CHANGE".equalsIgnoreCase(event.get("ACTION"))
+ || !"POWER_SUPPLY".equalsIgnoreCase(event.get("SUBSYSTEM"))) {
+ // Disregard any UEvents that do not correspond to battery changes.
+ return;
+ }
+ UEventBatteryListener.this.onBatteryUEvent(eventTime);
}
- default void removeListener(UEventBatteryListener listener) {
- listener.mObserver.stopObserving();
- }
+ public abstract void onBatteryUEvent(long eventTime);
}
// An interface used to change the API of adding a bluetooth battery listener to a more
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index c7b80af27d79..6a177e03102f 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -393,10 +393,12 @@ public class InputManagerService extends IInputManager.Stub
static class Injector {
private final Context mContext;
private final Looper mLooper;
+ private final UEventManager mUEventManager;
- Injector(Context context, Looper looper) {
+ Injector(Context context, Looper looper, UEventManager uEventManager) {
mContext = context;
mLooper = looper;
+ mUEventManager = uEventManager;
}
Context getContext() {
@@ -407,6 +409,10 @@ public class InputManagerService extends IInputManager.Stub
return mLooper;
}
+ UEventManager getUEventManager() {
+ return mUEventManager;
+ }
+
NativeInputManagerService getNativeService(InputManagerService service) {
return new NativeInputManagerService.NativeImpl(service, mLooper.getQueue());
}
@@ -417,7 +423,7 @@ public class InputManagerService extends IInputManager.Stub
}
public InputManagerService(Context context) {
- this(new Injector(context, DisplayThread.get().getLooper()));
+ this(new Injector(context, DisplayThread.get().getLooper(), new UEventManager() {}));
}
@VisibleForTesting
@@ -432,10 +438,12 @@ public class InputManagerService extends IInputManager.Stub
mSettingsObserver = new InputSettingsObserver(mContext, mHandler, this, mNative);
mKeyboardLayoutManager = new KeyboardLayoutManager(mContext, mNative, mDataStore,
injector.getLooper());
- mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
+ mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(),
+ injector.getUEventManager());
mKeyboardBacklightController = InputFeatureFlagProvider.isKeyboardBacklightControlEnabled()
? new KeyboardBacklightController(mContext, mNative, mDataStore,
- injector.getLooper()) : new KeyboardBacklightControllerInterface() {};
+ injector.getLooper(), injector.getUEventManager())
+ : new KeyboardBacklightControllerInterface() {};
mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
mUseDevInputEventForAudioJack =
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index 1253b5b5b303..c3205afe14f2 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -94,6 +94,7 @@ final class KeyboardBacklightController implements
private final PersistentDataStore mDataStore;
private final Handler mHandler;
private final AnimatorFactory mAnimatorFactory;
+ private final UEventManager mUEventManager;
// Always access on handler thread or need to lock this for synchronization.
private final SparseArray<KeyboardBacklightState> mKeyboardBacklights = new SparseArray<>(1);
// Maintains state if all backlights should be on or turned off
@@ -123,19 +124,21 @@ final class KeyboardBacklightController implements
}
KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
- PersistentDataStore dataStore, Looper looper) {
- this(context, nativeService, dataStore, looper, ValueAnimator::ofInt);
+ PersistentDataStore dataStore, Looper looper, UEventManager uEventManager) {
+ this(context, nativeService, dataStore, looper, ValueAnimator::ofInt, uEventManager);
}
@VisibleForTesting
KeyboardBacklightController(Context context, NativeInputManagerService nativeService,
- PersistentDataStore dataStore, Looper looper, AnimatorFactory animatorFactory) {
+ PersistentDataStore dataStore, Looper looper, AnimatorFactory animatorFactory,
+ UEventManager uEventManager) {
mContext = context;
mNative = nativeService;
mDataStore = dataStore;
mHandler = new Handler(looper, this::handleMessage);
mAnimatorFactory = animatorFactory;
mAmbientController = new AmbientKeyboardBacklightController(context, looper);
+ mUEventManager = uEventManager;
}
@Override
@@ -152,13 +155,12 @@ final class KeyboardBacklightController implements
// We want to observe creation of such LED nodes since they might be created after device
// FD created and InputDevice creation logic doesn't initialize LED nodes which leads to
// backlight not working.
- UEventObserver observer = new UEventObserver() {
+ mUEventManager.addListener(new UEventManager.UEventListener() {
@Override
- public void onUEvent(UEvent event) {
+ public void onUEvent(UEventObserver.UEvent event) {
onKeyboardBacklightUEvent(event);
}
- };
- observer.startObserving(UEVENT_KEYBOARD_BACKLIGHT_TAG);
+ }, UEVENT_KEYBOARD_BACKLIGHT_TAG);
if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
// Start ambient backlight controller
diff --git a/services/core/java/com/android/server/input/UEventManager.java b/services/core/java/com/android/server/input/UEventManager.java
new file mode 100644
index 000000000000..17d87e4f0596
--- /dev/null
+++ b/services/core/java/com/android/server/input/UEventManager.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.os.UEventObserver;
+
+/** An interface used to change the API of UEventObserver to a more test-friendly format. */
+interface UEventManager {
+
+ abstract class UEventListener {
+ private final UEventObserver mObserver = new UEventObserver() {
+ @Override
+ public void onUEvent(UEvent event) {
+ UEventListener.this.onUEvent(event);
+ }
+ };
+
+ public abstract void onUEvent(UEventObserver.UEvent event);
+ }
+
+ default void addListener(UEventListener listener, String match) {
+ listener.mObserver.startObserving(match);
+ }
+
+ default void removeListener(UEventListener listener) {
+ listener.mObserver.stopObserving();
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a6b1a6bd5832..498bc4c3605d 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2344,6 +2344,7 @@ public class NotificationManagerService extends SystemService {
mRankingHandler,
mZenModeHelper,
mPermissionHelper,
+ mPermissionManager,
mNotificationChannelLogger,
mAppOps,
new SysUiStatsEvent.BuilderFactory(),
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index c9a6c630d41b..0292a99f1122 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -27,6 +27,7 @@ import static android.service.notification.NotificationListenerService.Ranking.U
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.IActivityManager;
+import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.Person;
@@ -46,6 +47,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.PowerManager;
import android.os.UserHandle;
import android.os.VibrationEffect;
import android.provider.Settings;
@@ -101,7 +103,8 @@ public final class NotificationRecord {
final int mTargetSdkVersion;
final int mOriginalFlags;
private final Context mContext;
-
+ private final KeyguardManager mKeyguardManager;
+ private final PowerManager mPowerManager;
NotificationUsageStats.SingleNotificationStats stats;
boolean isCanceled;
IBinder permissionOwner;
@@ -228,6 +231,8 @@ public final class NotificationRecord {
mUpdateTimeMs = mCreationTimeMs;
mInterruptionTimeMs = mCreationTimeMs;
mContext = context;
+ mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+ mPowerManager = mContext.getSystemService(PowerManager.class);
stats = new NotificationUsageStats.SingleNotificationStats();
mChannel = channel;
mPreChannelsNotification = isPreChannelsNotification();
@@ -1619,6 +1624,11 @@ public final class NotificationRecord {
return mPhoneNumbers;
}
+ boolean isLocked() {
+ return mKeyguardManager.isKeyguardLocked()
+ || !mPowerManager.isInteractive(); // Unlocked AOD
+ }
+
@VisibleForTesting
static final class Light {
public final int color;
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 0cc4fc4e0516..5ca882cc1bb9 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -31,9 +31,12 @@ import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
import java.util.ArrayList;
import java.util.Objects;
@@ -500,6 +503,8 @@ interface NotificationRecordLogger {
final boolean is_foreground_service;
final long timeout_millis;
final boolean is_non_dismissible;
+ final int fsi_state;
+ final boolean is_locked;
@DurationMillisLong long post_duration_millis; // Not final; calculated at the end.
NotificationReported(NotificationRecordPair p,
@@ -530,6 +535,20 @@ interface NotificationRecordLogger {
this.is_foreground_service = NotificationRecordLogger.isForegroundService(p.r);
this.timeout_millis = p.r.getSbn().getNotification().getTimeoutAfter();
this.is_non_dismissible = NotificationRecordLogger.isNonDismissible(p.r);
+
+ final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
+ .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
+
+ final boolean hasFullScreenIntent =
+ p.r.getSbn().getNotification().fullScreenIntent != null;
+
+ final boolean hasFsiRequestedButDeniedFlag = (p.r.getSbn().getNotification().flags
+ & Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0;
+
+ this.fsi_state = NotificationRecordLogger.getFsiState(isStickyHunFlagEnabled,
+ hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType);
+
+ this.is_locked = p.r.isLocked();
}
}
@@ -558,7 +577,6 @@ interface NotificationRecordLogger {
}
/**
- * @param r NotificationRecord
* @return Whether the notification is a non-dismissible notification.
*/
static boolean isNonDismissible(@NonNull NotificationRecord r) {
@@ -567,4 +585,28 @@ interface NotificationRecordLogger {
}
return (r.getNotification().flags & Notification.FLAG_NO_DISMISS) != 0;
}
+
+ /**
+ * @return FrameworkStatsLog enum of the state of the full screen intent posted with this
+ * notification.
+ */
+ static int getFsiState(boolean isStickyHunFlagEnabled,
+ boolean hasFullScreenIntent,
+ boolean hasFsiRequestedButDeniedFlag,
+ NotificationReportedEvent eventType) {
+
+ if (!isStickyHunFlagEnabled
+ || eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) {
+ // Zeroes in protos take zero bandwidth, but non-zero numbers take bandwidth,
+ // so we should log 0 when possible.
+ return 0;
+ }
+ if (hasFullScreenIntent) {
+ return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_ALLOWED;
+ }
+ if (hasFsiRequestedButDeniedFlag) {
+ return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_DENIED;
+ }
+ return FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__NO_FSI;
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index feb75efe25f6..9da0e98c1775 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -75,7 +75,9 @@ class NotificationRecordLoggerImpl implements NotificationRecordLogger {
notificationReported.is_foreground_service,
notificationReported.timeout_millis,
notificationReported.is_non_dismissible,
- notificationReported.post_duration_millis);
+ notificationReported.post_duration_millis,
+ notificationReported.fsi_state,
+ notificationReported.is_locked);
}
@Override
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 4399a3ca46c5..838a0fb63a35 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -30,6 +30,9 @@ import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -41,6 +44,7 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.content.AttributionSource;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
@@ -53,6 +57,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
+import android.permission.PermissionManager;
import android.provider.Settings;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.NotificationListenerService;
@@ -72,6 +77,8 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
@@ -183,6 +190,7 @@ public class PreferencesHelper implements RankingConfig {
private final RankingHandler mRankingHandler;
private final ZenModeHelper mZenModeHelper;
private final PermissionHelper mPermissionHelper;
+ private final PermissionManager mPermissionManager;
private final NotificationChannelLogger mNotificationChannelLogger;
private final AppOpsManager mAppOps;
@@ -198,7 +206,7 @@ public class PreferencesHelper implements RankingConfig {
private boolean mAllowInvalidShortcuts = false;
public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
- ZenModeHelper zenHelper, PermissionHelper permHelper,
+ ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager,
SysUiStatsEvent.BuilderFactory statsEventBuilderFactory,
@@ -207,6 +215,7 @@ public class PreferencesHelper implements RankingConfig {
mZenModeHelper = zenHelper;
mRankingHandler = rankingHandler;
mPermissionHelper = permHelper;
+ mPermissionManager = permManager;
mPm = pm;
mNotificationChannelLogger = notificationChannelLogger;
mAppOps = appOpsManager;
@@ -2027,6 +2036,43 @@ public class PreferencesHelper implements RankingConfig {
}
/**
+ * @return State of the full screen intent permission for this package.
+ */
+ @VisibleForTesting
+ int getFsiState(String pkg, int uid, boolean requestedFSIPermission, boolean isFlagEnabled) {
+ if (!isFlagEnabled) {
+ return 0;
+ }
+ if (!requestedFSIPermission) {
+ return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+ }
+ final AttributionSource attributionSource =
+ new AttributionSource.Builder(uid).setPackageName(pkg).build();
+
+ final int result = mPermissionManager.checkPermissionForPreflight(
+ android.Manifest.permission.USE_FULL_SCREEN_INTENT, attributionSource);
+
+ if (result == PermissionManager.PERMISSION_GRANTED) {
+ return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
+ }
+ return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
+ }
+
+ /**
+ * @return True if the current full screen intent permission state for this package was set by
+ * the user.
+ */
+ @VisibleForTesting
+ boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags,
+ boolean isStickyHunFlagEnabled) {
+ if (!isStickyHunFlagEnabled
+ || fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) {
+ return false;
+ }
+ return (currentPermissionFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
+ }
+
+ /**
* Fills out {@link PackageNotificationPreferences} proto and wraps it in a {@link StatsEvent}.
*/
public void pullPackagePreferencesStats(List<StatsEvent> events,
@@ -2070,7 +2116,33 @@ public class PreferencesHelper implements RankingConfig {
event.writeInt(r.visibility);
event.writeInt(r.lockedAppFields);
- event.writeBoolean(importanceIsUserSet); // optional bool user_set_importance = 5;
+
+ // optional bool user_set_importance = 5;
+ event.writeBoolean(importanceIsUserSet);
+
+ // optional FsiState fsi_state = 6;
+ final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
+ .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
+
+ final boolean requestedFSIPermission = mPermissionHelper.hasRequestedPermission(
+ android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg, r.uid);
+
+ final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission,
+ isStickyHunFlagEnabled);
+
+ event.writeInt(fsiState);
+
+ // optional bool is_fsi_permission_user_set = 7;
+ final int currentPermissionFlags = mPm.getPermissionFlags(
+ android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg,
+ UserHandle.getUserHandleForUid(r.uid));
+
+ final boolean isUserSet =
+ isFsiPermissionUserSet(r.pkg, r.uid, fsiState, currentPermissionFlags,
+ isStickyHunFlagEnabled);
+
+ event.writeBoolean(isUserSet);
+
events.add(event.build());
}
}
diff --git a/services/core/java/com/android/server/notification/TEST_MAPPING b/services/core/java/com/android/server/notification/TEST_MAPPING
new file mode 100644
index 000000000000..59b2bc1e4f73
--- /dev/null
+++ b/services/core/java/com/android/server/notification/TEST_MAPPING
@@ -0,0 +1,49 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsNotificationTestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksUiServicesTests",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.LargeTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.LargeTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsNotificationTestCases"
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f2d1357ac1b5..aaf13ebeff2a 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3714,11 +3714,16 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
if (parser.getName().equals(TAG_PERMISSIONS)) {
final LegacyPermissionState legacyState;
if (ps.hasSharedUser()) {
- legacyState = getSettingLPr(ps.getSharedUserAppId()).getLegacyPermissionState();
+ final SettingBase sharedUserSettings = getSettingLPr(
+ ps.getSharedUserAppId());
+ legacyState = sharedUserSettings != null
+ ? sharedUserSettings.getLegacyPermissionState() : null;
} else {
legacyState = ps.getLegacyPermissionState();
}
- readInstallPermissionsLPr(parser, legacyState, users);
+ if (legacyState != null) {
+ readInstallPermissionsLPr(parser, legacyState, users);
+ }
} else if (parser.getName().equals(TAG_USES_STATIC_LIB)) {
readUsesStaticLibLPw(parser, ps);
} else if (parser.getName().equals(TAG_USES_SDK_LIB)) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index a020728cc85e..5b3514c01f9f 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2547,10 +2547,7 @@ public class ShortcutService extends IShortcutService.Stub {
enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
"getShareTargets");
final ComponentName chooser = injectChooserActivity();
- final String pkg = (chooser != null
- && mPackageManagerInternal.getComponentEnabledSetting(chooser,
- injectBinderCallingUid(), userId) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
- ? chooser.getPackageName() : mContext.getPackageName();
+ final String pkg = chooser != null ? chooser.getPackageName() : mContext.getPackageName();
synchronized (mLock) {
throwIfUserLockedL(userId);
final List<ShortcutManager.ShareShortcutInfo> shortcutInfoList = new ArrayList<>();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 3e7ae331d705..2499529f0fc0 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -141,6 +141,7 @@ import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.SharedUserApi;
import com.android.server.pm.pkg.component.ComponentMutateUtils;
import com.android.server.pm.pkg.component.ParsedPermission;
import com.android.server.pm.pkg.component.ParsedPermissionGroup;
@@ -4538,8 +4539,13 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
final int appId = ps.getAppId();
final LegacyPermissionState legacyState;
if (ps.hasSharedUser()) {
- legacyState = mPackageManagerInt.getSharedUserApi(
- ps.getSharedUserAppId()).getSharedUserLegacyPermissionState();
+ final int sharedUserId = ps.getSharedUserAppId();
+ SharedUserApi sharedUserApi = mPackageManagerInt.getSharedUserApi(sharedUserId);
+ if (sharedUserApi == null) {
+ Slog.wtf(TAG, "Missing shared user Api for " + sharedUserId);
+ return;
+ }
+ legacyState = sharedUserApi.getSharedUserLegacyPermissionState();
} else {
legacyState = ps.getLegacyPermissionState();
}
@@ -4584,8 +4590,13 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
ps.setInstallPermissionsFixed(false);
final LegacyPermissionState legacyState;
if (ps.hasSharedUser()) {
- legacyState = mPackageManagerInt.getSharedUserApi(
- ps.getSharedUserAppId()).getSharedUserLegacyPermissionState();
+ final int sharedUserId = ps.getSharedUserAppId();
+ SharedUserApi sharedUserApi = mPackageManagerInt.getSharedUserApi(sharedUserId);
+ if (sharedUserApi == null) {
+ Slog.wtf(TAG, "Missing shared user Api for " + sharedUserId);
+ return;
+ }
+ legacyState = sharedUserApi.getSharedUserLegacyPermissionState();
} else {
legacyState = ps.getLegacyPermissionState();
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2806a1176e1c..39bb2c02c24d 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -824,7 +824,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override public void onChange(boolean selfChange) {
updateSettings();
- updateRotation(false);
}
}
@@ -3046,6 +3045,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return key_consumed;
}
break;
+ case KeyEvent.KEYCODE_L:
+ if (down && event.isMetaPressed() && repeatCount == 0) {
+ lockNow(null /* options */);
+ return key_consumed;
+ }
+ break;
case KeyEvent.KEYCODE_N:
if (down && event.isMetaPressed()) {
if (event.isCtrlPressed()) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index 9ff6a0d7e6ee..d87fca4d3c71 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -133,16 +133,14 @@ class WallpaperData {
*/
final Rect cropHint = new Rect(0, 0, 0, 0);
- WallpaperData(int userId, File wallpaperDir, String inputFileName, String cropFileName) {
- this.userId = userId;
- wallpaperFile = new File(wallpaperDir, inputFileName);
- cropFile = new File(wallpaperDir, cropFileName);
- }
-
WallpaperData(int userId, @SetWallpaperFlags int wallpaperType) {
- this(userId, getWallpaperDir(userId),
- (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_ORIG : WALLPAPER,
- (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_CROP : WALLPAPER_CROP);
+ this.userId = userId;
+ this.mWhich = wallpaperType;
+ File wallpaperDir = getWallpaperDir(userId);
+ String wallpaperFileName = (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_ORIG : WALLPAPER;
+ String cropFileName = (wallpaperType == FLAG_LOCK) ? WALLPAPER_LOCK_CROP : WALLPAPER_CROP;
+ this.wallpaperFile = new File(wallpaperDir, wallpaperFileName);
+ this.cropFile = new File(wallpaperDir, cropFileName);
}
/**
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index aa553efae8af..c329d6bf2578 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1417,6 +1417,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
+ synchronized (mLock) {
+ saveSettingsLocked(mNewWallpaper.userId);
+ }
+
if (DEBUG) {
Slog.v(TAG, "--- wallpaper changed --");
Slog.v(TAG, "new sysWp: " + mWallpaperMap.get(mCurrentUserId));
@@ -1588,7 +1592,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mShuttingDown = false;
mImageWallpaper = ComponentName.unflattenFromString(
context.getResources().getString(R.string.image_wallpaper_component));
- mDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(context);
+ mDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(context);
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mIPackageManager = AppGlobals.getPackageManager();
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5ffb8fcf240a..3ba410c65ab8 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -898,6 +898,11 @@ class ActivityStarter {
}
mLastStartReason = request.reason;
mLastStartActivityTimeMs = System.currentTimeMillis();
+ // Reset the ActivityRecord#mCurrentLaunchCanTurnScreenOn state of last start activity in
+ // case the state is not yet consumed during rapid activity launch.
+ if (mLastStartActivityRecord != null) {
+ mLastStartActivityRecord.setCurrentLaunchCanTurnScreenOn(false);
+ }
mLastStartActivityRecord = null;
final IApplicationThread caller = request.caller;
@@ -1686,7 +1691,9 @@ class ActivityStarter {
}
// When running transient transition, the transient launch target should keep on top.
// So disallow the transient hide activity to move itself to front, e.g. trampoline.
- if (!mAvoidMoveToFront && r.mTransitionController.isTransientHide(targetTask)) {
+ if (!mAvoidMoveToFront && (mService.mHomeProcess == null
+ || mService.mHomeProcess.mUid != realCallingUid)
+ && r.mTransitionController.isTransientHide(targetTask)) {
mAvoidMoveToFront = true;
}
mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
@@ -2968,7 +2975,9 @@ class ActivityStarter {
// should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
&& intentActivity.inMultiWindowMode()
- && intentActivity == mTargetRootTask.topRunningActivity();
+ && intentActivity == mTargetRootTask.topRunningActivity()
+ && !intentActivity.mTransitionController.isTransientHide(
+ mTargetRootTask);
// We only want to move to the front, if we aren't going to launch on a
// different root task. If we launch on a different root task, we will put the
// task on top there.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 47b51ac164b9..4a658d6abaf4 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1411,29 +1411,39 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final long origId = Binder.clearCallingIdentity();
// TODO(b/64750076): Check if calling pid should really be -1.
- final int res = getActivityStartController()
- .obtainStarter(intent, "startNextMatchingActivity")
- .setCaller(r.app.getThread())
- .setResolvedType(r.resolvedType)
- .setActivityInfo(aInfo)
- .setResultTo(resultTo != null ? resultTo.token : null)
- .setResultWho(resultWho)
- .setRequestCode(requestCode)
- .setCallingPid(-1)
- .setCallingUid(r.launchedFromUid)
- .setCallingPackage(r.launchedFromPackage)
- .setCallingFeatureId(r.launchedFromFeatureId)
- .setRealCallingPid(-1)
- .setRealCallingUid(r.launchedFromUid)
- .setActivityOptions(options)
- .execute();
- Binder.restoreCallingIdentity(origId);
+ try {
+ if (options == null) {
+ options = new SafeActivityOptions(ActivityOptions.makeBasic());
+ }
- r.finishing = wasFinishing;
- if (res != ActivityManager.START_SUCCESS) {
- return false;
+ // Fixes b/230492947
+ // Prevents background activity launch through #startNextMatchingActivity
+ // An activity going into the background could still go back to the foreground
+ // if the intent used matches both:
+ // - the activity in the background
+ // - a second activity.
+ options.getOptions(r).setAvoidMoveToFront();
+ final int res = getActivityStartController()
+ .obtainStarter(intent, "startNextMatchingActivity")
+ .setCaller(r.app.getThread())
+ .setResolvedType(r.resolvedType)
+ .setActivityInfo(aInfo)
+ .setResultTo(resultTo != null ? resultTo.token : null)
+ .setResultWho(resultWho)
+ .setRequestCode(requestCode)
+ .setCallingPid(-1)
+ .setCallingUid(r.launchedFromUid)
+ .setCallingPackage(r.launchedFromPackage)
+ .setCallingFeatureId(r.launchedFromFeatureId)
+ .setRealCallingPid(-1)
+ .setRealCallingUid(r.launchedFromUid)
+ .setActivityOptions(options)
+ .execute();
+ r.finishing = wasFinishing;
+ return res == ActivityManager.START_SUCCESS;
+ } finally {
+ Binder.restoreCallingIdentity(origId);
}
- return true;
}
}
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 44d67687e260..98027bbed37f 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -23,6 +23,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowStateProto.IDENTIFIER;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.RemoteException;
@@ -217,10 +218,10 @@ class EmbeddedWindowController {
mHostWindowState.mInputWindowHandle.getInputApplicationHandle());
}
- InputChannel openInputChannel() {
+ void openInputChannel(@NonNull InputChannel outInputChannel) {
final String name = toString();
mInputChannel = mWmService.mInputManager.createInputChannel(name);
- return mInputChannel;
+ mInputChannel.copyTo(outInputChannel);
}
void onRemoved() {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 21004ab8d75a..e1c865bb85be 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -454,7 +454,7 @@ class InsetsSourceProvider {
if (mSource.getType() == WindowInsets.Type.ime()) {
setClientVisible(target.isRequestedVisible(WindowInsets.Type.ime()));
}
- final Transaction t = mDisplayContent.getSyncTransaction();
+ final Transaction t = mWindowContainer.getSyncTransaction();
mWindowContainer.startAnimation(t, mAdapter, !mClientVisible /* hidden */,
ANIMATION_TYPE_INSETS_CONTROL);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 4995236da1be..bc7fa31125f9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2357,10 +2357,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
final Transition transition = new Transition(TRANSIT_SLEEP, 0 /* flags */,
display.mTransitionController, mWmService.mSyncEngine);
final TransitionController.OnStartCollect sendSleepTransition = (deferred) -> {
- display.mTransitionController.requestStartTransition(transition,
- null /* trigger */, null /* remote */, null /* display */);
- // Force playing immediately so that unrelated ops can't be collected.
- transition.playNow();
+ if (deferred && !display.shouldSleep()) {
+ transition.abort();
+ } else {
+ display.mTransitionController.requestStartTransition(transition,
+ null /* trigger */, null /* remote */, null /* display */);
+ // Force playing immediately so that unrelated ops can't be collected.
+ transition.playNow();
+ }
};
if (!display.mTransitionController.isCollecting()) {
// Since this bypasses sync, submit directly ignoring whether sync-engine
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index 878b33fa55ef..100735784fb1 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -20,6 +20,8 @@ import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
+import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
+import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
import android.annotation.NonNull;
import android.content.Context;
@@ -59,6 +61,12 @@ class SystemGesturesPointerEventListener implements PointerEventListener {
private static final int SWIPE_FROM_RIGHT = 3;
private static final int SWIPE_FROM_LEFT = 4;
+ private static final int TRACKPAD_SWIPE_NONE = 0;
+ private static final int TRACKPAD_SWIPE_FROM_TOP = 1;
+ private static final int TRACKPAD_SWIPE_FROM_BOTTOM = 2;
+ private static final int TRACKPAD_SWIPE_FROM_RIGHT = 3;
+ private static final int TRACKPAD_SWIPE_FROM_LEFT = 4;
+
private final Context mContext;
private final Handler mHandler;
private int mDisplayCutoutTouchableRegionSize;
@@ -207,6 +215,25 @@ class SystemGesturesPointerEventListener implements PointerEventListener {
break;
case MotionEvent.ACTION_MOVE:
if (mSwipeFireable) {
+ int trackpadSwipe = detectTrackpadThreeFingerSwipe(event);
+ mSwipeFireable = trackpadSwipe == TRACKPAD_SWIPE_NONE;
+ if (!mSwipeFireable) {
+ if (trackpadSwipe == TRACKPAD_SWIPE_FROM_TOP) {
+ if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop from trackpad");
+ mCallbacks.onSwipeFromTop();
+ } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_BOTTOM) {
+ if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom from trackpad");
+ mCallbacks.onSwipeFromBottom();
+ } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_RIGHT) {
+ if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight from trackpad");
+ mCallbacks.onSwipeFromRight();
+ } else if (trackpadSwipe == TRACKPAD_SWIPE_FROM_LEFT) {
+ if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft from trackpad");
+ mCallbacks.onSwipeFromLeft();
+ }
+ break;
+ }
+
final int swipe = detectSwipe(event);
mSwipeFireable = swipe == SWIPE_NONE;
if (swipe == SWIPE_FROM_TOP) {
@@ -300,6 +327,31 @@ class SystemGesturesPointerEventListener implements PointerEventListener {
return mDownPointers - 1;
}
+ private int detectTrackpadThreeFingerSwipe(MotionEvent move) {
+ if (!isTrackpadThreeFingerSwipe(move)) {
+ return TRACKPAD_SWIPE_NONE;
+ }
+
+ float dx = move.getX() - mDownX[0];
+ float dy = move.getY() - mDownY[0];
+ if (Math.abs(dx) < Math.abs(dy)) {
+ if (Math.abs(dy) > mSwipeDistanceThreshold) {
+ return dy > 0 ? TRACKPAD_SWIPE_FROM_TOP : TRACKPAD_SWIPE_FROM_BOTTOM;
+ }
+ } else {
+ if (Math.abs(dx) > mSwipeDistanceThreshold) {
+ return dx > 0 ? TRACKPAD_SWIPE_FROM_LEFT : TRACKPAD_SWIPE_FROM_RIGHT;
+ }
+ }
+
+ return TRACKPAD_SWIPE_NONE;
+ }
+
+ private static boolean isTrackpadThreeFingerSwipe(MotionEvent event) {
+ return event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE
+ && event.getAxisValue(AXIS_GESTURE_SWIPE_FINGER_COUNT) == 3;
+ }
+
private int detectSwipe(MotionEvent move) {
final int historySize = move.getHistorySize();
final int pointerCount = move.getPointerCount();
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1ae1e03a6c4a..bfdf84e383a3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3298,6 +3298,12 @@ class Task extends TaskFragment {
scheduleAnimation();
}
+ // Let organizer manage task visibility for shell transition. So don't change it's
+ // visibility during collecting.
+ if (mTransitionController.isCollecting() && mCreatedByOrganizer) {
+ return;
+ }
+
// We intend to let organizer manage task visibility but it doesn't
// have enough information until we finish shell transitions.
// In the mean time we do an easy fix here.
@@ -5695,17 +5701,28 @@ class Task extends TaskFragment {
}
private boolean moveTaskToBackInner(@NonNull Task task) {
- moveToBack("moveTaskToBackInner", task);
-
- if (inPinnedWindowingMode()) {
- mTaskSupervisor.removeRootTask(this);
- return true;
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ // Preventing from update surface position for WindowState if configuration changed,
+ // because the position is depends on WindowFrame, so update the position before
+ // relayout will only update it to "old" position.
+ mAtmService.deferWindowLayout();
}
+ try {
+ moveToBack("moveTaskToBackInner", task);
- mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
- mDisplayContent.mDisplayId, false /* markFrozenIfConfigChanged */,
- false /* deferResume */);
+ if (inPinnedWindowingMode()) {
+ mTaskSupervisor.removeRootTask(this);
+ return true;
+ }
+ mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
+ mDisplayContent.mDisplayId, false /* markFrozenIfConfigChanged */,
+ false /* deferResume */);
+ } finally {
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ mAtmService.continueWindowLayout();
+ }
+ }
ActivityRecord topActivity = getDisplayArea().topRunningActivity();
Task topRootTask = topActivity.getRootTask();
if (topRootTask != null && topRootTask != this && topActivity.isState(RESUMED)) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 9a5f766989ca..795d022512a3 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -690,6 +690,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (!wc.mDisplayContent.getDisplayPolicy().isScreenOnFully()
|| wc.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF) {
mFlags |= WindowManager.TRANSIT_FLAG_INVISIBLE;
+ return;
}
if (mContainerFreezer == null) {
@@ -1190,7 +1191,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// processed all the participants first (in particular, we want to trigger pip-enter first)
for (int i = 0; i < mParticipants.size(); ++i) {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
- if (ar != null) {
+ // If the activity was just inserted to an invisible task, it will keep INITIALIZING
+ // state. Then no need to notify the callback to avoid clearing some states
+ // unexpectedly, e.g. launch-task-behind.
+ if (ar != null && (ar.isVisibleRequested()
+ || !ar.isState(ActivityRecord.State.INITIALIZING))) {
mController.dispatchLegacyAppTransitionFinished(ar);
}
}
@@ -2244,11 +2249,17 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc);
// Make leash based on highest (z-order) direct child of ancestor with a participant.
+ // Check whether the ancestor is belonged to last parent, shouldn't happen.
+ final boolean hasReparent = !wc.isDescendantOf(ancestor);
WindowContainer leashReference = wc;
- while (leashReference.getParent() != ancestor) {
- leashReference = leashReference.getParent();
+ if (hasReparent) {
+ Slog.e(TAG, "Did not find common ancestor! Ancestor= " + ancestor
+ + " target= " + wc);
+ } else {
+ while (leashReference.getParent() != ancestor) {
+ leashReference = leashReference.getParent();
+ }
}
-
final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
"Transition Root: " + leashReference.getName()).build();
rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
@@ -2307,23 +2318,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
task.fillTaskInfo(tinfo);
change.setTaskInfo(tinfo);
change.setRotationAnimation(getTaskRotationAnimation(task));
- final ActivityRecord topMostActivity = task.getTopMostActivity();
- change.setAllowEnterPip(topMostActivity != null
- && topMostActivity.checkEnterPictureInPictureAppOpsState());
final ActivityRecord topRunningActivity = task.topRunningActivity();
- if (topRunningActivity != null && task.mDisplayContent != null
- // Display won't be rotated for multi window Task, so the fixed rotation
- // won't be applied. This can happen when the windowing mode is changed
- // before the previous fixed rotation is applied.
- && (!task.inMultiWindowMode() || !topRunningActivity.inMultiWindowMode())) {
- // If Activity is in fixed rotation, its will be applied with the next rotation,
- // when the Task is still in the previous rotation.
- final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
- final int activityRotation = topRunningActivity.getWindowConfiguration()
- .getDisplayRotation();
- if (taskRotation != activityRotation) {
- change.setEndFixedRotation(activityRotation);
+ if (topRunningActivity != null) {
+ if (topRunningActivity.info.supportsPictureInPicture()) {
+ change.setAllowEnterPip(
+ topRunningActivity.checkEnterPictureInPictureAppOpsState());
}
+ setEndFixedRotationIfNeeded(change, task, topRunningActivity);
}
} else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) {
change.setRotationAnimation(ROTATION_ANIMATION_SEAMLESS);
@@ -2431,6 +2432,48 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
return animOptions;
}
+
+ private static void setEndFixedRotationIfNeeded(@NonNull TransitionInfo.Change change,
+ @NonNull Task task, @NonNull ActivityRecord taskTopRunning) {
+ if (!taskTopRunning.isVisibleRequested()) {
+ // Fixed rotation only applies to opening or changing activity.
+ return;
+ }
+ if (task.inMultiWindowMode() && taskTopRunning.inMultiWindowMode()) {
+ // Display won't be rotated for multi window Task, so the fixed rotation won't be
+ // applied. This can happen when the windowing mode is changed before the previous
+ // fixed rotation is applied. Check both task and activity because the activity keeps
+ // fullscreen mode when the task is entering PiP.
+ return;
+ }
+ final int taskRotation = task.getWindowConfiguration().getDisplayRotation();
+ final int activityRotation = taskTopRunning.getWindowConfiguration()
+ .getDisplayRotation();
+ // If the Activity uses fixed rotation, its rotation will be applied to display after
+ // the current transition is done, while the Task is still in the previous rotation.
+ if (taskRotation != activityRotation) {
+ change.setEndFixedRotation(activityRotation);
+ return;
+ }
+
+ // For example, the task is entering PiP so it no longer decides orientation. If the next
+ // orientation source (it could be an activity which was behind the PiP or launching to top)
+ // will change display rotation, then set the fixed rotation hint as well so the animation
+ // can consider the rotated position.
+ if (!task.inPinnedWindowingMode() || taskTopRunning.mDisplayContent.inTransition()) {
+ return;
+ }
+ final WindowContainer<?> orientationSource =
+ taskTopRunning.mDisplayContent.getLastOrientationSource();
+ if (orientationSource == null) {
+ return;
+ }
+ final int nextRotation = orientationSource.getWindowConfiguration().getDisplayRotation();
+ if (taskRotation != nextRotation) {
+ change.setEndFixedRotation(nextRotation);
+ }
+ }
+
/**
* Finds the top-most common ancestor of app targets.
*
@@ -2453,6 +2496,20 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// Skip the non-app window or windows on a different display
continue;
}
+ // Re-initiate the last parent as the initial ancestor instead of the top target.
+ // When move a leaf task from organized task to display area, try to keep the transition
+ // root be the original organized task for close transition animation.
+ // Otherwise, shell will use wrong root layer to play animation.
+ // Note: Since the target is sorted, so only need to do this at the lowest target.
+ if (change.mStartParent != null && wc.getParent() != null
+ && change.mStartParent.isAttached() && wc.getParent() != change.mStartParent
+ && i == targets.size() - 1) {
+ final int transitionMode = change.getTransitMode(wc);
+ if (transitionMode == TRANSIT_CLOSE || transitionMode == TRANSIT_TO_BACK) {
+ ancestor = change.mStartParent;
+ continue;
+ }
+ }
while (!wc.isDescendantOf(ancestor)) {
ancestor = ancestor.getParent();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0caff466bee3..bb3d10912724 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -88,6 +88,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.fixScale;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
@@ -3591,6 +3592,7 @@ public class WindowManagerService extends IWindowManager.Stub
public void setCurrentUser(@UserIdInt int newUserId) {
synchronized (mGlobalLock) {
+ mAtmService.getTransitionController().requestTransitionIfNeeded(TRANSIT_OPEN, null);
mCurrentUserId = newUserId;
mPolicy.setCurrentUserLw(newUserId);
mKeyguardDisableHandler.setCurrentUser(newUserId);
@@ -8791,24 +8793,22 @@ public class WindowManagerService extends IWindowManager.Stub
final int sanitizedType = sanitizeWindowType(session, displayId, windowToken, type);
final InputApplicationHandle applicationHandle;
final String name;
- final InputChannel clientChannel;
+ Objects.requireNonNull(outInputChannel);
synchronized (mGlobalLock) {
EmbeddedWindowController.EmbeddedWindow win =
new EmbeddedWindowController.EmbeddedWindow(session, this, window,
mInputToWindowMap.get(hostInputToken), callingUid, callingPid,
sanitizedType, displayId, focusGrantToken, inputHandleName,
(flags & FLAG_NOT_FOCUSABLE) == 0);
- clientChannel = win.openInputChannel();
- mEmbeddedWindowController.add(clientChannel.getToken(), win);
+ win.openInputChannel(outInputChannel);
+ mEmbeddedWindowController.add(outInputChannel.getToken(), win);
applicationHandle = win.getApplicationHandle();
name = win.toString();
}
- updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface,
+ updateInputChannel(outInputChannel.getToken(), callingUid, callingPid, displayId, surface,
name, applicationHandle, flags, privateFlags, inputFeatures, sanitizedType,
null /* region */, window);
-
- clientChannel.copyTo(outInputChannel);
}
boolean transferEmbeddedTouchFocusToHost(IWindow embeddedWindow) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index c918fb87154f..9c1d765fe0f9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -101,7 +101,7 @@ final class DevicePolicyEngine {
private final UserManager mUserManager;
// TODO(b/256849338): add more granular locks
- private final Object mLock = new Object();
+ private final Object mLock;
/**
* Map of <userId, Map<policyKey, policyState>>
@@ -122,9 +122,11 @@ final class DevicePolicyEngine {
DevicePolicyEngine(
@NonNull Context context,
- @NonNull DeviceAdminServiceController deviceAdminServiceController) {
+ @NonNull DeviceAdminServiceController deviceAdminServiceController,
+ @NonNull Object lock) {
mContext = Objects.requireNonNull(context);
mDeviceAdminServiceController = Objects.requireNonNull(deviceAdminServiceController);
+ mLock = Objects.requireNonNull(lock);
mUserManager = mContext.getSystemService(UserManager.class);
mLocalPolicies = new SparseArray<>();
mGlobalPolicies = new HashMap<>();
@@ -152,8 +154,8 @@ final class DevicePolicyEngine {
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
if (policyDefinition.isNonCoexistablePolicy()) {
- setNonCoexistableLocalPolicy(policyDefinition, localPolicyState, enforcingAdmin,
- value, userId, skipEnforcePolicy);
+ setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState,
+ enforcingAdmin, value, userId, skipEnforcePolicy);
return;
}
@@ -173,7 +175,7 @@ final class DevicePolicyEngine {
// the data structures.
if (!skipEnforcePolicy) {
if (policyChanged) {
- onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId);
+ onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId);
}
boolean policyEnforced = Objects.equals(
localPolicyState.getCurrentResolvedPolicy(), value);
@@ -211,7 +213,7 @@ final class DevicePolicyEngine {
*
* <p>Passing a {@code null} value means the policy set by this admin should be removed.
*/
- private <V> void setNonCoexistableLocalPolicy(
+ private <V> void setNonCoexistableLocalPolicyLocked(
PolicyDefinition<V> policyDefinition,
PolicyState<V> localPolicyState,
EnforcingAdmin enforcingAdmin,
@@ -266,8 +268,8 @@ final class DevicePolicyEngine {
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
if (policyDefinition.isNonCoexistablePolicy()) {
- setNonCoexistableLocalPolicy(policyDefinition, localPolicyState, enforcingAdmin,
- /* value= */ null, userId, /* skipEnforcePolicy= */ false);
+ setNonCoexistableLocalPolicyLocked(policyDefinition, localPolicyState,
+ enforcingAdmin, /* value= */ null, userId, /* skipEnforcePolicy= */ false);
return;
}
@@ -282,7 +284,7 @@ final class DevicePolicyEngine {
}
if (policyChanged) {
- onLocalPolicyChanged(policyDefinition, enforcingAdmin, userId);
+ onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId);
}
// For a removePolicy to be enforced, it means no current policy exists
@@ -348,7 +350,7 @@ final class DevicePolicyEngine {
/**
* Enforces the new policy and notifies relevant admins.
*/
- private <V> void onLocalPolicyChanged(
+ private <V> void onLocalPolicyChangedLocked(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin,
int userId) {
@@ -358,7 +360,7 @@ final class DevicePolicyEngine {
policyDefinition, localPolicyState.getCurrentResolvedPolicy(), userId);
// Send policy updates to admins who've set it locally
- sendPolicyChangedToAdmins(
+ sendPolicyChangedToAdminsLocked(
localPolicyState,
enforcingAdmin,
policyDefinition,
@@ -369,7 +371,7 @@ final class DevicePolicyEngine {
// Send policy updates to admins who've set it globally
if (hasGlobalPolicyLocked(policyDefinition)) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
- sendPolicyChangedToAdmins(
+ sendPolicyChangedToAdminsLocked(
globalPolicyState,
enforcingAdmin,
policyDefinition,
@@ -424,7 +426,7 @@ final class DevicePolicyEngine {
// the data structures.
if (!skipEnforcePolicy) {
if (policyChanged) {
- onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
+ onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
}
boolean policyAppliedGlobally = Objects.equals(
@@ -473,7 +475,7 @@ final class DevicePolicyEngine {
boolean policyChanged = policyState.removePolicy(enforcingAdmin);
if (policyChanged) {
- onGlobalPolicyChanged(policyDefinition, enforcingAdmin);
+ onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
}
applyGlobalPolicyOnUsersWithLocalPoliciesLocked(policyDefinition, enforcingAdmin,
@@ -499,7 +501,7 @@ final class DevicePolicyEngine {
/**
* Enforces the new policy globally and notifies relevant admins.
*/
- private <V> void onGlobalPolicyChanged(
+ private <V> void onGlobalPolicyChangedLocked(
@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
@@ -507,7 +509,7 @@ final class DevicePolicyEngine {
enforcePolicy(policyDefinition, policyState.getCurrentResolvedPolicy(),
UserHandle.USER_ALL);
- sendPolicyChangedToAdmins(
+ sendPolicyChangedToAdminsLocked(
policyState,
enforcingAdmin,
policyDefinition,
@@ -552,7 +554,7 @@ final class DevicePolicyEngine {
policyDefinition,
localPolicyState.getCurrentResolvedPolicy(),
userId);
- sendPolicyChangedToAdmins(
+ sendPolicyChangedToAdminsLocked(
localPolicyState,
enforcingAdmin,
policyDefinition,
@@ -745,34 +747,35 @@ final class DevicePolicyEngine {
}
<V> void transferPolicies(EnforcingAdmin oldAdmin, EnforcingAdmin newAdmin) {
- Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
- for (PolicyKey policy : globalPolicies) {
- PolicyState<?> policyState = mGlobalPolicies.get(policy);
- if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
- PolicyDefinition<V> policyDefinition =
- (PolicyDefinition<V>) policyState.getPolicyDefinition();
- PolicyValue<V> policyValue =
- (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
- setGlobalPolicy(policyDefinition, newAdmin, policyValue);
- }
- }
-
- for (int i = 0; i < mLocalPolicies.size(); i++) {
- int userId = mLocalPolicies.keyAt(i);
- Set<PolicyKey> localPolicies = new HashSet<>(
- mLocalPolicies.get(userId).keySet());
- for (PolicyKey policy : localPolicies) {
- PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+ synchronized (mLock) {
+ Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
+ for (PolicyKey policy : globalPolicies) {
+ PolicyState<?> policyState = mGlobalPolicies.get(policy);
if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
PolicyDefinition<V> policyDefinition =
(PolicyDefinition<V>) policyState.getPolicyDefinition();
PolicyValue<V> policyValue =
(PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
- setLocalPolicy(policyDefinition, newAdmin, policyValue, userId);
+ setGlobalPolicy(policyDefinition, newAdmin, policyValue);
}
}
- }
+ for (int i = 0; i < mLocalPolicies.size(); i++) {
+ int userId = mLocalPolicies.keyAt(i);
+ Set<PolicyKey> localPolicies = new HashSet<>(
+ mLocalPolicies.get(userId).keySet());
+ for (PolicyKey policy : localPolicies) {
+ PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+ if (policyState.getPoliciesSetByAdmins().containsKey(oldAdmin)) {
+ PolicyDefinition<V> policyDefinition =
+ (PolicyDefinition<V>) policyState.getPolicyDefinition();
+ PolicyValue<V> policyValue =
+ (PolicyValue<V>) policyState.getPoliciesSetByAdmins().get(oldAdmin);
+ setLocalPolicy(policyDefinition, newAdmin, policyValue, userId);
+ }
+ }
+ }
+ }
removePoliciesForAdmin(oldAdmin);
}
@@ -836,7 +839,7 @@ final class DevicePolicyEngine {
mLocalPolicies.get(userId).put(
policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
}
- return getPolicyState(mLocalPolicies.get(userId), policyDefinition);
+ return getPolicyStateLocked(mLocalPolicies.get(userId), policyDefinition);
}
private <V> void removeLocalPolicyStateLocked(
@@ -858,14 +861,14 @@ final class DevicePolicyEngine {
mGlobalPolicies.put(
policyDefinition.getPolicyKey(), new PolicyState<>(policyDefinition));
}
- return getPolicyState(mGlobalPolicies, policyDefinition);
+ return getPolicyStateLocked(mGlobalPolicies, policyDefinition);
}
private <V> void removeGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
mGlobalPolicies.remove(policyDefinition.getPolicyKey());
}
- private static <V> PolicyState<V> getPolicyState(
+ private static <V> PolicyState<V> getPolicyStateLocked(
Map<PolicyKey, PolicyState<?>> policies, PolicyDefinition<V> policyDefinition) {
try {
// This will not throw an exception because policyDefinition is of type V, so unless
@@ -935,7 +938,7 @@ final class DevicePolicyEngine {
}
// TODO(b/261430877): Finalise the decision on which admins to send the updates to.
- private <V> void sendPolicyChangedToAdmins(
+ private <V> void sendPolicyChangedToAdminsLocked(
PolicyState<V> policyState,
EnforcingAdmin callingAdmin,
PolicyDefinition<V> policyDefinition,
@@ -1210,17 +1213,19 @@ final class DevicePolicyEngine {
if (parentInfo == null || parentInfo.getUserHandle().getIdentifier() == userId) {
return;
}
- if (!mLocalPolicies.contains(parentInfo.getUserHandle().getIdentifier())) {
- return;
- }
- for (Map.Entry<PolicyKey, PolicyState<?>> entry : mLocalPolicies.get(
- parentInfo.getUserHandle().getIdentifier()).entrySet()) {
- enforcePolicyOnUser(userId, entry.getValue());
+ synchronized (mLock) {
+ if (!mLocalPolicies.contains(parentInfo.getUserHandle().getIdentifier())) {
+ return;
+ }
+ for (Map.Entry<PolicyKey, PolicyState<?>> entry : mLocalPolicies.get(
+ parentInfo.getUserHandle().getIdentifier()).entrySet()) {
+ enforcePolicyOnUserLocked(userId, entry.getValue());
+ }
}
});
}
- private <V> void enforcePolicyOnUser(int userId, PolicyState<V> policyState) {
+ private <V> void enforcePolicyOnUserLocked(int userId, PolicyState<V> policyState) {
if (!policyState.getPolicyDefinition().isInheritable()) {
return;
}
@@ -1239,26 +1244,28 @@ final class DevicePolicyEngine {
*/
@NonNull
DevicePolicyState getDevicePolicyState() {
- Map<UserHandle, Map<PolicyKey, android.app.admin.PolicyState<?>>> policies =
- new HashMap<>();
- for (int i = 0; i < mLocalPolicies.size(); i++) {
- UserHandle user = UserHandle.of(mLocalPolicies.keyAt(i));
- policies.put(user, new HashMap<>());
- for (PolicyKey policyKey : mLocalPolicies.valueAt(i).keySet()) {
- policies.get(user).put(
- policyKey,
- mLocalPolicies.valueAt(i).get(policyKey).getParcelablePolicyState());
+ synchronized (mLock) {
+ Map<UserHandle, Map<PolicyKey, android.app.admin.PolicyState<?>>> policies =
+ new HashMap<>();
+ for (int i = 0; i < mLocalPolicies.size(); i++) {
+ UserHandle user = UserHandle.of(mLocalPolicies.keyAt(i));
+ policies.put(user, new HashMap<>());
+ for (PolicyKey policyKey : mLocalPolicies.valueAt(i).keySet()) {
+ policies.get(user).put(
+ policyKey,
+ mLocalPolicies.valueAt(i).get(policyKey).getParcelablePolicyState());
+ }
}
- }
- if (!mGlobalPolicies.isEmpty()) {
- policies.put(UserHandle.ALL, new HashMap<>());
- for (PolicyKey policyKey : mGlobalPolicies.keySet()) {
- policies.get(UserHandle.ALL).put(
- policyKey,
- mGlobalPolicies.get(policyKey).getParcelablePolicyState());
+ if (!mGlobalPolicies.isEmpty()) {
+ policies.put(UserHandle.ALL, new HashMap<>());
+ for (PolicyKey policyKey : mGlobalPolicies.keySet()) {
+ policies.get(UserHandle.ALL).put(
+ policyKey,
+ mGlobalPolicies.get(policyKey).getParcelablePolicyState());
+ }
}
+ return new DevicePolicyState(policies);
}
- return new DevicePolicyState(policies);
}
@@ -1266,23 +1273,25 @@ final class DevicePolicyEngine {
* Removes all local and global policies set by that admin.
*/
void removePoliciesForAdmin(EnforcingAdmin admin) {
- Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
- for (PolicyKey policy : globalPolicies) {
- PolicyState<?> policyState = mGlobalPolicies.get(policy);
- if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
- removeGlobalPolicy(policyState.getPolicyDefinition(), admin);
+ synchronized (mLock) {
+ Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
+ for (PolicyKey policy : globalPolicies) {
+ PolicyState<?> policyState = mGlobalPolicies.get(policy);
+ if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
+ removeGlobalPolicy(policyState.getPolicyDefinition(), admin);
+ }
}
- }
- for (int i = 0; i < mLocalPolicies.size(); i++) {
- Set<PolicyKey> localPolicies = new HashSet<>(
- mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet());
- for (PolicyKey policy : localPolicies) {
- PolicyState<?> policyState = mLocalPolicies.get(
- mLocalPolicies.keyAt(i)).get(policy);
- if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
- removeLocalPolicy(
- policyState.getPolicyDefinition(), admin, mLocalPolicies.keyAt(i));
+ for (int i = 0; i < mLocalPolicies.size(); i++) {
+ Set<PolicyKey> localPolicies = new HashSet<>(
+ mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet());
+ for (PolicyKey policy : localPolicies) {
+ PolicyState<?> policyState = mLocalPolicies.get(
+ mLocalPolicies.keyAt(i)).get(policy);
+ if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
+ removeLocalPolicy(
+ policyState.getPolicyDefinition(), admin, mLocalPolicies.keyAt(i));
+ }
}
}
}
@@ -1292,23 +1301,25 @@ final class DevicePolicyEngine {
* Removes all local policies for the provided {@code userId}.
*/
private void removeLocalPoliciesForUser(int userId) {
- if (!mLocalPolicies.contains(userId)) {
- // No policies on user
- return;
- }
+ synchronized (mLock) {
+ if (!mLocalPolicies.contains(userId)) {
+ // No policies on user
+ return;
+ }
- Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet());
- for (PolicyKey policy : localPolicies) {
- PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
- Set<EnforcingAdmin> admins = new HashSet<>(
- policyState.getPoliciesSetByAdmins().keySet());
- for (EnforcingAdmin admin : admins) {
- removeLocalPolicy(
- policyState.getPolicyDefinition(), admin, userId);
+ Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet());
+ for (PolicyKey policy : localPolicies) {
+ PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+ Set<EnforcingAdmin> admins = new HashSet<>(
+ policyState.getPoliciesSetByAdmins().keySet());
+ for (EnforcingAdmin admin : admins) {
+ removeLocalPolicy(
+ policyState.getPolicyDefinition(), admin, userId);
+ }
}
- }
- mLocalPolicies.remove(userId);
+ mLocalPolicies.remove(userId);
+ }
}
/**
@@ -1376,7 +1387,7 @@ final class DevicePolicyEngine {
*/
private void updateDeviceAdminServiceOnPolicyRemoveLocked(
@NonNull EnforcingAdmin enforcingAdmin) {
- if (doesAdminHavePolicies(enforcingAdmin)) {
+ if (doesAdminHavePoliciesLocked(enforcingAdmin)) {
return;
}
int userId = enforcingAdmin.getUserId();
@@ -1399,7 +1410,7 @@ final class DevicePolicyEngine {
/* actionForLog= */ "policy-removed");
}
- private boolean doesAdminHavePolicies(@NonNull EnforcingAdmin enforcingAdmin) {
+ private boolean doesAdminHavePoliciesLocked(@NonNull EnforcingAdmin enforcingAdmin) {
for (PolicyKey policy : mGlobalPolicies.keySet()) {
PolicyState<?> policyState = mGlobalPolicies.get(policy);
if (policyState.getPoliciesSetByAdmins().containsKey(enforcingAdmin)) {
@@ -1420,13 +1431,17 @@ final class DevicePolicyEngine {
@NonNull
private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) {
- return mEnforcingAdmins.contains(userId)
- ? mEnforcingAdmins.get(userId) : Collections.emptySet();
+ synchronized (mLock) {
+ return mEnforcingAdmins.contains(userId)
+ ? mEnforcingAdmins.get(userId) : Collections.emptySet();
+ }
}
private void write() {
- Log.d(TAG, "Writing device policies to file.");
- new DevicePoliciesReaderWriter().writeToFileLocked();
+ synchronized (mLock) {
+ Log.d(TAG, "Writing device policies to file.");
+ new DevicePoliciesReaderWriter().writeToFileLocked();
+ }
}
// TODO(b/256852787): trigger resolving logic after loading policies as roles are recalculated
@@ -1436,11 +1451,11 @@ final class DevicePolicyEngine {
synchronized (mLock) {
clear();
new DevicePoliciesReaderWriter().readFromFileLocked();
- reapplyAllPolicies();
+ reapplyAllPoliciesLocked();
}
}
- private <V> void reapplyAllPolicies() {
+ private <V> void reapplyAllPoliciesLocked() {
for (PolicyKey policy : mGlobalPolicies.keySet()) {
PolicyState<?> policyState = mGlobalPolicies.get(policy);
// Policy definition and value will always be of the same type
@@ -1470,10 +1485,8 @@ final class DevicePolicyEngine {
* <p>Note that this doesn't clear any enforcements, it only clears the data structures.
*/
void clearAllPolicies() {
- synchronized (mLock) {
- clear();
- write();
- }
+ clear();
+ write();
}
private void clear() {
synchronized (mLock) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b43209f26128..417fc3949f2c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2093,7 +2093,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mUserData = new SparseArray<>();
mOwners = makeOwners(injector, pathProvider);
- mDevicePolicyEngine = new DevicePolicyEngine(mContext, mDeviceAdminServiceController);
+ mDevicePolicyEngine = new DevicePolicyEngine(
+ mContext, mDeviceAdminServiceController, getLockObject());
if (!mHasFeature) {
// Skip the rest of the initialization
@@ -7840,27 +7841,29 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
throw new SecurityException("Cannot wipe data. " + restriction
+ " restriction is set for user " + userId);
}
+ });
- boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
- boolean wipeDevice;
- if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
- adminPackage,
- userId)) {
- // Legacy mode
- wipeDevice = isSystemUser;
+ boolean isSystemUser = userId == UserHandle.USER_SYSTEM;
+ boolean wipeDevice;
+ if (factoryReset == null || !mInjector.isChangeEnabled(EXPLICIT_WIPE_BEHAVIOUR,
+ adminPackage,
+ userId)) {
+ // Legacy mode
+ wipeDevice = isSystemUser;
+ } else {
+ // Explicit behaviour
+ if (factoryReset) {
+ EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
+ /*admin=*/ null,
+ /*permission=*/ new String[]{MANAGE_DEVICE_POLICY_WIPE_DATA,
+ MASTER_CLEAR},
+ USES_POLICY_WIPE_DATA,
+ adminPackage,
+ factoryReset ? UserHandle.USER_ALL :
+ getAffectedUser(calledOnParentInstance));
+ wipeDevice = true;
} else {
- // Explicit behaviour
- if (factoryReset) {
- EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
- /*admin=*/ null,
- /*permission=*/ new String[]{MANAGE_DEVICE_POLICY_WIPE_DATA,
- MASTER_CLEAR},
- USES_POLICY_WIPE_DATA,
- adminPackage,
- factoryReset ? UserHandle.USER_ALL :
- getAffectedUser(calledOnParentInstance));
- wipeDevice = true;
- } else {
+ mInjector.binderWithCleanCallingIdentity(() -> {
Preconditions.checkCallAuthorization(!isSystemUser,
"User %s is a system user and cannot be removed", userId);
boolean isLastNonHeadlessUser = getUserInfo(userId).isFull()
@@ -7871,9 +7874,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
"Removing user %s would leave the device without any active users. "
+ "Consider factory resetting the device instead.",
userId);
- wipeDevice = false;
- }
+ });
+ wipeDevice = false;
}
+ }
+ mInjector.binderWithCleanCallingIdentity(() -> {
if (wipeDevice) {
forceWipeDeviceNoLock(
(flags & WIPE_EXTERNAL_STORAGE) != 0,
@@ -13593,6 +13598,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
: SecurityLog.TAG_USER_RESTRICTION_REMOVED;
SecurityLog.writeEvent(eventTag, caller.getPackageName(), caller.getUserId(), key);
}
+
+ Slogf.i(LOG_TAG, "Changing user restriction %s to: %b caller: %s",
+ key, enabled, caller.toString());
}
private void saveUserRestrictionsLocked(int userId) {
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/affected_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/affected_cpus
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/affected_cpus
@@ -0,0 +1 @@
+
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/related_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/related_cpus
new file mode 100644
index 000000000000..c227083464fb
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/related_cpus
@@ -0,0 +1 @@
+0 \ No newline at end of file
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_cur_freq
new file mode 100644
index 000000000000..dadd9737778d
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_cur_freq
@@ -0,0 +1 @@
+1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_max_freq
new file mode 100644
index 000000000000..a93d6f7b2c09
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/scaling_max_freq
@@ -0,0 +1 @@
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/stats/time_in_state b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/stats/time_in_state
new file mode 100644
index 000000000000..5121f6661a7e
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy0/stats/time_in_state
@@ -0,0 +1,4 @@
+200000 500
+350000 500
+500000 1000
+2500000 100
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/affected_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/affected_cpus
new file mode 100644
index 000000000000..d00491fd7e5b
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/affected_cpus
@@ -0,0 +1 @@
+1
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/related_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/related_cpus
new file mode 100644
index 000000000000..56a6051ca2b0
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/related_cpus
@@ -0,0 +1 @@
+1 \ No newline at end of file
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_cur_freq
new file mode 100644
index 000000000000..2bc4ce96ec98
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_cur_freq
@@ -0,0 +1 @@
+1450000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_max_freq
new file mode 100644
index 000000000000..c754f1a461ab
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/scaling_max_freq
@@ -0,0 +1 @@
+2800000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/stats/time_in_state b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/stats/time_in_state
new file mode 100644
index 000000000000..7ed12cfce58d
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_affected_cpus/policy1/stats/time_in_state
@@ -0,0 +1,4 @@
+200000 500
+350000 500
+500000 1000
+2800000 100
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/affected_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/affected_cpus
new file mode 100644
index 000000000000..573541ac9702
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/affected_cpus
@@ -0,0 +1 @@
+0
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/related_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/related_cpus
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/related_cpus
@@ -0,0 +1 @@
+
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_cur_freq
new file mode 100644
index 000000000000..dadd9737778d
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_cur_freq
@@ -0,0 +1 @@
+1230000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_max_freq
new file mode 100644
index 000000000000..a93d6f7b2c09
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/scaling_max_freq
@@ -0,0 +1 @@
+2500000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/stats/time_in_state b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/stats/time_in_state
new file mode 100644
index 000000000000..5121f6661a7e
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy0/stats/time_in_state
@@ -0,0 +1,4 @@
+200000 500
+350000 500
+500000 1000
+2500000 100
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/affected_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/affected_cpus
new file mode 100644
index 000000000000..d00491fd7e5b
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/affected_cpus
@@ -0,0 +1 @@
+1
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/related_cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/related_cpus
new file mode 100644
index 000000000000..56a6051ca2b0
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/related_cpus
@@ -0,0 +1 @@
+1 \ No newline at end of file
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_cur_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_cur_freq
new file mode 100644
index 000000000000..2bc4ce96ec98
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_cur_freq
@@ -0,0 +1 @@
+1450000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_max_freq b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_max_freq
new file mode 100644
index 000000000000..c754f1a461ab
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/scaling_max_freq
@@ -0,0 +1 @@
+2800000
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/stats/time_in_state b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/stats/time_in_state
new file mode 100644
index 000000000000..7ed12cfce58d
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpufreq_with_empty_related_cpus/policy1/stats/time_in_state
@@ -0,0 +1,4 @@
+200000 500
+350000 500
+500000 1000
+2800000 100
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/background/cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/background/cpus
new file mode 100644
index 000000000000..8b137891791f
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/background/cpus
@@ -0,0 +1 @@
+
diff --git a/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/top-app/cpus b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/top-app/cpus
new file mode 100644
index 000000000000..40c7bb2f1a2a
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/CpuInfoReaderTest/valid_cpuset_with_empty_cpus/top-app/cpus
@@ -0,0 +1 @@
+0-3
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
index e2c2bec729ad..2fbe8aab73d0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java
@@ -48,6 +48,11 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
private static final String TAG = CpuInfoReaderTest.class.getSimpleName();
private static final String ROOT_DIR_NAME = "CpuInfoReaderTest";
private static final String VALID_CPUSET_DIR = "valid_cpuset";
+ private static final String VALID_CPUSET_WITH_EMPTY_CPUS = "valid_cpuset_with_empty_cpus";
+ private static final String VALID_CPUFREQ_WITH_EMPTY_AFFECTED_CPUS =
+ "valid_cpufreq_with_empty_affected_cpus";
+ private static final String VALID_CPUFREQ_WITH_EMPTY_RELATED_CPUS =
+ "valid_cpufreq_with_empty_related_cpus";
private static final String VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR =
"valid_cpufreq_with_time_in_state";
private static final String VALID_CPUFREQ_WITH_TIME_IN_STATE_2_DIR =
@@ -403,6 +408,104 @@ public final class CpuInfoReaderTest extends ExpectableTestCase {
}
@Test
+ public void testReadCpuInfoWithEmptyRelatedCpus() throws Exception {
+ CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+ getCacheFile(VALID_CPUFREQ_WITH_EMPTY_RELATED_CPUS),
+ getCacheFile(VALID_PROC_STAT));
+
+ SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+ SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
+
+ expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
+ FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
+ /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+ /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
+ new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
+ /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
+ /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
+ /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
+ /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
+ /* guestNiceTimeMillis= */ 0)));
+
+ compareCpuInfos("CPU infos with policy 0 containing an empty related_cpus file",
+ expectedCpuInfos, actualCpuInfos);
+ }
+
+ @Test
+ public void testReadCpuInfoWithEmptyCpusetCpus() throws Exception {
+ CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_WITH_EMPTY_CPUS),
+ getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR),
+ getCacheFile(VALID_PROC_STAT));
+
+ SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+ SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
+ expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0,
+ FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000,
+ /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095,
+ new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610,
+ /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050,
+ /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810,
+ /* irqTimeMillis= */ 8_146_740, /* softirqTimeMillis= */ 428_970,
+ /* stealTimeMillis= */ 81_950, /* guestTimeMillis= */ 0,
+ /* guestNiceTimeMillis= */ 0)));
+ expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
+ FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
+ /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+ new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
+ /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
+ /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
+ /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
+ /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
+ /* guestNiceTimeMillis= */ 0)));
+ expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2,
+ FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
+ /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+ new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280,
+ /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020,
+ /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960,
+ /* irqTimeMillis= */ 14_796_940, /* softirqTimeMillis= */ 1_478_130,
+ /* stealTimeMillis= */ 88_780, /* guestTimeMillis= */ 0,
+ /* guestNiceTimeMillis= */ 0)));
+ expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3,
+ FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000,
+ /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285,
+ /* normalizedAvailableCpuFreqKHz= */ 1_907_125,
+ new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610,
+ /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050,
+ /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810,
+ /* irqTimeMillis= */ 8_136_740, /* softirqTimeMillis= */ 438_970,
+ /* stealTimeMillis= */ 71_950, /* guestTimeMillis= */ 0,
+ /* guestNiceTimeMillis= */ 0)));
+
+ compareCpuInfos("CPU infos with empty background cpu set", expectedCpuInfos,
+ actualCpuInfos);
+ }
+
+ @Test
+ public void testReadCpuInfoWithEmptyAffectedCpus() throws Exception {
+ CpuInfoReader cpuInfoReader = newCpuInfoReader(getCacheFile(VALID_CPUSET_DIR),
+ getCacheFile(VALID_CPUFREQ_WITH_EMPTY_AFFECTED_CPUS),
+ getCacheFile(VALID_PROC_STAT));
+
+ SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos();
+ SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>();
+
+ expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1,
+ FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000,
+ /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380,
+ /* normalizedAvailableCpuFreqKHz= */ 2_693_525,
+ new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280,
+ /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020,
+ /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960,
+ /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130,
+ /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0,
+ /* guestNiceTimeMillis= */ 0)));
+
+ compareCpuInfos("CPU infos with policy 0 containing an empty affected_cpus file",
+ expectedCpuInfos, actualCpuInfos);
+ }
+
+ @Test
public void testReadCpuInfoWithEmptyProcStat() throws Exception {
File emptyFile = getCacheFile(EMPTY_FILE);
assertWithMessage("Create empty file %s", emptyFile).that(emptyFile.createNewFile())
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index c5ff8cc7b0d6..dd23d9f006e3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -28,6 +28,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -148,6 +149,9 @@ public class JobSchedulerServiceTest {
// Used in JobConcurrencyManager.
doReturn(mock(UserManagerInternal.class))
.when(() -> LocalServices.getService(UserManagerInternal.class));
+ // Used in JobStatus.
+ doReturn(mock(JobSchedulerInternal.class))
+ .when(() -> LocalServices.getService(JobSchedulerInternal.class));
// Called via IdleController constructor.
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
when(mContext.getResources()).thenReturn(mock(Resources.class));
@@ -168,6 +172,8 @@ public class JobSchedulerServiceTest {
JobSchedulerService.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+ // Make sure the uptime is at least 24 hours so that tests that rely on high uptime work.
+ sUptimeMillisClock = getAdvancedClock(sUptimeMillisClock, 24 * HOUR_IN_MILLIS);
// Called by DeviceIdlenessTracker
when(mContext.getSystemService(UiModeManager.class)).thenReturn(mock(UiModeManager.class));
@@ -313,6 +319,260 @@ public class JobSchedulerServiceTest {
}
@Test
+ public void testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_disabled() {
+ JobStatus jobUij = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+ createJobInfo(1)
+ .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus jobEj = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+ createJobInfo(2).setExpedited(true));
+ JobStatus jobReg = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+ createJobInfo(3));
+ spyOn(jobUij);
+ when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+ jobUij.startedAsUserInitiatedJob = true;
+ spyOn(jobEj);
+ when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
+ jobEj.startedAsExpeditedJob = true;
+
+ mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = false;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
+ mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
+
+ // Safeguards disabled -> no penalties.
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 1 UIJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 2 UIJ timeouts. Safeguards disabled -> no penalties.
+ jobUij.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 1 EJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 2 EJ timeouts. Safeguards disabled -> no penalties.
+ jobEj.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 1 reg timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 2 Reg timeouts. Safeguards disabled -> no penalties.
+ jobReg.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+ }
+
+ @Test
+ public void testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_enabled() {
+ JobStatus jobUij = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+ createJobInfo(1)
+ .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus jobEj = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+ createJobInfo(2).setExpedited(true));
+ JobStatus jobReg = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
+ createJobInfo(3));
+ spyOn(jobUij);
+ when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+ jobUij.startedAsUserInitiatedJob = true;
+ spyOn(jobEj);
+ when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
+ jobEj.startedAsExpeditedJob = true;
+
+ mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
+ mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
+
+ // No timeouts -> no penalties.
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 1 UIJ timeout. No execution penalty yet.
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // Not a timeout -> 1 UIJ timeout. No execution penalty yet.
+ jobUij.madeActive = sUptimeMillisClock.millis() - 1;
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 2 UIJ timeouts. Min execution penalty only for UIJs.
+ jobUij.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 1 EJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 2 EJ timeouts. Max execution penalty for EJs.
+ jobEj.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 1 reg timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+
+ // 2 Reg timeouts. Max execution penalty for regular jobs.
+ jobReg.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMinJobExecutionGuaranteeMs(jobReg));
+ }
+
+ @Test
public void testGetMaxJobExecutionTimeMs() {
JobStatus jobUIDT = createJobStatus("testGetMaxJobExecutionTimeMs",
createJobInfo(10)
@@ -327,7 +587,7 @@ public class JobSchedulerServiceTest {
doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
.when(quotaController).getMaxJobExecutionTimeMsLocked(any());
doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
- .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
+ .when(tareController).getMaxJobExecutionTimeMsLocked(any());
grantRunUserInitiatedJobsPermission(true);
assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
@@ -337,6 +597,306 @@ public class JobSchedulerServiceTest {
mService.getMaxJobExecutionTimeMs(jobUIDT));
}
+ @Test
+ public void testGetMaxJobExecutionTimeMs_timeoutSafeguards_disabled() {
+ JobStatus jobUij = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+ createJobInfo(1)
+ .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+ createJobInfo(2).setExpedited(true));
+ JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+ createJobInfo(3));
+ spyOn(jobUij);
+ when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+ jobUij.startedAsUserInitiatedJob = true;
+ spyOn(jobEj);
+ when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
+ jobEj.startedAsExpeditedJob = true;
+
+ QuotaController quotaController = mService.getQuotaController();
+ spyOn(quotaController);
+ TareController tareController = mService.getTareController();
+ spyOn(tareController);
+ doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+ .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
+ doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+ .when(tareController).getMaxJobExecutionTimeMsLocked(any());
+
+ mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = false;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
+ mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
+
+ // Safeguards disabled -> no penalties.
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 1 UIJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 2 UIJ timeouts. Safeguards disabled -> no penalties.
+ jobUij.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 1 EJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 2 EJ timeouts. Safeguards disabled -> no penalties.
+ jobEj.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 1 reg timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 2 Reg timeouts. Safeguards disabled -> no penalties.
+ jobReg.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+ }
+
+ @Test
+ public void testGetMaxJobExecutionTimeMs_timeoutSafeguards_enabled() {
+ JobStatus jobUij = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+ createJobInfo(1)
+ .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+ JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+ createJobInfo(2).setExpedited(true));
+ JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
+ createJobInfo(3));
+ spyOn(jobUij);
+ when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+ jobUij.startedAsUserInitiatedJob = true;
+ spyOn(jobEj);
+ when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
+ jobEj.startedAsExpeditedJob = true;
+
+ QuotaController quotaController = mService.getQuotaController();
+ spyOn(quotaController);
+ TareController tareController = mService.getTareController();
+ spyOn(tareController);
+ doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+ .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
+ doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
+ .when(tareController).getMaxJobExecutionTimeMsLocked(any());
+
+ mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
+ mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
+ mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
+
+ // No timeouts -> no penalties.
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 1 UIJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // Not a timeout -> 1 UIJ timeout. No max execution penalty yet.
+ jobUij.madeActive = sUptimeMillisClock.millis() - 1;
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 2 UIJ timeouts. Max execution penalty only for UIJs.
+ jobUij.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 1 EJ timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // Not a timeout -> 1 EJ timeout. No max execution penalty yet.
+ jobEj.madeActive = sUptimeMillisClock.millis() - 1;
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 2 EJ timeouts. Max execution penalty for EJs.
+ jobEj.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 1 reg timeout. No max execution penalty yet.
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // Not a timeout -> 1 reg timeout. No max execution penalty yet.
+ jobReg.madeActive = sUptimeMillisClock.millis() - 1;
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+
+ // 2 Reg timeouts. Max execution penalty for regular jobs.
+ jobReg.madeActive =
+ sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
+ mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
+ grantRunUserInitiatedJobsPermission(true);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ grantRunUserInitiatedJobsPermission(false);
+ assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+ mService.getMaxJobExecutionTimeMs(jobUij));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMaxJobExecutionTimeMs(jobEj));
+ assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+ mService.getMaxJobExecutionTimeMs(jobReg));
+ }
+
/**
* Confirm that
* {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
@@ -1226,6 +1786,7 @@ public class JobSchedulerServiceTest {
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
for (int i = 0; i < 500; ++i) {
@@ -1249,6 +1810,7 @@ public class JobSchedulerServiceTest {
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
for (int i = 0; i < 500; ++i) {
@@ -1270,6 +1832,7 @@ public class JobSchedulerServiceTest {
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
for (int i = 0; i < 500; ++i) {
@@ -1292,6 +1855,7 @@ public class JobSchedulerServiceTest {
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
for (int i = 0; i < 500; ++i) {
@@ -1315,6 +1879,7 @@ public class JobSchedulerServiceTest {
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(false).build();
final JobWorkItem item = new JobWorkItem.Builder().build();
@@ -1337,6 +1902,7 @@ public class JobSchedulerServiceTest {
mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
mService.updateQuotaTracker();
+ mService.resetScheduleQuota();
final JobInfo job = createJobInfo().setPersisted(true).build();
final JobWorkItem item = new JobWorkItem.Builder().build();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 2180a781e437..2b56ea8bba33 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -73,6 +73,7 @@ import android.telephony.TelephonyManager;
import android.util.DataUnit;
import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -124,6 +125,10 @@ public class ConnectivityControllerTest {
LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class);
LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal);
+ // Used in JobStatus.
+ LocalServices.removeServiceForTest(JobSchedulerInternal.class);
+ LocalServices.addService(JobSchedulerInternal.class, mock(JobSchedulerInternal.class));
+
// Freeze the clocks at this moment in time
JobSchedulerService.sSystemClock =
Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 05780ebe6c4b..1de7e3719112 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -21,6 +21,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
+import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
import static com.android.server.job.JobSchedulerService.RARE_INDEX;
@@ -45,6 +46,8 @@ import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.when;
@@ -582,6 +585,40 @@ public class JobStatusTest {
}
@Test
+ public void testGetEffectiveStandbyBucket_buggyApp() {
+ when(mJobSchedulerInternal.isAppConsideredBuggy(
+ anyInt(), anyString(), anyInt(), anyString()))
+ .thenReturn(true);
+
+ final JobInfo jobInfo = new JobInfo.Builder(1234, TEST_JOB_COMPONENT).build();
+ JobStatus job = createJobStatus(jobInfo);
+
+ // Exempt apps be exempting.
+ job.setStandbyBucket(EXEMPTED_INDEX);
+ assertEquals(EXEMPTED_INDEX, job.getEffectiveStandbyBucket());
+
+ // Actual bucket is higher than the buggy cap, so the cap comes into effect.
+ job.setStandbyBucket(ACTIVE_INDEX);
+ assertEquals(WORKING_INDEX, job.getEffectiveStandbyBucket());
+
+ // Buckets at the cap or below shouldn't be affected.
+ job.setStandbyBucket(WORKING_INDEX);
+ assertEquals(WORKING_INDEX, job.getEffectiveStandbyBucket());
+
+ job.setStandbyBucket(FREQUENT_INDEX);
+ assertEquals(FREQUENT_INDEX, job.getEffectiveStandbyBucket());
+
+ job.setStandbyBucket(RARE_INDEX);
+ assertEquals(RARE_INDEX, job.getEffectiveStandbyBucket());
+
+ job.setStandbyBucket(RESTRICTED_INDEX);
+ assertEquals(RESTRICTED_INDEX, job.getEffectiveStandbyBucket());
+
+ job.setStandbyBucket(NEVER_INDEX);
+ assertEquals(NEVER_INDEX, job.getEffectiveStandbyBucket());
+ }
+
+ @Test
public void testModifyingInternalFlags() {
final JobInfo jobInfo =
new JobInfo.Builder(101, new ComponentName("foo", "bar"))
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index fb59ea2bb63b..7cc01e1b4292 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -58,6 +58,7 @@ import android.util.SparseArray;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.controllers.PrefetchController.PcConstants;
@@ -135,6 +136,9 @@ public class PrefetchControllerTest {
when(mJobSchedulerService.getPackagesForUidLocked(anyInt()))
.thenAnswer(invocationOnMock
-> mPackagesForUid.get(invocationOnMock.getArgument(0)));
+ // Used in JobStatus.
+ doReturn(mock(JobSchedulerInternal.class))
+ .when(() -> LocalServices.getService(JobSchedulerInternal.class));
// Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
// in the past, and PrefetchController sometimes floors values at 0, so if the test time
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 6f713e0fad64..dce162c58d0b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -85,6 +85,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
import com.android.server.PowerAllowlistInternal;
+import com.android.server.job.JobSchedulerInternal;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobStore;
import com.android.server.job.controllers.QuotaController.ExecutionStats;
@@ -190,6 +191,8 @@ public class QuotaControllerTest {
doReturn(mPowerAllowlistInternal)
.when(() -> LocalServices.getService(PowerAllowlistInternal.class));
// Used in JobStatus.
+ doReturn(mock(JobSchedulerInternal.class))
+ .when(() -> LocalServices.getService(JobSchedulerInternal.class));
doReturn(mPackageManagerInternal)
.when(() -> LocalServices.getService(PackageManagerInternal.class));
// Used in QuotaController.Handler.
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index c6a5260a695e..ea261d1fa711 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -17,8 +17,10 @@
package com.android.server.wallpaper;
import static android.app.WallpaperManager.COMMAND_REAPPLY;
+import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
import static android.os.FileObserver.CLOSE_WRITE;
+import static android.os.UserHandle.MIN_SECONDARY_USER_ID;
import static android.os.UserHandle.USER_SYSTEM;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -105,6 +107,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.util.List;
/**
* Tests for the {@link WallpaperManagerService} class.
@@ -171,12 +174,12 @@ public class WallpaperManagerServiceTests {
sImageWallpaperComponentName = ComponentName.unflattenFromString(
sContext.getResources().getString(R.string.image_wallpaper_component));
// Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper.
- sDefaultWallpaperComponent = WallpaperManager.getCmfDefaultWallpaperComponent(sContext);
+ sDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(sContext);
if (sDefaultWallpaperComponent == null) {
sDefaultWallpaperComponent = sImageWallpaperComponentName;
doReturn(sImageWallpaperComponentName).when(() ->
- WallpaperManager.getCmfDefaultWallpaperComponent(any()));
+ WallpaperManager.getDefaultWallpaperComponent(any()));
} else {
sContext.addMockService(sDefaultWallpaperComponent, sWallpaperService);
}
@@ -261,6 +264,25 @@ public class WallpaperManagerServiceTests {
}
/**
+ * Tests that the fundamental fields are set by the main WallpaperData constructor
+ */
+ @Test
+ public void testWallpaperDataConstructor() {
+ final int testUserId = MIN_SECONDARY_USER_ID;
+ for (int which: List.of(FLAG_LOCK, FLAG_SYSTEM)) {
+ WallpaperData newWallpaperData = new WallpaperData(testUserId, which);
+ assertEquals(which, newWallpaperData.mWhich);
+ assertEquals(testUserId, newWallpaperData.userId);
+
+ WallpaperData wallpaperData = mService.getWallpaperSafeLocked(testUserId, which);
+ assertEquals(wallpaperData.cropFile.getAbsolutePath(),
+ newWallpaperData.cropFile.getAbsolutePath());
+ assertEquals(wallpaperData.wallpaperFile.getAbsolutePath(),
+ newWallpaperData.wallpaperFile.getAbsolutePath());
+ }
+ }
+
+ /**
* Tests that internal basic data should be correct after boot up.
*/
@Test
@@ -402,10 +424,7 @@ public class WallpaperManagerServiceTests {
fail("exception occurred while writing system wallpaper attributes");
}
- WallpaperData shouldMatchSystem = new WallpaperData(systemWallpaperData.userId,
- systemWallpaperData.wallpaperFile.getParentFile(),
- systemWallpaperData.wallpaperFile.getAbsolutePath(),
- systemWallpaperData.cropFile.getAbsolutePath());
+ WallpaperData shouldMatchSystem = new WallpaperData(0, FLAG_SYSTEM);
try {
TypedXmlPullParser parser = Xml.newBinaryPullParser();
mService.mWallpaperDataParser.parseWallpaperAttributes(parser, shouldMatchSystem, true);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 662477ddbbe9..8346050c3c89 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -26,6 +26,7 @@ import static android.hardware.biometrics.BiometricPrompt.DISMISSED_REASON_NEGAT
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING;
+import static com.android.server.biometrics.BiometricServiceStateProto.STATE_ERROR_PENDING_SYSUI;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -48,6 +49,7 @@ import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.ComponentInfoInternal;
@@ -104,6 +106,7 @@ public class AuthSessionTest {
@Mock private KeyStore mKeyStore;
@Mock private AuthSession.ClientDeathReceiver mClientDeathReceiver;
@Mock private BiometricFrameworkStatsLogger mBiometricFrameworkStatsLogger;
+ @Mock BiometricSensorPrivacy mBiometricSensorPrivacy;
private Random mRandom;
private IBinder mToken;
@@ -210,6 +213,40 @@ public class AuthSessionTest {
}
@Test
+ public void testOnErrorReceived_lockoutError() throws RemoteException {
+ setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+ setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ mock(IBiometricAuthenticator.class));
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ 0 /* operationId */,
+ 0 /* userId */);
+ session.goToInitialState();
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
+ session.onCookieReceived(
+ session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie());
+ }
+ assertTrue(session.allCookiesReceived());
+ assertEquals(STATE_AUTH_STARTED, session.getState());
+
+ // Either of strong sensor's lockout should cancel both sensors.
+ final int cookie1 = session.mPreAuthInfo.eligibleSensors.get(0).getCookie();
+ session.onErrorReceived(0, cookie1, BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, 0);
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertEquals(BiometricSensor.STATE_CANCELING, sensor.getSensorState());
+ }
+ assertEquals(STATE_ERROR_PENDING_SYSUI, session.getState());
+
+ // If the sensor is STATE_CANCELING, delayed onAuthenticationRejected() shouldn't change the
+ // session state to STATE_AUTH_PAUSED.
+ session.onAuthenticationRejected(1);
+ assertEquals(STATE_ERROR_PENDING_SYSUI, session.getState());
+ }
+
+ @Test
public void testCancelReducesAppetiteForCookies() throws Exception {
setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
mock(IBiometricAuthenticator.class));
@@ -571,7 +608,8 @@ public class AuthSessionTest {
promptInfo,
TEST_PACKAGE,
checkDevicePolicyManager,
- mContext);
+ mContext,
+ mBiometricSensorPrivacy);
}
private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
new file mode 100644
index 000000000000..0c98c8d88d83
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
+
+import static com.android.server.biometrics.sensors.LockoutTracker.LOCKOUT_NONE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.trust.ITrustManager;
+import android.content.Context;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.IBiometricAuthenticator;
+import android.hardware.biometrics.PromptInfo;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+
+@Presubmit
+@SmallTest
+public class PreAuthInfoTest {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ private static final int SENSOR_ID_FACE = 1;
+ private static final String TEST_PACKAGE_NAME = "PreAuthInfoTestPackage";
+
+ @Mock
+ IBiometricAuthenticator mFaceAuthenticator;
+ @Mock
+ Context mContext;
+ @Mock
+ ITrustManager mTrustManager;
+ @Mock
+ DevicePolicyManager mDevicePolicyManager;
+ @Mock
+ BiometricService.SettingObserver mSettingObserver;
+ @Mock
+ BiometricSensorPrivacy mBiometricSensorPrivacyUtil;
+
+ @Before
+ public void setup() throws RemoteException {
+ when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true);
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(any(), anyInt()))
+ .thenReturn(KEYGUARD_DISABLE_FEATURES_NONE);
+ when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+ when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
+ when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
+ when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
+ .thenReturn(LOCKOUT_NONE);
+ }
+
+ @Test
+ public void testFaceAuthentication_whenCameraPrivacyIsEnabled() throws Exception {
+ when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(true);
+
+ BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+ @Override
+ boolean confirmationAlwaysRequired(int userId) {
+ return false;
+ }
+
+ @Override
+ boolean confirmationSupported() {
+ return false;
+ }
+ };
+ PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+ PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor),
+ 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil);
+
+ assertThat(preAuthInfo.eligibleSensors).isEmpty();
+ }
+
+ @Test
+ public void testFaceAuthentication_whenCameraPrivacyIsDisabled() throws Exception {
+ when(mBiometricSensorPrivacyUtil.isCameraPrivacyEnabled()).thenReturn(false);
+
+ BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FACE, TYPE_FACE,
+ BiometricManager.Authenticators.BIOMETRIC_STRONG, mFaceAuthenticator) {
+ @Override
+ boolean confirmationAlwaysRequired(int userId) {
+ return false;
+ }
+
+ @Override
+ boolean confirmationSupported() {
+ return false;
+ }
+ };
+ PromptInfo promptInfo = new PromptInfo();
+ promptInfo.setConfirmationRequested(false /* requireConfirmation */);
+ promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
+ promptInfo.setDisallowBiometricsIfPolicyExists(false /* checkDevicePolicy */);
+ PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+ mSettingObserver, List.of(sensor),
+ 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+ false /* checkDevicePolicyManager */, mContext, mBiometricSensorPrivacyUtil);
+
+ assertThat(preAuthInfo.eligibleSensors).hasSize(1);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index 5f19887f15b6..e4571194b37d 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -143,25 +143,13 @@ public class ContentCaptureManagerServiceTest {
}
@Test
- public void constructor_contentProtection_serviceInfoThrows_noBlocklistManager() {
- mDevCfgEnableContentProtectionReceiver = true;
- mContentProtectionServiceInfoConstructorShouldThrow = true;
-
- mContentCaptureManagerService = new TestContentCaptureManagerService();
-
- assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
- assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
- verifyZeroInteractions(mMockContentProtectionBlocklistManager);
- }
-
- @Test
public void constructor_contentProtection_enabled_createsBlocklistManager() {
mDevCfgEnableContentProtectionReceiver = true;
mContentCaptureManagerService = new TestContentCaptureManagerService();
assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
- assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
+ assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
}
@@ -361,6 +349,21 @@ public class ContentCaptureManagerServiceTest {
.getContentCaptureManagerServiceStub()
.onLoginDetected(PARCELED_EVENTS);
+ assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+ assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0);
+ verifyZeroInteractions(mMockRemoteContentProtectionService);
+ }
+
+ @Test
+ public void onLoginDetected_invalidPermissions() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentProtectionServiceInfoConstructorShouldThrow = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ mContentCaptureManagerService
+ .getContentCaptureManagerServiceStub()
+ .onLoginDetected(PARCELED_EVENTS);
+
assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0);
verifyZeroInteractions(mMockRemoteContentProtectionService);
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index 416b1f49f5d9..c36122b7e788 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -40,8 +40,7 @@ import androidx.test.core.app.ApplicationProvider
import com.android.server.input.BatteryController.BluetoothBatteryManager
import com.android.server.input.BatteryController.BluetoothBatteryManager.BluetoothBatteryListener
import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS
-import com.android.server.input.BatteryController.UEventManager
-import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener
+import com.android.server.input.BatteryController.UEventBatteryListener
import com.android.server.input.BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS
import org.hamcrest.Description
import org.hamcrest.Matcher
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
index 677144c144a7..72066ea951e3 100644
--- a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
@@ -23,9 +23,12 @@ import android.hardware.display.DisplayViewport
import android.os.IInputConstants
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
+import android.provider.Settings
+import android.test.mock.MockContentResolver
import android.view.Display
import android.view.PointerIcon
import androidx.test.InstrumentationRegistry
+import com.android.internal.util.test.FakeSettingsProvider
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -33,6 +36,8 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
@@ -44,7 +49,9 @@ import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.junit.MockitoJUnit
+import org.mockito.stubbing.OngoingStubbing
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -58,7 +65,10 @@ import java.util.concurrent.TimeUnit
class InputManagerServiceTests {
@get:Rule
- val rule = MockitoJUnit.rule()!!
+ val mockitoRule = MockitoJUnit.rule()!!
+
+ @get:Rule
+ val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
@Mock
private lateinit var native: NativeInputManagerService
@@ -66,17 +76,25 @@ class InputManagerServiceTests {
@Mock
private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks
+ @Mock
+ private lateinit var uEventManager: UEventManager
+
private lateinit var service: InputManagerService
private lateinit var localService: InputManagerInternal
private lateinit var context: Context
private lateinit var testLooper: TestLooper
+ private lateinit var contentResolver: MockContentResolver
@Before
fun setup() {
context = spy(ContextWrapper(InstrumentationRegistry.getContext()))
+ contentResolver = MockContentResolver(context)
+ contentResolver.addProvider(Settings.AUTHORITY, FakeSettingsProvider())
+ whenever(context.contentResolver).thenReturn(contentResolver)
testLooper = TestLooper()
service =
- InputManagerService(object : InputManagerService.Injector(context, testLooper.looper) {
+ InputManagerService(object : InputManagerService.Injector(
+ context, testLooper.looper, uEventManager) {
override fun getNativeService(
service: InputManagerService?
): NativeInputManagerService {
@@ -92,9 +110,36 @@ class InputManagerServiceTests {
}
@Test
+ fun testStart() {
+ verifyZeroInteractions(native)
+
+ service.start()
+ verify(native).start()
+ }
+
+ @Test
+ fun testInputSettingsUpdatedOnSystemRunning() {
+ verifyZeroInteractions(native)
+
+ service.systemRunning()
+
+ verify(native).setPointerSpeed(anyInt())
+ verify(native).setTouchpadPointerSpeed(anyInt())
+ verify(native).setTouchpadNaturalScrollingEnabled(anyBoolean())
+ verify(native).setTouchpadTapToClickEnabled(anyBoolean())
+ verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
+ verify(native).setShowTouches(anyBoolean())
+ verify(native).reloadPointerIcons()
+ verify(native).notifyKeyGestureTimeoutsChanged()
+ verify(native).setMotionClassifierEnabled(anyBoolean())
+ verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
+ verify(native).setStylusPointerIconEnabled(anyBoolean())
+ }
+
+ @Test
fun testPointerDisplayUpdatesWhenDisplayViewportsChanged() {
val displayId = 123
- `when`(wmCallbacks.pointerDisplayId).thenReturn(displayId)
+ whenever(wmCallbacks.pointerDisplayId).thenReturn(displayId)
val viewports = listOf<DisplayViewport>()
localService.setDisplayViewports(viewports)
verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java))
@@ -337,3 +382,5 @@ class InputManagerServiceTests {
thread.join(100 /*millis*/)
}
}
+
+private fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall)
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index 67158f24839d..3f4a4fba841e 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -105,6 +105,8 @@ class KeyboardBacklightControllerTests {
private lateinit var iInputManager: IInputManager
@Mock
private lateinit var native: NativeInputManagerService
+ @Mock
+ private lateinit var uEventManager: UEventManager
private lateinit var keyboardBacklightController: KeyboardBacklightController
private lateinit var context: Context
private lateinit var dataStore: PersistentDataStore
@@ -130,7 +132,7 @@ class KeyboardBacklightControllerTests {
})
testLooper = TestLooper()
keyboardBacklightController = KeyboardBacklightController(context, native, dataStore,
- testLooper.looper, FakeAnimatorFactory())
+ testLooper.looper, FakeAnimatorFactory(), uEventManager)
InputManagerGlobal.resetInstance(iInputManager)
val inputManager = InputManager(context)
`when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
index beab107ec556..0b147c333dd2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
@@ -18,6 +18,8 @@ package com.android.server.notification;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_POSTED;
+import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_UPDATED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -33,6 +35,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.UiServiceTestCase;
+import com.android.internal.util.FrameworkStatsLog;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -155,4 +158,54 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase {
// Then: should return false
assertFalse(NotificationRecordLogger.isNonDismissible(p.r));
}
+
+ @Test
+ public void testGetFsiState_stickyHunFlagDisabled_zero() {
+ final int fsiState = NotificationRecordLogger.getFsiState(
+ /* isStickyHunFlagEnabled= */ false,
+ /* hasFullScreenIntent= */ true,
+ /* hasFsiRequestedButDeniedFlag= */ true,
+ /* eventType= */ NOTIFICATION_POSTED);
+ assertEquals(0, fsiState);
+ }
+
+ @Test
+ public void testGetFsiState_isUpdate_zero() {
+ final int fsiState = NotificationRecordLogger.getFsiState(
+ /* isStickyHunFlagEnabled= */ true,
+ /* hasFullScreenIntent= */ true,
+ /* hasFsiRequestedButDeniedFlag= */ true,
+ /* eventType= */ NOTIFICATION_UPDATED);
+ assertEquals(0, fsiState);
+ }
+
+ @Test
+ public void testGetFsiState_hasFsi_allowedEnum() {
+ final int fsiState = NotificationRecordLogger.getFsiState(
+ /* isStickyHunFlagEnabled= */ true,
+ /* hasFullScreenIntent= */ true,
+ /* hasFsiRequestedButDeniedFlag= */ false,
+ /* eventType= */ NOTIFICATION_POSTED);
+ assertEquals(FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_ALLOWED, fsiState);
+ }
+
+ @Test
+ public void testGetFsiState_fsiPermissionDenied_deniedEnum() {
+ final int fsiState = NotificationRecordLogger.getFsiState(
+ /* isStickyHunFlagEnabled= */ true,
+ /* hasFullScreenIntent= */ false,
+ /* hasFsiRequestedButDeniedFlag= */ true,
+ /* eventType= */ NOTIFICATION_POSTED);
+ assertEquals(FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__FSI_DENIED, fsiState);
+ }
+
+ @Test
+ public void testGetFsiState_noFsi_noFsiEnum() {
+ final int fsiState = NotificationRecordLogger.getFsiState(
+ /* isStickyHunFlagEnabled= */ true,
+ /* hasFullScreenIntent= */ false,
+ /* hasFsiRequestedButDeniedFlag= */ false,
+ /* eventType= */ NOTIFICATION_POSTED);
+ assertEquals(FrameworkStatsLog.NOTIFICATION_REPORTED__FSI_STATE__NO_FSI, fsiState);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 48ad86da1bc5..47340c1aef06 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -36,6 +36,9 @@ import static android.util.StatsLog.ANNOTATION_ID_IS_UID;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_ID_FIELD_NUMBER;
import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_NAME_FIELD_NUMBER;
import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.IMPORTANCE_FIELD_NUMBER;
@@ -48,7 +51,6 @@ import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_P
import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT;
import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT;
import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID;
-
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertNull;
@@ -102,6 +104,7 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.PermissionManager;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
@@ -185,6 +188,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Mock Context mContext;
@Mock ZenModeHelper mMockZenModeHelper;
@Mock AppOpsManager mAppOpsManager;
+ @Mock PermissionManager mPermissionManager;
private NotificationManager.Policy mTestNotificationPolicy;
@@ -218,6 +222,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
InstrumentationRegistry.getContext().getResources());
when(mContext.getContentResolver()).thenReturn(
InstrumentationRegistry.getContext().getContentResolver());
+ when(mPm.getPermissionFlags(any(), any(), any()))
+ .thenReturn(PackageManager.FLAG_PERMISSION_USER_SET);
when(mContext.getPackageManager()).thenReturn(mPm);
when(mContext.getApplicationInfo()).thenReturn(legacy);
// most tests assume badging is enabled
@@ -303,7 +309,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mStatsEventBuilderFactory = new WrappedSysUiStatsEvent.WrappedBuilderFactory();
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager,
+ mStatsEventBuilderFactory, false);
resetZenModeHelper();
mAudioAttributes = new AudioAttributes.Builder()
@@ -645,7 +652,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_oldXml_migrates() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
String xml = "<ranking version=\"2\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
@@ -711,7 +718,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_oldXml_backup_migratesWhenPkgInstalled() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
when(mPm.getPackageUidAsUser("pkg1", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
when(mPm.getPackageUidAsUser("pkg2", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
@@ -789,7 +796,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -846,7 +853,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_newXml_permissionNotificationOff() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -903,7 +910,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, true);
String xml = "<ranking version=\"4\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -959,7 +966,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_oldXml_migration_NoUid() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
String xml = "<ranking version=\"2\">\n"
@@ -992,7 +999,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testReadXml_newXml_noMigration_NoUid() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
when(mPm.getPackageUidAsUser("something", USER_SYSTEM)).thenReturn(UNKNOWN_UID);
String xml = "<ranking version=\"3\">\n"
@@ -1024,7 +1031,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testChannelXmlForNonBackup_postMigration() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false));
@@ -1110,7 +1117,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testChannelXmlForBackup_postMigration() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false));
@@ -1202,7 +1209,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testChannelXmlForBackup_postMigration_noExternal() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
appPermissions.put(new Pair<>(UID_P, PKG_P), new Pair<>(true, false));
@@ -1287,7 +1294,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testChannelXmlForBackup_postMigration_noLocalSettings() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> appPermissions = new ArrayMap<>();
appPermissions.put(new Pair<>(1, "first"), new Pair<>(true, false));
@@ -1498,7 +1505,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
new FileNotFoundException("")).thenReturn(resId);
mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
+ mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mStatsEventBuilderFactory, false);
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -2443,7 +2450,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
@@ -2474,7 +2481,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
// create notification channel that can bypass dnd, but app is blocked
@@ -2498,7 +2505,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
// create notification channel that can bypass dnd, but app is blocked
@@ -2552,7 +2559,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
assertFalse(mHelper.areChannelsBypassingDnd());
verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean());
@@ -2565,7 +2572,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0);
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
assertFalse(mHelper.areChannelsBypassingDnd());
verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean());
@@ -3668,7 +3675,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
+ "</package>\n"
+ "</ranking>\n";
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
loadByteArrayXml(preQXml.getBytes(), true, USER_SYSTEM);
@@ -3682,7 +3689,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -3752,7 +3759,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -3765,7 +3772,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -3779,7 +3786,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -3795,7 +3802,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -3851,7 +3858,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -3889,7 +3896,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
loadStreamXml(baos, false, UserHandle.USER_ALL);
@@ -4547,7 +4554,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testPlaceholderConversationId_shortcutRequired() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
final String xml = "<ranking version=\"1\">\n"
@@ -4567,7 +4574,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testNormalConversationId_shortcutRequired() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
final String xml = "<ranking version=\"1\">\n"
@@ -4587,7 +4594,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testNoConversationId_shortcutRequired() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
final String xml = "<ranking version=\"1\">\n"
@@ -4607,7 +4614,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testDeleted_noTime() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
final String xml = "<ranking version=\"1\">\n"
@@ -4627,7 +4634,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testDeleted_twice() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
mHelper.createNotificationChannel(
@@ -4642,7 +4649,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testDeleted_recentTime() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
mHelper.createNotificationChannel(
@@ -4661,7 +4668,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
null);
parser.nextTag();
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
mHelper.readXml(parser, true, USER_SYSTEM);
@@ -4673,7 +4680,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testUnDelete_time() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
mHelper.createNotificationChannel(
@@ -4695,7 +4702,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testDeleted_longTime() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mPermissionHelper, mLogger,
+ mPermissionHelper, mPermissionManager, mLogger,
mAppOpsManager, mStatsEventBuilderFactory, false);
long time = System.currentTimeMillis() - (DateUtils.DAY_IN_MILLIS * 30);
@@ -5464,4 +5471,93 @@ public class PreferencesHelperTest extends UiServiceTestCase {
verifyZeroInteractions(mHandler);
}
+
+ @Test
+ public void testGetFsiState_flagDisabled_zero() {
+ final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+ /* requestedFsiPermission */ true, /* isFlagEnabled= */ false);
+
+ assertEquals(0, fsiState);
+ }
+
+ @Test
+ public void testGetFsiState_appDidNotRequest_enumNotRequested() {
+ final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+ /* requestedFsiPermission */ false, /* isFlagEnabled= */ true);
+
+ assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED, fsiState);
+ }
+
+ @Test
+ public void testGetFsiState_permissionGranted_enumGranted() {
+ when(mPermissionManager.checkPermissionForPreflight(any(), any()))
+ .thenReturn(PermissionManager.PERMISSION_GRANTED);
+
+ final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+ /* requestedFsiPermission= */ true, /* isFlagEnabled= */ true);
+
+ assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, fsiState);
+ }
+
+ @Test
+ public void testGetFsiState_permissionSoftDenied_enumDenied() {
+ when(mPermissionManager.checkPermissionForPreflight(any(), any()))
+ .thenReturn(PermissionManager.PERMISSION_SOFT_DENIED);
+
+ final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+ /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+
+ assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
+ }
+
+ @Test
+ public void testGetFsiState_permissionHardDenied_enumDenied() {
+ when(mPermissionManager.checkPermissionForPreflight(any(), any()))
+ .thenReturn(PermissionManager.PERMISSION_HARD_DENIED);
+
+ final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
+ /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+
+ assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
+ }
+
+ @Test
+ public void testIsFsiPermissionUserSet_appDidNotRequest_false() {
+ final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
+ /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED,
+ /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
+ /* isStickyHunFlagEnabled= */ true);
+
+ assertFalse(isUserSet);
+ }
+
+ @Test
+ public void testIsFsiPermissionUserSet_flagDisabled_false() {
+ final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
+ /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
+ /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
+ /* isStickyHunFlagEnabled= */ false);
+
+ assertFalse(isUserSet);
+ }
+
+ @Test
+ public void testIsFsiPermissionUserSet_userSet_true() {
+ final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
+ /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
+ /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
+ /* isStickyHunFlagEnabled= */ true);
+
+ assertTrue(isUserSet);
+ }
+
+ @Test
+ public void testIsFsiPermissionUserSet_notUserSet_false() {
+ final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
+ /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
+ /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET,
+ /* isStickyHunFlagEnabled= */ true);
+
+ assertFalse(isUserSet);
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index b59f027fec83..11267268a6fa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1346,6 +1346,16 @@ public class TransitionTests extends WindowTestsBase {
activity1.setVisible(false);
abortTransition.abort();
assertTrue(activity1.isVisible());
+
+ // The mLaunchTaskBehind flag of an invisible initializing activity should not be cleared.
+ final Transition noChangeTransition = controller.createTransition(TRANSIT_OPEN);
+ noChangeTransition.collect(activity1);
+ activity1.setVisibleRequested(false);
+ activity1.setState(ActivityRecord.State.INITIALIZING, "test");
+ activity1.mLaunchTaskBehind = true;
+ mWm.mSyncEngine.abort(noChangeTransition.getSyncId());
+ noChangeTransition.finishTransition();
+ assertTrue(activity1.mLaunchTaskBehind);
}
@Test
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 188894339f6a..5b1c6b1c88b8 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -10318,7 +10318,7 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
CellSignalStrengthLte.USE_RSRP);
sDefaults.putInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT, 300);
- sDefaults.putInt(KEY_PREFERRED_IKE_PROTOCOL_INT, 0);
+ sDefaults.putInt(KEY_PREFERRED_IKE_PROTOCOL_INT, -1);
// Default wifi configurations.
sDefaults.putAll(Wifi.getDefaults());
sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false);
diff --git a/tests/SilkFX/res/layout/gainmap_transform_test.xml b/tests/SilkFX/res/layout/gainmap_transform_test.xml
new file mode 100644
index 000000000000..5aeb53661cbc
--- /dev/null
+++ b/tests/SilkFX/res/layout/gainmap_transform_test.xml
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.test.silkfx.hdr.GainmapTransformsTest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/original"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Original" />
+
+ <Button
+ android:id="@+id/scaled"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Scaled (1/3)" />
+
+ <Button
+ android:id="@+id/rotate_90"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Rotate 90" />
+
+ <Button
+ android:id="@+id/rotate_90_scaled"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Rot90+Scale" />
+
+ <Button
+ android:id="@+id/crop"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Crop" />
+
+ <Button
+ android:id="@+id/crop_200"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Crop 200" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/source_info"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/sdr_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageView
+ android:id="@+id/sdr_source"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="8dp"
+ android:scaleType="fitStart" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@+id/gainmap_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <ImageView
+ android:id="@+id/gainmap"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="8dp"
+ android:scaleType="fitStart" />
+ </LinearLayout>
+
+ </LinearLayout>
+
+</com.android.test.silkfx.hdr.GainmapTransformsTest> \ No newline at end of file
diff --git a/tests/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/SilkFX/src/com/android/test/silkfx/Main.kt
index a6cdbb9865bc..59a6078376cf 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/Main.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/Main.kt
@@ -55,7 +55,9 @@ private val AllDemos = listOf(
Demo("Color Grid", R.layout.color_grid),
Demo("Gradient Sweep", R.layout.gradient_sweep),
Demo("Gainmap Image", R.layout.gainmap_image),
- Demo("Gainmap Decode Test", R.layout.gainmap_decode_test, commonControls = false)
+ Demo("Gainmap Decode Test", R.layout.gainmap_decode_test, commonControls = false),
+ Demo("Gainmap Transform Test", R.layout.gainmap_transform_test,
+ commonControls = false)
)),
DemoGroup("Materials", listOf(
Demo("Glass", GlassActivity::class),
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt
index a004fb5a4305..585320aee615 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapDecodeTest.kt
@@ -17,7 +17,12 @@
package com.android.test.silkfx.hdr
import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.ColorMatrixColorFilter
+import android.graphics.Gainmap
import android.graphics.ImageDecoder
+import android.graphics.Paint
import android.graphics.Rect
import android.util.AttributeSet
import android.widget.Button
@@ -34,6 +39,25 @@ enum class DecodeMode {
CropedSquaredScaled33
}
+fun gainmapVisualizer(gainmap: Gainmap): Bitmap {
+ val map = gainmap.gainmapContents
+ val gainmapVisualizer = Bitmap.createBitmap(map.width, map.height,
+ Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(gainmapVisualizer!!)
+ val paint = Paint()
+ paint.colorFilter = ColorMatrixColorFilter(
+ floatArrayOf(
+ 0f, 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 1f, 0f,
+ 0f, 0f, 0f, 0f, 255f
+ )
+ )
+ canvas.drawBitmap(map, 0f, 0f, paint)
+ canvas.setBitmap(null)
+ return gainmapVisualizer
+}
+
class GainmapDecodeTest(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
private fun decode(mode: DecodeMode) {
@@ -71,7 +95,7 @@ class GainmapDecodeTest(context: Context, attrs: AttributeSet?) : LinearLayout(c
}
}
- val gainmapContents = gainmapImage.gainmap!!.gainmapContents!!
+ val gainmapContents = gainmapImage.gainmap?.let { gainmapVisualizer(it) }
val sdrBitmap = gainmapImage.also { it.gainmap = null }
findViewById<ImageView>(R.id.sdr_source)!!.setImageBitmap(sdrBitmap)
@@ -80,7 +104,7 @@ class GainmapDecodeTest(context: Context, attrs: AttributeSet?) : LinearLayout(c
findViewById<ImageView>(R.id.gainmap)!!.setImageBitmap(gainmapContents)
findViewById<TextView>(R.id.gainmap_label)!!.text =
- "Gainmap Size: ${gainmapContents.width}x${gainmapContents.height}"
+ "Gainmap Size: ${gainmapContents?.width ?: 0}x${gainmapContents?.height ?: 0}"
}
override fun onFinishInflate() {
diff --git a/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt
new file mode 100644
index 000000000000..20984fae2133
--- /dev/null
+++ b/tests/SilkFX/src/com/android/test/silkfx/hdr/GainmapTransformsTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.silkfx.hdr
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.ImageDecoder
+import android.graphics.Matrix
+import android.util.AttributeSet
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.android.test.silkfx.R
+
+class GainmapTransformsTest(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
+
+ private val sourceImage = loadSample()
+
+ private fun loadSample(): Bitmap {
+ val source = ImageDecoder.createSource(resources.assets,
+ "gainmaps/${context.assets.list("gainmaps")!![0]}")
+
+ return ImageDecoder.decodeBitmap(source) { decoder, info, source ->
+ decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ }
+ }
+
+ private fun process(transform: (Bitmap) -> Bitmap) {
+ val result = transform(sourceImage)
+
+ val gainmapContents = result.gainmap?.let { gainmapVisualizer(it) }
+ val sdrBitmap = result.also { it.gainmap = null }
+
+ findViewById<ImageView>(R.id.sdr_source)!!.setImageBitmap(sdrBitmap)
+ findViewById<TextView>(R.id.sdr_label)!!.text =
+ "SDR Size: ${sdrBitmap.width}x${sdrBitmap.height}"
+
+ findViewById<ImageView>(R.id.gainmap)!!.setImageBitmap(gainmapContents)
+ findViewById<TextView>(R.id.gainmap_label)!!.text =
+ "Gainmap Size: ${gainmapContents?.width ?: 0}x${gainmapContents?.height ?: 0}"
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ val sourceInfo = findViewById<TextView>(R.id.source_info)!!
+ sourceInfo.text = "Original size ${sourceImage.width}x${sourceImage.height}"
+ process { it.copy(Bitmap.Config.ARGB_8888, false) }
+
+ findViewById<Button>(R.id.original)!!.setOnClickListener {
+ process { it.copy(Bitmap.Config.ARGB_8888, false) }
+ }
+
+ findViewById<Button>(R.id.scaled)!!.setOnClickListener {
+ process { Bitmap.createScaledBitmap(it, it.width / 3, it.height / 3, true) }
+ }
+
+ findViewById<Button>(R.id.rotate_90)!!.setOnClickListener {
+ process {
+ val width: Int = it.width
+ val height: Int = it.height
+
+ val m = Matrix()
+ m.setRotate(90.0f, (width / 2).toFloat(), (height / 2).toFloat())
+ Bitmap.createBitmap(it, 0, 0, width, height, m, false)
+ }
+ }
+
+ findViewById<Button>(R.id.rotate_90_scaled)!!.setOnClickListener {
+ process {
+ val width: Int = it.width
+ val height: Int = it.height
+
+ val m = Matrix()
+ m.setRotate(90.0f, (width / 2).toFloat(), (height / 2).toFloat())
+ m.preScale(.3f, .3f)
+ Bitmap.createBitmap(it, 0, 0, width, height, m, false)
+ }
+ }
+
+ findViewById<Button>(R.id.crop)!!.setOnClickListener {
+ process {
+ val width: Int = it.width
+ val height: Int = it.height
+ Bitmap.createBitmap(it, width / 2, height / 2,
+ width / 4, height / 4, null, false)
+ }
+ }
+
+ findViewById<Button>(R.id.crop_200)!!.setOnClickListener {
+ process {
+ val width: Int = it.width
+ val height: Int = it.height
+
+ val m = Matrix()
+ m.setRotate(200.0f, (width / 2).toFloat(), (height / 2).toFloat())
+ Bitmap.createBitmap(it, width / 2, height / 2,
+ width / 4, height / 4, m, false)
+ }
+ }
+ }
+} \ No newline at end of file