summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java4
-rw-r--r--apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java9
-rw-r--r--apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java7
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java21
-rw-r--r--boot/Android.bp4
-rw-r--r--core/java/android/app/ActivityThread.java33
-rw-r--r--core/java/android/app/Dialog.java2
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java4
-rw-r--r--core/java/android/app/usage/UsageStats.java11
-rw-r--r--core/java/android/app/usage/UsageStatsManager.java8
-rw-r--r--core/java/android/companion/AssociationInfo.java46
-rw-r--r--core/java/android/content/pm/parsing/ApkLiteParseUtils.java5
-rw-r--r--core/java/android/content/res/CompatibilityInfo.java5
-rw-r--r--core/java/android/debug/AdbManagerInternal.java6
-rw-r--r--core/java/android/hardware/display/DisplayManager.java10
-rw-r--r--core/java/android/hardware/radio/ProgramList.java44
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java3
-rw-r--r--core/java/android/os/TEST_MAPPING12
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java37
-rw-r--r--core/java/android/view/IRemoteAnimationRunner.aidl2
-rw-r--r--core/java/android/view/IWindowSession.aidl48
-rw-r--r--core/java/android/view/ViewRootImpl.java182
-rw-r--r--core/java/android/view/WindowLayout.java20
-rw-r--r--core/java/android/view/WindowManager.java5
-rw-r--r--core/java/android/view/WindowManagerGlobal.java5
-rw-r--r--core/java/android/view/WindowlessWindowLayout.java8
-rw-r--r--core/java/android/view/WindowlessWindowManager.java29
-rw-r--r--core/java/android/window/ClientWindowFrames.java13
-rw-r--r--core/java/android/window/CompatOnBackInvokedCallback.java30
-rw-r--r--core/java/android/window/ProxyOnBackInvokedDispatcher.java12
-rw-r--r--core/java/android/window/SizeConfigurationBuckets.java17
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java63
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java12
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java5
-rw-r--r--core/res/res/layout/notification_template_material_base.xml4
-rw-r--r--core/res/res/layout/notification_template_right_icon.xml4
-rw-r--r--core/res/res/values/config.xml12
-rw-r--r--core/tests/coretests/AndroidManifest.xml5
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java2
-rw-r--r--core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java256
-rw-r--r--core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java15
-rw-r--r--data/etc/services.core.protolog.json42
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java263
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java60
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java85
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java31
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java27
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java116
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java54
-rw-r--r--libs/WindowManager/Shell/res/values-television/config.xml9
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java17
-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.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt129
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java3
-rw-r--r--media/java/android/media/projection/IMediaProjection.aidl14
-rw-r--r--media/java/android/media/projection/MediaProjection.java10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java90
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java32
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java2
-rw-r--r--packages/SystemUI/Android.bp1
-rw-r--r--packages/SystemUI/TEST_MAPPING11
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt2
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java2
-rw-r--r--packages/SystemUI/res-keyguard/values-ca/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-en-rCA/strings.xml16
-rw-r--r--packages/SystemUI/res-keyguard/values-eu/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values-te/strings.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml13
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml4
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml4
-rw-r--r--packages/SystemUI/res/layout/hybrid_conversation_notification.xml6
-rw-r--r--packages/SystemUI/res/layout/hybrid_notification.xml9
-rw-r--r--packages/SystemUI/res/layout/media_ttt_chip.xml5
-rw-r--r--packages/SystemUI/res/layout/media_ttt_chip_receiver.xml5
-rw-r--r--packages/SystemUI/res/values-h800dp/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/attrs.xml6
-rw-r--r--packages/SystemUI/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml8
-rw-r--r--packages/SystemUI/res/values/styles.xml24
-rw-r--r--packages/SystemUI/screenshot/Android.bp48
-rw-r--r--packages/SystemUI/screenshot/AndroidManifest.xml28
-rw-r--r--packages/SystemUI/screenshot/res/values/themes.xml25
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java193
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt22
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestRule.kt206
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestSpec.kt78
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt80
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java9
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java35
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java456
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt188
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java91
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt123
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionClassifier.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java96
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java132
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java41
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java157
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java89
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt78
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java80
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java89
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt82
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt95
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt144
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java18
-rw-r--r--services/companion/java/com/android/server/companion/AssociationStoreImpl.java16
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java372
-rw-r--r--services/companion/java/com/android/server/companion/PersistentDataStore.java24
-rw-r--r--services/companion/java/com/android/server/companion/RolesUtils.java2
-rw-r--r--services/core/java/com/android/server/adb/AdbDebuggingManager.java543
-rw-r--r--services/core/java/com/android/server/adb/AdbService.java8
-rw-r--r--services/core/java/com/android/server/am/TraceErrorLogger.java3
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java30
-rw-r--r--services/core/java/com/android/server/audio/RotationHelper.java31
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java128
-rw-r--r--services/core/java/com/android/server/biometrics/AuthSession.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java84
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java482
-rw-r--r--services/core/java/com/android/server/connectivity/VpnIkev2Utils.java68
-rw-r--r--services/core/java/com/android/server/display/BrightnessThrottler.java205
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java56
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java45
-rw-r--r--services/core/java/com/android/server/display/color/ColorDisplayService.java2
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java12
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java20
-rw-r--r--services/core/java/com/android/server/pm/PackageDexOptimizer.java16
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java2
-rw-r--r--services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java4
-rw-r--r--services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java5
-rw-r--r--services/core/java/com/android/server/testharness/TestHarnessModeService.java1
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java3
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java50
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java57
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java70
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java118
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java16
-rw-r--r--services/core/java/com/android/server/wm/ContentRecordingController.java1
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java18
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java42
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java3
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java48
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java126
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java4
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java5
-rw-r--r--services/core/java/com/android/server/wm/Session.java41
-rw-r--r--services/core/java/com/android/server/wm/StartingSurfaceController.java5
-rw-r--r--services/core/java/com/android/server/wm/Task.java55
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java7
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java100
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java3
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java3
-rw-r--r--services/core/java/com/android/server/wm/Transition.java34
-rw-r--r--services/core/java/com/android/server/wm/TransitionTracer.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java110
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java94
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java8
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java13
-rw-r--r--services/java/com/android/server/SystemServer.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java205
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java43
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java101
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java181
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java15
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java86
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java35
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java15
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java61
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java31
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java146
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java76
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsDatabase.java37
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java3
-rw-r--r--services/usage/java/com/android/server/usage/UserUsageStatsService.java5
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java52
-rw-r--r--telecomm/java/android/telecom/InCallService.java25
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt18
330 files changed, 8588 insertions, 2883 deletions
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index 452bb0ab5909..06207215b7be 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -19,6 +19,7 @@ package android.wm;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
import android.perftests.utils.ManualBenchmarkState;
@@ -86,6 +87,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
+ final Rect mOutAttachedFrame = new Rect();
TestWindow() {
mLayoutParams.setTitle(TestWindow.class.getName());
@@ -104,7 +106,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
long startTime = SystemClock.elapsedRealtimeNanos();
session.addToDisplay(this, mLayoutParams, View.VISIBLE,
Display.DEFAULT_DISPLAY, mRequestedVisibilities, inputChannel,
- mOutInsetsState, mOutControls);
+ mOutInsetsState, mOutControls, mOutAttachedFrame);
final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
state.addExtraResult("add", elapsedTimeNsOfAdd);
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 2fcab59cdec7..78214dc27a9f 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -385,6 +385,12 @@ public class PowerExemptionManager {
*/
public static final int REASON_ACTIVE_DEVICE_ADMIN = 324;
+ /**
+ * Media notification re-generate during transferring.
+ * @hide
+ */
+ public static final int REASON_MEDIA_NOTIFICATION_TRANSFER = 325;
+
/** @hide The app requests out-out. */
public static final int REASON_OPT_OUT_REQUESTED = 1000;
@@ -465,6 +471,7 @@ public class PowerExemptionManager {
REASON_DPO_PROTECTED_APP,
REASON_DISALLOW_APPS_CONTROL,
REASON_ACTIVE_DEVICE_ADMIN,
+ REASON_MEDIA_NOTIFICATION_TRANSFER,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ReasonCode {}
@@ -830,6 +837,8 @@ public class PowerExemptionManager {
return "ACTIVE_DEVICE_ADMIN";
case REASON_OPT_OUT_REQUESTED:
return "REASON_OPT_OUT_REQUESTED";
+ case REASON_MEDIA_NOTIFICATION_TRANSFER:
+ return "REASON_MEDIA_NOTIFICATION_TRANSFER";
default:
return "(unknown:" + reasonCode + ")";
}
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index c43c832992cf..9b64edf53d8c 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -164,6 +164,13 @@ public interface AppStandbyInternal {
@ElapsedRealtimeLong long elapsedRealtime);
/**
+ * Puts the list of apps in the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RARE}
+ * bucket.
+ * @param restoredApps the list of restored apps
+ */
+ void restoreAppsToRare(@NonNull Set<String> restoredApps, int userId);
+
+ /**
* Put the specified app in the
* {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED}
* bucket. If it has been used by the user recently, the restriction will delayed until an
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 9e3e3553c125..5d9f3357125a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -23,6 +23,7 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_DEFAULT_APP_RESTORED;
import static android.app.usage.UsageStatsManager.REASON_SUB_DEFAULT_APP_UPDATE;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_USER_FLAG_INTERACTION;
@@ -1605,6 +1606,26 @@ public class AppStandbyController
}
@Override
+ public void restoreAppsToRare(Set<String> restoredApps, int userId) {
+ final int reason = REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_RESTORED;
+ final long nowElapsed = mInjector.elapsedRealtime();
+ for (String packageName : restoredApps) {
+ // If the package is not installed, don't allow the bucket to be set.
+ if (!mInjector.isPackageInstalled(packageName, 0, userId)) {
+ Slog.e(TAG, "Tried to restore bucket for uninstalled app: " + packageName);
+ continue;
+ }
+
+ final int standbyBucket = getAppStandbyBucket(packageName, userId, nowElapsed, false);
+ // Only update the standby bucket to RARE if the app is still in the NEVER bucket.
+ if (standbyBucket == STANDBY_BUCKET_NEVER) {
+ setAppStandbyBucket(packageName, userId, STANDBY_BUCKET_RARE, reason,
+ nowElapsed, false);
+ }
+ }
+ }
+
+ @Override
public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId,
int callingUid, int callingPid) {
setAppStandbyBuckets(
diff --git a/boot/Android.bp b/boot/Android.bp
index 5b265a5dc9de..3f14ebc4fd92 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -60,8 +60,8 @@ platform_bootclasspath {
module: "art-bootclasspath-fragment",
},
{
- apex: "com.android.bluetooth",
- module: "com.android.bluetooth-bootclasspath-fragment",
+ apex: "com.android.btservices",
+ module: "com.android.btservices-bootclasspath-fragment",
},
{
apex: "com.android.conscrypt",
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 802458b1793c..546efaad85ac 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -274,11 +274,6 @@ public final class ActivityThread extends ClientTransactionHandler
public static final boolean DEBUG_ORDER = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
/**
- * If the activity doesn't become idle in time, the timeout will ensure to apply the pending top
- * process state.
- */
- private static final long PENDING_TOP_PROCESS_STATE_TIMEOUT = 1000;
- /**
* The delay to release the provider when it has no more references. It reduces the number of
* transactions for acquiring and releasing provider if the client accesses the provider
* frequently in a short time.
@@ -367,8 +362,6 @@ public final class ActivityThread extends ClientTransactionHandler
private final AtomicInteger mNumLaunchingActivities = new AtomicInteger();
@GuardedBy("mAppThread")
private int mLastProcessState = PROCESS_STATE_UNKNOWN;
- @GuardedBy("mAppThread")
- private int mPendingProcessState = PROCESS_STATE_UNKNOWN;
ArrayList<WeakReference<AssistStructure>> mLastAssistStructures = new ArrayList<>();
private int mLastSessionId;
final ArrayMap<IBinder, CreateServiceData> mServicesData = new ArrayMap<>();
@@ -2384,7 +2377,6 @@ public final class ActivityThread extends ClientTransactionHandler
if (stopProfiling) {
mProfiler.stopProfiling();
}
- applyPendingProcessState();
return false;
}
}
@@ -3438,16 +3430,7 @@ public final class ActivityThread extends ClientTransactionHandler
return;
}
mLastProcessState = processState;
- // Defer the top state for VM to avoid aggressive JIT compilation affecting activity
- // launch time.
- if (processState == ActivityManager.PROCESS_STATE_TOP
- && mNumLaunchingActivities.get() > 0) {
- mPendingProcessState = processState;
- mH.postDelayed(this::applyPendingProcessState, PENDING_TOP_PROCESS_STATE_TIMEOUT);
- } else {
- mPendingProcessState = PROCESS_STATE_UNKNOWN;
- updateVmProcessState(processState);
- }
+ updateVmProcessState(processState);
if (localLOGV) {
Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
+ (fromIpc ? " (from ipc" : ""));
@@ -3465,20 +3448,6 @@ public final class ActivityThread extends ClientTransactionHandler
VMRuntime.getRuntime().updateProcessState(state);
}
- private void applyPendingProcessState() {
- synchronized (mAppThread) {
- if (mPendingProcessState == PROCESS_STATE_UNKNOWN) {
- return;
- }
- final int pendingState = mPendingProcessState;
- mPendingProcessState = PROCESS_STATE_UNKNOWN;
- // Only apply the pending state if the last state doesn't change.
- if (pendingState == mLastProcessState) {
- updateVmProcessState(pendingState);
- }
- }
- }
-
@Override
public void countLaunchingActivities(int num) {
mNumLaunchingActivities.getAndAdd(num);
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 33cf71256d51..de0f7522f36d 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -1482,8 +1482,6 @@ public class Dialog implements DialogInterface, Window.Callback,
/**
* Returns the {@link OnBackInvokedDispatcher} instance associated with the window that this
* dialog is attached to.
- *
- * Returns null if the dialog is not attached to a window with a decor.
*/
@NonNull
public OnBackInvokedDispatcher getOnBackInvokedDispatcher() {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0e1a5461840e..34c91c360dbe 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -770,6 +770,10 @@ public class DevicePolicyManager {
* <p>If this extra is set to {@code true}, the provisioning flow will still try to connect to
* the internet, but if it fails it will start the offline provisioning flow.
*
+ * <p>For T if this extra is set to {@code true}, the provisioning flow will be forced through
+ * the platform and there will be no attempt to download and install the device policy
+ * management role holder.
+ *
* <p>The default value is {@code false}.
*
* <p>This extra is respected when provided via the provisioning intent actions such as {@link
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index d61abc64fae5..e213c934e95a 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -293,6 +293,17 @@ public final class UsageStats implements Parcelable {
}
/**
+ * Returns the last time the package was used - defined by the latest of
+ * mLastTimeUsed, mLastTimeVisible, mLastTimeForegroundServiceUsed, or mLastTimeComponentUsed.
+ * @hide
+ */
+ public long getLastTimePackageUsed() {
+ return Math.max(mLastTimeUsed,
+ Math.max(mLastTimeVisible,
+ Math.max(mLastTimeForegroundServiceUsed, mLastTimeComponentUsed)));
+ }
+
+ /**
* Returns the number of times the app was launched as an activity from outside of the app.
* Excludes intra-app activity transitions.
* @hide
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index c013fcd5adb9..1dfc7d48640e 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -220,6 +220,11 @@ public final class UsageStatsManager {
*/
public static final int REASON_SUB_DEFAULT_APP_UPDATE = 0x0001;
/**
+ * The app was restored.
+ * @hide
+ */
+ public static final int REASON_SUB_DEFAULT_APP_RESTORED = 0x0002;
+ /**
* The app was interacted with in some way by the system.
* @hide
*/
@@ -1209,6 +1214,9 @@ public final class UsageStatsManager {
case REASON_SUB_DEFAULT_APP_UPDATE:
sb.append("-au");
break;
+ case REASON_SUB_DEFAULT_APP_RESTORED:
+ sb.append("-ar");
+ break;
}
break;
case REASON_MAIN_FORCED_BY_SYSTEM:
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index f7f0235cd508..93748f81ffa1 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -55,6 +55,14 @@ public final class AssociationInfo implements Parcelable {
private final boolean mSelfManaged;
private final boolean mNotifyOnDeviceNearby;
+
+ /**
+ * Indicates that the association has been revoked (removed), but we keep the association
+ * record for final clean up (e.g. removing the app from the list of the role holders).
+ *
+ * @see CompanionDeviceManager#disassociate(int)
+ */
+ private final boolean mRevoked;
private final long mTimeApprovedMs;
/**
* A long value indicates the last time connected reported by selfManaged devices
@@ -71,7 +79,7 @@ public final class AssociationInfo implements Parcelable {
public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby,
- long timeApprovedMs, long lastTimeConnectedMs) {
+ boolean revoked, long timeApprovedMs, long lastTimeConnectedMs) {
if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
}
@@ -91,6 +99,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = selfManaged;
mNotifyOnDeviceNearby = notifyOnDeviceNearby;
+ mRevoked = revoked;
mTimeApprovedMs = timeApprovedMs;
mLastTimeConnectedMs = lastTimeConnectedMs;
}
@@ -176,6 +185,14 @@ public final class AssociationInfo implements Parcelable {
}
/**
+ * @return if the association has been revoked (removed).
+ * @hide
+ */
+ public boolean isRevoked() {
+ return mRevoked;
+ }
+
+ /**
* @return the last time self reported disconnected for selfManaged only.
* @hide
*/
@@ -244,6 +261,7 @@ public final class AssociationInfo implements Parcelable {
+ ", mDeviceProfile='" + mDeviceProfile + '\''
+ ", mSelfManaged=" + mSelfManaged
+ ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
+ + ", mRevoked=" + mRevoked
+ ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
+ ", mLastTimeConnectedMs=" + (
mLastTimeConnectedMs == Long.MAX_VALUE
@@ -260,6 +278,7 @@ public final class AssociationInfo implements Parcelable {
&& mUserId == that.mUserId
&& mSelfManaged == that.mSelfManaged
&& mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
+ && mRevoked == that.mRevoked
&& mTimeApprovedMs == that.mTimeApprovedMs
&& mLastTimeConnectedMs == that.mLastTimeConnectedMs
&& Objects.equals(mPackageName, that.mPackageName)
@@ -271,7 +290,7 @@ public final class AssociationInfo implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
- mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mTimeApprovedMs,
+ mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, mTimeApprovedMs,
mLastTimeConnectedMs);
}
@@ -293,6 +312,7 @@ public final class AssociationInfo implements Parcelable {
dest.writeBoolean(mSelfManaged);
dest.writeBoolean(mNotifyOnDeviceNearby);
+ dest.writeBoolean(mRevoked);
dest.writeLong(mTimeApprovedMs);
dest.writeLong(mLastTimeConnectedMs);
}
@@ -309,6 +329,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = in.readBoolean();
mNotifyOnDeviceNearby = in.readBoolean();
+ mRevoked = in.readBoolean();
mTimeApprovedMs = in.readLong();
mLastTimeConnectedMs = in.readLong();
}
@@ -352,11 +373,13 @@ public final class AssociationInfo implements Parcelable {
@NonNull
private final AssociationInfo mOriginalInfo;
private boolean mNotifyOnDeviceNearby;
+ private boolean mRevoked;
private long mLastTimeConnectedMs;
private Builder(@NonNull AssociationInfo info) {
mOriginalInfo = info;
mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
+ mRevoked = info.mRevoked;
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
}
@@ -388,6 +411,17 @@ public final class AssociationInfo implements Parcelable {
}
/**
+ * Should only be used by the CompanionDeviceManagerService.
+ * @hide
+ */
+ @Override
+ @NonNull
+ public Builder setRevoked(boolean revoked) {
+ mRevoked = revoked;
+ return this;
+ }
+
+ /**
* @hide
*/
@NonNull
@@ -401,6 +435,7 @@ public final class AssociationInfo implements Parcelable {
mOriginalInfo.mDeviceProfile,
mOriginalInfo.mSelfManaged,
mNotifyOnDeviceNearby,
+ mRevoked,
mOriginalInfo.mTimeApprovedMs,
mLastTimeConnectedMs
);
@@ -433,5 +468,12 @@ public final class AssociationInfo implements Parcelable {
*/
@NonNull
Builder setLastTimeConnected(long lastTimeConnectedMs);
+
+ /**
+ * Should only be used by the CompanionDeviceManagerService.
+ * @hide
+ */
+ @NonNull
+ Builder setRevoked(boolean revoked);
}
}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 20a4fdf658c6..10d6f2d6d04b 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -542,14 +542,17 @@ public class ApkLiteParseUtils {
int minVer = DEFAULT_MIN_SDK_VERSION;
String minCode = null;
+ boolean minAssigned = false;
int targetVer = DEFAULT_TARGET_SDK_VERSION;
String targetCode = null;
if (!TextUtils.isEmpty(minSdkVersionString)) {
try {
minVer = Integer.parseInt(minSdkVersionString);
+ minAssigned = true;
} catch (NumberFormatException ignored) {
minCode = minSdkVersionString;
+ minAssigned = !TextUtils.isEmpty(minCode);
}
}
@@ -558,7 +561,7 @@ public class ApkLiteParseUtils {
targetVer = Integer.parseInt(targetSdkVersionString);
} catch (NumberFormatException ignored) {
targetCode = targetSdkVersionString;
- if (minCode == null) {
+ if (!minAssigned) {
minCode = targetCode;
}
}
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 439c6396f1d0..608e34bd07b4 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -420,7 +420,10 @@ public class CompatibilityInfo implements Parcelable {
* Translate a Rect in screen coordinates into the app window's coordinates.
*/
@UnsupportedAppUsage
- public void translateRectInScreenToAppWindow(Rect rect) {
+ public void translateRectInScreenToAppWindow(@Nullable Rect rect) {
+ if (rect == null) {
+ return;
+ }
rect.scale(applicationInvertedScale);
}
diff --git a/core/java/android/debug/AdbManagerInternal.java b/core/java/android/debug/AdbManagerInternal.java
index d730129507d7..e448706fabfe 100644
--- a/core/java/android/debug/AdbManagerInternal.java
+++ b/core/java/android/debug/AdbManagerInternal.java
@@ -55,6 +55,12 @@ public abstract class AdbManagerInternal {
public abstract File getAdbTempKeysFile();
/**
+ * Notify the AdbManager that the key files have changed and any in-memory state should be
+ * reloaded.
+ */
+ public abstract void notifyKeyFilesUpdated();
+
+ /**
* Starts adbd for a transport.
*/
public abstract void startAdbdForTransport(byte transportType);
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index b505395a091a..8bc11cbc61de 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1448,5 +1448,15 @@ public final class DisplayManager {
* @hide
*/
String KEY_HIGH_REFRESH_RATE_BLACKLIST = "high_refresh_rate_blacklist";
+
+ /**
+ * Key for the brightness throttling data as a String formatted:
+ * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>]
+ * Where the latter part is repeated for each throttling level, and the entirety is repeated
+ * for each display, separated by a semicolon.
+ * For example:
+ * 123,1,critical,0.8;456,2,moderate,0.9,critical,0.7
+ */
+ String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";
}
}
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index 3a042a5dee4d..e8e4fc988937 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -26,7 +26,6 @@ import android.os.Parcelable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -173,38 +172,63 @@ public final class ProgramList implements AutoCloseable {
}
}
- void apply(@NonNull Chunk chunk) {
+ void apply(Chunk chunk) {
+ List<ProgramSelector.Identifier> removedList = new ArrayList<>();
+ List<ProgramSelector.Identifier> changedList = new ArrayList<>();
+ List<ProgramList.ListCallback> listCallbacksCopied;
+ List<OnCompleteListener> onCompleteListenersCopied = new ArrayList<>();
synchronized (mLock) {
if (mIsClosed) return;
mIsComplete = false;
+ listCallbacksCopied = new ArrayList<>(mListCallbacks);
if (chunk.isPurge()) {
- new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id));
+ for (ProgramSelector.Identifier id : mPrograms.keySet()) {
+ removeLocked(id, removedList);
+ }
}
- chunk.getRemoved().stream().forEach(id -> removeLocked(id));
- chunk.getModified().stream().forEach(info -> putLocked(info));
+ chunk.getRemoved().stream().forEach(id -> removeLocked(id, removedList));
+ chunk.getModified().stream().forEach(info -> putLocked(info, changedList));
if (chunk.isComplete()) {
mIsComplete = true;
- mOnCompleteListeners.forEach(cb -> cb.onComplete());
+ onCompleteListenersCopied = new ArrayList<>(mOnCompleteListeners);
+ }
+ }
+
+ for (int i = 0; i < removedList.size(); i++) {
+ for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
+ listCallbacksCopied.get(cbIndex).onItemRemoved(removedList.get(i));
+ }
+ }
+ for (int i = 0; i < changedList.size(); i++) {
+ for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
+ listCallbacksCopied.get(cbIndex).onItemChanged(changedList.get(i));
+ }
+ }
+ if (chunk.isComplete()) {
+ for (int cbIndex = 0; cbIndex < onCompleteListenersCopied.size(); cbIndex++) {
+ onCompleteListenersCopied.get(cbIndex).onComplete();
}
}
}
- private void putLocked(@NonNull RadioManager.ProgramInfo value) {
+ private void putLocked(RadioManager.ProgramInfo value,
+ List<ProgramSelector.Identifier> changedIdentifierList) {
ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
mPrograms.put(Objects.requireNonNull(key), value);
ProgramSelector.Identifier sel = value.getSelector().getPrimaryId();
- mListCallbacks.forEach(cb -> cb.onItemChanged(sel));
+ changedIdentifierList.add(sel);
}
- private void removeLocked(@NonNull ProgramSelector.Identifier key) {
+ private void removeLocked(ProgramSelector.Identifier key,
+ List<ProgramSelector.Identifier> removedIdentifierList) {
RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
if (removed == null) return;
ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId();
- mListCallbacks.forEach(cb -> cb.onItemRemoved(sel));
+ removedIdentifierList.add(sel);
}
/**
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 334a659d7f23..8e67705c5cf0 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -135,6 +135,7 @@ import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
+import android.window.CompatOnBackInvokedCallback;
import android.window.ImeOnBackInvokedDispatcher;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
@@ -350,7 +351,7 @@ public class InputMethodService extends AbstractInputMethodService {
private RingBuffer<MotionEvent> mPendingEvents;
private ImeOnBackInvokedDispatcher mImeDispatcher;
private Boolean mBackCallbackRegistered = false;
- private final OnBackInvokedCallback mCompatBackCallback = this::compatHandleBack;
+ private final CompatOnBackInvokedCallback mCompatBackCallback = this::compatHandleBack;
/**
* Returns whether {@link InputMethodService} is responsible for rendering the back button and
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index 22ddbccbaaae..c70f1f07e739 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -59,6 +59,18 @@
},
{
"file_patterns": [
+ "Parcel\\.java",
+ "[^/]*Bundle[^/]*\\.java"
+ ],
+ "name": "FrameworksMockingCoreTests",
+ "options": [
+ { "include-filter": "android.os.BundleRecyclingTest"},
+ { "exclude-annotation": "androidx.test.filters.FlakyTest" },
+ { "exclude-annotation": "org.junit.Ignore" }
+ ]
+ },
+ {
+ "file_patterns": [
"BatteryUsageStats[^/]*\\.java",
"PowerComponents\\.java",
"[^/]*BatteryConsumer[^/]*\\.java"
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 9679a6ab3acb..1df7dbc0cd0b 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -24,7 +24,6 @@ import static android.graphics.Matrix.MSKEW_X;
import static android.graphics.Matrix.MSKEW_Y;
import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
-import static android.view.ViewRootImpl.LOCAL_LAYOUT;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import android.animation.AnimationHandler;
@@ -41,7 +40,6 @@ import android.app.Service;
import android.app.WallpaperColors;
import android.app.WallpaperInfo;
import android.app.WallpaperManager;
-import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -260,8 +258,6 @@ public abstract class WallpaperService extends Service {
private final Point mLastSurfaceSize = new Point();
private final Matrix mTmpMatrix = new Matrix();
private final float[] mTmpValues = new float[9];
- private final WindowLayout mWindowLayout = new WindowLayout();
- private final Rect mTempRect = new Rect();
final WindowManager.LayoutParams mLayout
= new WindowManager.LayoutParams();
@@ -1100,8 +1096,7 @@ public abstract class WallpaperService extends Service {
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
final Configuration config = mMergedConfiguration.getMergedConfiguration();
- final WindowConfiguration winConfig = config.windowConfiguration;
- final Rect maxBounds = winConfig.getMaxBounds();
+ final Rect maxBounds = config.windowConfiguration.getMaxBounds();
if (myWidth == ViewGroup.LayoutParams.MATCH_PARENT
&& myHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
mLayout.width = myWidth;
@@ -1139,7 +1134,7 @@ public abstract class WallpaperService extends Service {
if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE,
mDisplay.getDisplayId(), mRequestedVisibilities, inputChannel,
- mInsetsState, mTempControls) < 0) {
+ mInsetsState, mTempControls, new Rect()) < 0) {
Log.w(TAG, "Failed to add window while updating wallpaper surface.");
return;
}
@@ -1158,29 +1153,9 @@ public abstract class WallpaperService extends Service {
} else {
mLayout.surfaceInsets.set(0, 0, 0, 0);
}
-
- int relayoutResult = 0;
- if (LOCAL_LAYOUT) {
- if (!mSurfaceControl.isValid()) {
- relayoutResult = mSession.updateVisibility(mWindow, mLayout,
- View.VISIBLE, mMergedConfiguration, mSurfaceControl,
- mInsetsState, mTempControls);
- }
-
- final Rect displayCutoutSafe = mTempRect;
- mInsetsState.getDisplayCutoutSafe(displayCutoutSafe);
- mWindowLayout.computeFrames(mLayout, mInsetsState, displayCutoutSafe,
- winConfig.getBounds(), winConfig.getWindowingMode(), mWidth,
- mHeight, mRequestedVisibilities, null /* attachedWindowFrame */,
- 1f /* compatScale */, mWinFrames);
-
- mSession.updateLayout(mWindow, mLayout, 0 /* flags */, mWinFrames, mWidth,
- mHeight);
- } else {
- relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
- View.VISIBLE, 0, mWinFrames, mMergedConfiguration,
- mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle);
- }
+ final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
+ View.VISIBLE, 0, mWinFrames, mMergedConfiguration, mSurfaceControl,
+ mInsetsState, mTempControls, mSyncSeqIdBundle);
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
@@ -1229,7 +1204,7 @@ public abstract class WallpaperService extends Service {
null /* ignoringVisibilityState */, config.isScreenRound(),
false /* alwaysConsumeSystemBars */, mLayout.softInputMode,
mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, mLayout.type,
- winConfig.getWindowingMode(), null /* typeSideMap */);
+ config.windowConfiguration.getWindowingMode(), null /* typeSideMap */);
if (!fixedSize) {
final Rect padding = mIWallpaperEngine.mDisplayPadding;
diff --git a/core/java/android/view/IRemoteAnimationRunner.aidl b/core/java/android/view/IRemoteAnimationRunner.aidl
index 1f64fb8ca2ec..1981c9d66c8b 100644
--- a/core/java/android/view/IRemoteAnimationRunner.aidl
+++ b/core/java/android/view/IRemoteAnimationRunner.aidl
@@ -46,5 +46,5 @@ oneway interface IRemoteAnimationRunner {
* won't have any effect anymore.
*/
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
- void onAnimationCancelled();
+ void onAnimationCancelled(boolean isKeyguardOccluded);
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 649accd1126d..ef57b1ddd4b3 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -50,13 +50,15 @@ interface IWindowSession {
int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in InsetsVisibilities requestedVisibilities,
out InputChannel outInputChannel, out InsetsState insetsState,
- out InsetsSourceControl[] activeControls);
+ out InsetsSourceControl[] activeControls, out Rect attachedFrame);
int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in int userId,
in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel,
- out InsetsState insetsState, out InsetsSourceControl[] activeControls);
+ out InsetsState insetsState, out InsetsSourceControl[] activeControls,
+ out Rect attachedFrame);
int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
- in int viewVisibility, in int layerStackId, out InsetsState insetsState);
+ in int viewVisibility, in int layerStackId, out InsetsState insetsState,
+ out Rect attachedFrame);
@UnsupportedAppUsage
void remove(IWindow window);
@@ -107,41 +109,6 @@ interface IWindowSession {
out InsetsState insetsState, out InsetsSourceControl[] activeControls,
out Bundle bundle);
- /**
- * Changes the view visibility and the attributes of a window. This should only be called when
- * the visibility of the root view is changed. This returns a valid surface if the root view is
- * visible. This also returns the latest information for the caller to compute its window frame.
- *
- * @param window The window being updated.
- * @param attrs If non-null, new attributes to apply to the window.
- * @param viewVisibility Window root view's visibility.
- * @param outMergedConfiguration New config container that holds global, override and merged
- * config for window, if it is now becoming visible and the merged configuration has changed
- * since it was last displayed.
- * @param outSurfaceControl Object in which is placed the new display surface.
- * @param outInsetsState The current insets state in the system.
- * @param outActiveControls The insets source controls for the caller to override the insets
- * state in the system.
- *
- * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
- */
- int updateVisibility(IWindow window, in WindowManager.LayoutParams attrs, int viewVisibility,
- out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
- out InsetsState outInsetsState, out InsetsSourceControl[] outActiveControls);
-
- /**
- * Reports the layout results and the attributes of a window to the server.
- *
- * @param window The window being reported.
- * @param attrs If non-null, new attributes to apply to the window.
- * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}.
- * @param clientFrames the window frames computed by the client.
- * @param requestedWidth The width the window wants to be.
- * @param requestedHeight The height the window wants to be.
- */
- oneway void updateLayout(IWindow window, in WindowManager.LayoutParams attrs, int flags,
- in ClientWindowFrames clientFrames, int requestedWidth, int requestedHeight);
-
/*
* Notify the window manager that an application is relaunching and
* windows should be prepared for replacement.
@@ -384,4 +351,9 @@ interface IWindowSession {
* Clears a touchable region set by {@link #setInsets}.
*/
void clearTouchableRegion(IWindow window);
+
+ /**
+ * Returns whether this window needs to cancel draw and retry later.
+ */
+ boolean cancelDraw(IWindow window);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 294d7d6e2bc6..9a3957c1bcae 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -60,13 +60,11 @@ import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
-import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
-import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED;
@@ -77,13 +75,12 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
@@ -196,6 +193,7 @@ import android.view.contentcapture.MainContentCaptureSession;
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
import android.window.ClientWindowFrames;
+import android.window.CompatOnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
import android.window.SurfaceSyncer;
@@ -339,13 +337,12 @@ public final class ViewRootImpl implements ViewParent,
/**
* The top level {@link OnBackInvokedDispatcher}.
*/
- private final WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher =
- new WindowOnBackInvokedDispatcher();
+ private final WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
/**
* Compatibility {@link OnBackInvokedCallback} that dispatches KEYCODE_BACK events
* to view root for apps using legacy back behavior.
*/
- private OnBackInvokedCallback mCompatOnBackInvokedCallback;
+ private CompatOnBackInvokedCallback mCompatOnBackInvokedCallback;
/**
* Callback for notifying about global configuration changes.
@@ -566,8 +563,6 @@ public final class ViewRootImpl implements ViewParent,
private final WindowLayout mWindowLayout;
- private ViewRootImpl mParentViewRoot;
-
// This is used to reduce the race between window focus changes being dispatched from
// the window manager and input events coming through the input system.
@GuardedBy("this")
@@ -601,6 +596,14 @@ public final class ViewRootImpl implements ViewParent,
*/
private boolean mSyncBuffer = false;
+ /**
+ * Flag to determine whether the client needs to check with WMS if it can draw. WMS will notify
+ * the client that it can't draw if we're still in the middle of a sync set that includes this
+ * window. Once the sync is complete, the window can resume drawing. This is to ensure we don't
+ * deadlock the client by trying to request draws when there may not be any buffers available.
+ */
+ private boolean mCheckIfCanDraw = false;
+
int mSyncSeqId = 0;
int mLastSyncSeqId = 0;
@@ -962,6 +965,8 @@ public final class ViewRootImpl implements ViewParent,
mFastScrollSoundEffectsEnabled = audioManager.areNavigationRepeatSoundEffectsEnabled();
mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS;
+ mOnBackInvokedDispatcher = new WindowOnBackInvokedDispatcher(
+ context.getApplicationInfo().isOnBackInvokedCallbackEnabled());
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -1185,7 +1190,6 @@ public final class ViewRootImpl implements ViewParent,
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
- mParentViewRoot = panelParentView.getViewRootImpl();
}
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
@@ -1216,14 +1220,21 @@ public final class ViewRootImpl implements ViewParent,
collectViewAttributes();
adjustLayoutParamsForCompatibility(mWindowAttributes);
controlInsetsForCompatibility(mWindowAttributes);
+
+ Rect attachedFrame = new Rect();
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
- mTempControls);
+ mTempControls, attachedFrame);
+ if (!attachedFrame.isValid()) {
+ attachedFrame = null;
+ }
if (mTranslator != null) {
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+ mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
+ mTmpFrames.attachedFrame = attachedFrame;
} catch (RemoteException e) {
mAdded = false;
mView = null;
@@ -1250,8 +1261,8 @@ public final class ViewRootImpl implements ViewParent,
mWindowLayout.computeFrames(mWindowAttributes, state,
displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
- mInsetsController.getRequestedVisibilities(),
- getAttachedWindowFrame(), 1f /* compactScale */, mTmpFrames);
+ mInsetsController.getRequestedVisibilities(), 1f /* compactScale */,
+ mTmpFrames);
setFrame(mTmpFrames.frame);
registerBackCallbackOnWindow();
if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
@@ -1373,14 +1384,6 @@ public final class ViewRootImpl implements ViewParent,
}
}
- private Rect getAttachedWindowFrame() {
- final int type = mWindowAttributes.type;
- final boolean layoutAttached = (mParentViewRoot != null
- && type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW
- && type != TYPE_APPLICATION_ATTACHED_DIALOG);
- return layoutAttached ? mParentViewRoot.mWinFrame : null;
- }
-
/**
* Register any kind of listeners if setView was success.
*/
@@ -1738,16 +1741,20 @@ public final class ViewRootImpl implements ViewParent,
final Rect frame = frames.frame;
final Rect displayFrame = frames.displayFrame;
+ final Rect attachedFrame = frames.attachedFrame;
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(frame);
mTranslator.translateRectInScreenToAppWindow(displayFrame);
+ mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
final boolean frameChanged = !mWinFrame.equals(frame);
final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration);
+ final boolean attachedFrameChanged = LOCAL_LAYOUT
+ && !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
final boolean displayChanged = mDisplay.getDisplayId() != displayId;
final boolean resizeModeChanged = mResizeMode != resizeMode;
- if (msg == MSG_RESIZED && !frameChanged && !configChanged && !displayChanged
- && !resizeModeChanged && !forceNextWindowRelayout) {
+ if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
+ && !displayChanged && !resizeModeChanged && !forceNextWindowRelayout) {
return;
}
@@ -1765,6 +1772,9 @@ public final class ViewRootImpl implements ViewParent,
setFrame(frame);
mTmpFrames.displayFrame.set(displayFrame);
+ if (mTmpFrames.attachedFrame != null && attachedFrame != null) {
+ mTmpFrames.attachedFrame.set(attachedFrame);
+ }
if (mDragResizing && mUseMTRenderer) {
boolean fullscreen = frame.equals(mPendingBackDropFrame);
@@ -2698,6 +2708,9 @@ public final class ViewRootImpl implements ViewParent,
mIsInTraversal = true;
mWillDrawSoon = true;
+ boolean cancelDraw = false;
+ boolean isSyncRequest = false;
+
boolean windowSizeMayChange = false;
WindowManager.LayoutParams lp = mWindowAttributes;
@@ -2803,10 +2816,6 @@ public final class ViewRootImpl implements ViewParent,
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
- if (mApplyInsetsRequested) {
- dispatchApplyInsets(host);
- }
-
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
@@ -2870,6 +2879,18 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ if (mApplyInsetsRequested) {
+ dispatchApplyInsets(host);
+ if (mLayoutRequested) {
+ // Short-circuit catching a new layout request here, so
+ // we don't need to go through two layout passes when things
+ // change due to fitting system windows, which can happen a lot.
+ windowSizeMayChange |= measureHierarchy(host, lp,
+ mView.getContext().getResources(),
+ desiredWindowWidth, desiredWindowHeight);
+ }
+ }
+
if (layoutRequested) {
// Clear this now, so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
@@ -2967,6 +2988,8 @@ public final class ViewRootImpl implements ViewParent,
mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
}
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
+ cancelDraw = (relayoutResult & RELAYOUT_RES_CANCEL_AND_REDRAW)
+ == RELAYOUT_RES_CANCEL_AND_REDRAW;
final boolean dragResizing = mPendingDragResizing;
if (mSyncSeqId > mLastSyncSeqId) {
mLastSyncSeqId = mSyncSeqId;
@@ -2975,6 +2998,7 @@ public final class ViewRootImpl implements ViewParent,
}
reportNextDraw();
mSyncBuffer = true;
+ isSyncRequest = true;
}
final boolean surfaceControlChanged =
@@ -3263,6 +3287,19 @@ public final class ViewRootImpl implements ViewParent,
}
}
} else {
+ // If a relayout isn't going to happen, we still need to check if this window can draw
+ // when mCheckIfCanDraw is set. This is because it means we had a sync in the past, but
+ // have not been told by WMS that the sync is complete and that we can continue to draw
+ if (mCheckIfCanDraw) {
+ try {
+ cancelDraw = mWindowSession.cancelDraw(mWindow);
+ if (DEBUG_BLAST) {
+ Log.d(mTag, "cancelDraw returned " + cancelDraw);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
@@ -3481,7 +3518,9 @@ public final class ViewRootImpl implements ViewParent,
reportNextDraw();
}
- boolean cancelAndRedraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
+ mCheckIfCanDraw = isSyncRequest || cancelDraw;
+
+ boolean cancelAndRedraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || cancelDraw;
if (!cancelAndRedraw) {
createSyncIfNeeded();
}
@@ -8006,69 +8045,20 @@ public final class ViewRootImpl implements ViewParent,
final int requestedWidth = (int) (mView.getMeasuredWidth() * appScale + 0.5f);
final int requestedHeight = (int) (mView.getMeasuredHeight() * appScale + 0.5f);
- int relayoutResult = 0;
- WindowConfiguration winConfig = getConfiguration().windowConfiguration;
- if (LOCAL_LAYOUT) {
- if (mFirst || viewVisibility != mViewVisibility) {
- relayoutResult = mWindowSession.updateVisibility(mWindow, params, viewVisibility,
- mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mTempControls);
- if (mTranslator != null) {
- mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
- mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
- }
- mInsetsController.onStateChanged(mTempInsets);
- mInsetsController.onControlsChanged(mTempControls);
-
- mPendingAlwaysConsumeSystemBars =
- (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
- }
- final InsetsState state = mInsetsController.getState();
- final Rect displayCutoutSafe = mTempRect;
- state.getDisplayCutoutSafe(displayCutoutSafe);
- if (mWindowAttributes.type == TYPE_APPLICATION_STARTING) {
- // TODO(b/210378379): Remove the special logic.
- // Letting starting window use the window bounds from the pending config is for the
- // fixed rotation, because the config is not overridden before the starting window
- // is created.
- winConfig = mPendingMergedConfiguration.getMergedConfiguration()
- .windowConfiguration;
- }
- mWindowLayout.computeFrames(mWindowAttributes, state, displayCutoutSafe,
- winConfig.getBounds(), winConfig.getWindowingMode(), requestedWidth,
- requestedHeight, mInsetsController.getRequestedVisibilities(),
- getAttachedWindowFrame(), 1f /* compatScale */, mTmpFrames);
-
- mWindowSession.updateLayout(mWindow, params,
- insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mTmpFrames,
- requestedWidth, requestedHeight);
-
- } else {
- relayoutResult = mWindowSession.relayout(mWindow, params,
- requestedWidth, requestedHeight, viewVisibility,
- insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
- mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
- mTempControls, mRelayoutBundle);
- final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
- if (maybeSyncSeqId > 0) {
- mSyncSeqId = maybeSyncSeqId;
- }
-
- if (mTranslator != null) {
- mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
- mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
- mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
- mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
- }
- mInsetsController.onStateChanged(mTempInsets);
- mInsetsController.onControlsChanged(mTempControls);
-
- mPendingAlwaysConsumeSystemBars =
- (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
+ int relayoutResult = mWindowSession.relayout(mWindow, params,
+ requestedWidth, requestedHeight, viewVisibility,
+ insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
+ mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
+ mTempControls, mRelayoutBundle);
+ final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
+ if (maybeSyncSeqId > 0) {
+ mSyncSeqId = maybeSyncSeqId;
}
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
+ final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
requestedHeight, mTmpFrames.frame, mPendingDragResizing, mSurfaceSize);
@@ -8117,10 +8107,23 @@ public final class ViewRootImpl implements ViewParent,
destroySurface();
}
+ mPendingAlwaysConsumeSystemBars =
+ (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
+
if (restore) {
params.restore();
}
+
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
+ mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
+ mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
+ mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
+ mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+ }
setFrame(mTmpFrames.frame);
+ mInsetsController.onStateChanged(mTempInsets);
+ mInsetsController.onControlsChanged(mTempControls);
return relayoutResult;
}
@@ -10854,13 +10857,6 @@ public final class ViewRootImpl implements ViewParent,
OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatOnBackInvokedCallback);
}
- private void unregisterCompatOnBackInvokedCallback() {
- if (mCompatOnBackInvokedCallback != null) {
- mOnBackInvokedDispatcher.unregisterOnBackInvokedCallback(mCompatOnBackInvokedCallback);
- mCompatOnBackInvokedCallback = null;
- }
- }
-
@Override
public void setTouchableRegion(Region r) {
if (r != null) {
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index c320b262ebd7..9b6b2b906e8e 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -66,14 +66,15 @@ public class WindowLayout {
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities,
- Rect attachedWindowFrame, float compatScale, ClientWindowFrames outFrames) {
+ float compatScale, ClientWindowFrames frames) {
final int type = attrs.type;
final int fl = attrs.flags;
final int pfl = attrs.privateFlags;
final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
- final Rect outDisplayFrame = outFrames.displayFrame;
- final Rect outParentFrame = outFrames.parentFrame;
- final Rect outFrame = outFrames.frame;
+ final Rect attachedWindowFrame = frames.attachedFrame;
+ final Rect outDisplayFrame = frames.displayFrame;
+ final Rect outParentFrame = frames.parentFrame;
+ final Rect outFrame = frames.frame;
// Compute bounds restricted by insets
final Insets insets = state.calculateInsets(windowBounds, attrs.getFitInsetsTypes(),
@@ -104,7 +105,7 @@ public class WindowLayout {
final DisplayCutout cutout = state.getDisplayCutout();
final Rect displayCutoutSafeExceptMaybeBars = mTempDisplayCutoutSafeExceptMaybeBarsRect;
displayCutoutSafeExceptMaybeBars.set(displayCutoutSafe);
- outFrames.isParentFrameClippedByDisplayCutout = false;
+ frames.isParentFrameClippedByDisplayCutout = false;
if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS && !cutout.isEmpty()) {
// Ensure that windows with a non-ALWAYS display cutout mode are laid out in
// the cutout safe zone.
@@ -167,7 +168,7 @@ public class WindowLayout {
if (!attachedInParent && !floatingInScreenWindow) {
mTempRect.set(outParentFrame);
outParentFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
- outFrames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame);
+ frames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame);
}
outDisplayFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
}
@@ -287,12 +288,9 @@ public class WindowLayout {
}
}
- if (DEBUG) Log.d(TAG, "computeWindowFrames " + attrs.getTitle()
- + " outFrames=" + outFrames
+ if (DEBUG) Log.d(TAG, "computeFrames " + attrs.getTitle()
+ + " frames=" + frames
+ " windowBounds=" + windowBounds.toShortString()
- + " attachedWindowFrame=" + (attachedWindowFrame != null
- ? attachedWindowFrame.toShortString()
- : "null")
+ " requestedWidth=" + requestedWidth
+ " requestedHeight=" + requestedHeight
+ " compatScale=" + compatScale
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index b818a41293ca..dd990e75e3cc 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2591,6 +2591,7 @@ public interface WindowManager extends ViewManager {
PRIVATE_FLAG_SYSTEM_ERROR,
PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
+ PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
@@ -2652,6 +2653,10 @@ public interface WindowManager extends ViewManager {
equals = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
name = "FORCE_STATUS_BAR_VISIBLE"),
@ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
+ equals = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
+ name = "LAYOUT_SIZE_EXTENDED_BY_CUTOUT"),
+ @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
name = "FORCE_DECOR_VIEW_VISIBILITY"),
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 85a5762f7f3d..25445abefca2 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -83,6 +83,11 @@ public final class WindowManagerGlobal {
public static final int RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS = 1 << 3;
/**
+ * The window manager has told the window it cannot draw this frame and should retry again.
+ */
+ public static final int RELAYOUT_RES_CANCEL_AND_REDRAW = 1 << 4;
+
+ /**
* Flag for relayout: the client will be later giving
* internal insets; as a result, the window will not impact other window
* layouts until the insets are given.
diff --git a/core/java/android/view/WindowlessWindowLayout.java b/core/java/android/view/WindowlessWindowLayout.java
index 7cc50c579d0d..5bec5b6b6722 100644
--- a/core/java/android/view/WindowlessWindowLayout.java
+++ b/core/java/android/view/WindowlessWindowLayout.java
@@ -30,10 +30,10 @@ public class WindowlessWindowLayout extends WindowLayout {
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities,
- Rect attachedWindowFrame, float compatScale, ClientWindowFrames outFrames) {
- outFrames.frame.set(0, 0, attrs.width, attrs.height);
- outFrames.displayFrame.set(outFrames.frame);
- outFrames.parentFrame.set(outFrames.frame);
+ float compatScale, ClientWindowFrames frames) {
+ frames.frame.set(0, 0, attrs.width, attrs.height);
+ frames.displayFrame.set(frames.frame);
+ frames.parentFrame.set(frames.frame);
}
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index a212348a2315..94da2741f71a 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -149,7 +149,7 @@ public class WindowlessWindowManager implements IWindowSession {
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
.setFormat(attrs.format)
.setBLASTLayer()
@@ -181,6 +181,7 @@ public class WindowlessWindowManager implements IWindowSession {
synchronized (this) {
mStateForWindow.put(window.asBinder(), state);
}
+ outAttachedFrame.set(0, 0, -1, -1);
final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
WindowManagerGlobal.ADD_FLAG_USE_BLAST;
@@ -196,15 +197,15 @@ public class WindowlessWindowManager implements IWindowSession {
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibilities,
- outInputChannel, outInsetsState, outActiveControls);
+ outInputChannel, outInsetsState, outActiveControls, outAttachedFrame);
}
@Override
public int addToDisplayWithoutInputChannel(android.view.IWindow window,
android.view.WindowManager.LayoutParams attrs, int viewVisibility, int layerStackId,
- android.view.InsetsState insetsState) {
+ android.view.InsetsState insetsState, Rect outAttachedFrame) {
return 0;
}
@@ -337,21 +338,6 @@ public class WindowlessWindowManager implements IWindowSession {
}
@Override
- public int updateVisibility(IWindow window, WindowManager.LayoutParams inAttrs,
- int viewVisibility, MergedConfiguration outMergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
- // TODO(b/161810301): Finish the implementation.
- return 0;
- }
-
- @Override
- public void updateLayout(IWindow window, WindowManager.LayoutParams inAttrs, int flags,
- ClientWindowFrames clientWindowFrames, int requestedWidth, int requestedHeight) {
- // TODO(b/161810301): Finish the implementation.
- }
-
- @Override
public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) {
}
@@ -552,4 +538,9 @@ public class WindowlessWindowManager implements IWindowSession {
}
}
}
+
+ @Override
+ public boolean cancelDraw(IWindow window) {
+ return false;
+ }
}
diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java
index 51f3fe2551ad..929e81ed9044 100644
--- a/core/java/android/window/ClientWindowFrames.java
+++ b/core/java/android/window/ClientWindowFrames.java
@@ -17,6 +17,7 @@
package android.window;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
@@ -40,6 +41,12 @@ public class ClientWindowFrames implements Parcelable {
*/
public final @NonNull Rect parentFrame = new Rect();
+ /**
+ * The frame this window attaches to. If this is not null, this is the frame of the parent
+ * window.
+ */
+ public @Nullable Rect attachedFrame;
+
public boolean isParentFrameClippedByDisplayCutout;
public ClientWindowFrames() {
@@ -49,6 +56,9 @@ public class ClientWindowFrames implements Parcelable {
frame.set(other.frame);
displayFrame.set(other.displayFrame);
parentFrame.set(other.parentFrame);
+ if (other.attachedFrame != null) {
+ attachedFrame = new Rect(other.attachedFrame);
+ }
isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout;
}
@@ -61,6 +71,7 @@ public class ClientWindowFrames implements Parcelable {
frame.readFromParcel(in);
displayFrame.readFromParcel(in);
parentFrame.readFromParcel(in);
+ attachedFrame = in.readTypedObject(Rect.CREATOR);
isParentFrameClippedByDisplayCutout = in.readBoolean();
}
@@ -69,6 +80,7 @@ public class ClientWindowFrames implements Parcelable {
frame.writeToParcel(dest, flags);
displayFrame.writeToParcel(dest, flags);
parentFrame.writeToParcel(dest, flags);
+ dest.writeTypedObject(attachedFrame, flags);
dest.writeBoolean(isParentFrameClippedByDisplayCutout);
}
@@ -78,6 +90,7 @@ public class ClientWindowFrames implements Parcelable {
return "ClientWindowFrames{frame=" + frame.toShortString(sb)
+ " display=" + displayFrame.toShortString(sb)
+ " parentFrame=" + parentFrame.toShortString(sb)
+ + (attachedFrame != null ? " attachedFrame=" + attachedFrame.toShortString() : "")
+ " parentClippedByDisplayCutout=" + isParentFrameClippedByDisplayCutout + "}";
}
diff --git a/core/java/android/window/CompatOnBackInvokedCallback.java b/core/java/android/window/CompatOnBackInvokedCallback.java
new file mode 100644
index 000000000000..81d7291d9f88
--- /dev/null
+++ b/core/java/android/window/CompatOnBackInvokedCallback.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * Marker interface for {@link OnBackInvokedCallback} used for backward compatibility between the
+ * new system back and the old back event dispatching. Callbacks implementing this interface are
+ * allowed to be registered even if <code>enableOnbackInvoked</code> is set to false in the
+ * application manifest.
+ * @hide
+ */
+public interface CompatOnBackInvokedCallback extends OnBackInvokedCallback{
+
+ @Override
+ void onBackInvoked();
+}
diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
index 10d43e89a09d..8ad109317f4b 100644
--- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Log;
import android.util.Pair;
+import android.window.WindowOnBackInvokedDispatcher.Checker;
import java.util.ArrayList;
import java.util.List;
@@ -50,6 +51,11 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
private final Object mLock = new Object();
private OnBackInvokedDispatcher mActualDispatcher = null;
private ImeOnBackInvokedDispatcher mImeDispatcher;
+ private final Checker mChecker;
+
+ public ProxyOnBackInvokedDispatcher(boolean applicationCallBackEnabled) {
+ mChecker = new Checker(applicationCallBackEnabled);
+ }
@Override
public void registerOnBackInvokedCallback(
@@ -58,11 +64,9 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
Log.v(TAG, String.format("Proxy register %s. mActualDispatcher=%s", callback,
mActualDispatcher));
}
- if (priority < 0) {
- throw new IllegalArgumentException("Application registered OnBackInvokedCallback "
- + "cannot have negative priority. Priority: " + priority);
+ if (mChecker.checkApplicationCallbackRegistration(priority, callback)) {
+ registerOnBackInvokedCallbackUnchecked(callback, priority);
}
- registerOnBackInvokedCallbackUnchecked(callback, priority);
}
@Override
diff --git a/core/java/android/window/SizeConfigurationBuckets.java b/core/java/android/window/SizeConfigurationBuckets.java
index f474f0a76cc6..998bec0892ae 100644
--- a/core/java/android/window/SizeConfigurationBuckets.java
+++ b/core/java/android/window/SizeConfigurationBuckets.java
@@ -104,24 +104,15 @@ public final class SizeConfigurationBuckets implements Parcelable {
/**
* Get the changes between two configurations but don't count changes in sizes if they don't
* cross boundaries that are important to the app.
- *
- * This is a static helper to deal with null `buckets`. When no buckets have been specified,
- * this actually filters out all 3 size-configs. This is legacy behavior.
*/
public static int filterDiff(int diff, @NonNull Configuration oldConfig,
@NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets buckets) {
- final boolean nonSizeLayoutFieldsUnchanged =
- areNonSizeLayoutFieldsUnchanged(oldConfig.screenLayout, newConfig.screenLayout);
if (buckets == null) {
- // Only unflip CONFIG_SCREEN_LAYOUT if non-size-related attributes of screen layout do
- // not change.
- if (nonSizeLayoutFieldsUnchanged) {
- return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
- | CONFIG_SCREEN_LAYOUT);
- } else {
- return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE);
- }
+ return diff;
}
+
+ final boolean nonSizeLayoutFieldsUnchanged =
+ areNonSizeLayoutFieldsUnchanged(oldConfig.screenLayout, newConfig.screenLayout);
if ((diff & CONFIG_SCREEN_SIZE) != 0) {
final boolean crosses = buckets.crossesHorizontalSizeThreshold(oldConfig.screenWidthDp,
newConfig.screenWidthDp)
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 1d0bc5a6f0ba..d147524d3b3d 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -30,6 +30,7 @@ import android.view.IWindowSession;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Objects;
import java.util.TreeMap;
/**
@@ -62,6 +63,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
/** Holds all callbacks by priorities. */
private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
mOnBackInvokedCallbacks = new TreeMap<>();
+ private final Checker mChecker;
+
+ public WindowOnBackInvokedDispatcher(boolean applicationCallBackEnabled) {
+ mChecker = new Checker(applicationCallBackEnabled);
+ }
/**
* Sends the pending top callback (if one exists) to WM when the view root
@@ -86,14 +92,16 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override
public void registerOnBackInvokedCallback(
@Priority int priority, @NonNull OnBackInvokedCallback callback) {
- if (priority < 0) {
- throw new IllegalArgumentException("Application registered OnBackInvokedCallback "
- + "cannot have negative priority. Priority: " + priority);
+ if (mChecker.checkApplicationCallbackRegistration(priority, callback)) {
+ registerOnBackInvokedCallbackUnchecked(callback, priority);
}
- registerOnBackInvokedCallbackUnchecked(callback, priority);
}
- private void registerOnBackInvokedCallbackUnchecked(
+ /**
+ * Register a callback bypassing platform checks. This is used to register compatibility
+ * callbacks.
+ */
+ public void registerOnBackInvokedCallbackUnchecked(
@NonNull OnBackInvokedCallback callback, @Priority int priority) {
if (mImeDispatcher != null) {
mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
@@ -203,6 +211,14 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
return null;
}
+ /**
+ * Returns the checker used to check whether a callback can be registered
+ */
+ @NonNull
+ public Checker getChecker() {
+ return mChecker;
+ }
+
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference<OnBackInvokedCallback> mCallback;
@@ -289,4 +305,41 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
mImeDispatcher = imeDispatcher;
}
+
+
+ /**
+ * Class used to check whether a callback can be registered or not. This is meant to be
+ * shared with {@link ProxyOnBackInvokedDispatcher} which needs to do the same checks.
+ */
+ public static class Checker {
+
+ private final boolean mApplicationCallBackEnabled;
+
+ public Checker(boolean applicationCallBackEnabled) {
+ mApplicationCallBackEnabled = applicationCallBackEnabled;
+ }
+
+ /**
+ * Checks whether the given callback can be registered with the given priority.
+ * @return true if the callback can be added.
+ * @throws IllegalArgumentException if the priority is negative.
+ */
+ public boolean checkApplicationCallbackRegistration(int priority,
+ OnBackInvokedCallback callback) {
+ if (!mApplicationCallBackEnabled
+ && !(callback instanceof CompatOnBackInvokedCallback)) {
+ Log.w("OnBackInvokedCallback",
+ "OnBackInvokedCallback is not enabled for the application."
+ + "\nSet 'android:enableOnBackInvokedCallback=\"true\"' in the"
+ + " application manifest.");
+ return false;
+ }
+ if (priority < 0) {
+ throw new IllegalArgumentException("Application registered OnBackInvokedCallback "
+ + "cannot have negative priority. Priority: " + priority);
+ }
+ Objects.requireNonNull(callback);
+ return true;
+ }
+ }
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 52fd7fec932e..22340c6b0c55 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -75,6 +75,8 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
@@ -206,6 +208,8 @@ public class InteractionJankMonitor {
public static final int CUJ_SETTINGS_TOGGLE = 57;
public static final int CUJ_SHADE_DIALOG_OPEN = 58;
public static final int CUJ_USER_DIALOG_OPEN = 59;
+ public static final int CUJ_TASKBAR_EXPAND = 60;
+ public static final int CUJ_TASKBAR_COLLAPSE = 61;
private static final int NO_STATSD_LOGGING = -1;
@@ -274,6 +278,8 @@ public class InteractionJankMonitor {
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE,
};
private static volatile InteractionJankMonitor sInstance;
@@ -354,6 +360,8 @@ public class InteractionJankMonitor {
CUJ_SETTINGS_TOGGLE,
CUJ_SHADE_DIALOG_OPEN,
CUJ_USER_DIALOG_OPEN,
+ CUJ_TASKBAR_EXPAND,
+ CUJ_TASKBAR_COLLAPSE
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -792,6 +800,10 @@ public class InteractionJankMonitor {
return "SHADE_DIALOG_OPEN";
case CUJ_USER_DIALOG_OPEN:
return "USER_DIALOG_OPEN";
+ case CUJ_TASKBAR_EXPAND:
+ return "TASKBAR_EXPAND";
+ case CUJ_TASKBAR_COLLAPSE:
+ return "TASKBAR_COLLAPSE";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index e258fc19a75f..fb38bba8ee16 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -342,8 +342,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
boolean mDecorFitsSystemWindows = true;
- private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher =
- new ProxyOnBackInvokedDispatcher();
+ private final ProxyOnBackInvokedDispatcher mProxyOnBackInvokedDispatcher;
static class WindowManagerHolder {
static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
@@ -358,6 +357,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
mLayoutInflater = LayoutInflater.from(context);
mRenderShadowsInCompositor = Settings.Global.getInt(context.getContentResolver(),
DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0;
+ mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(
+ context.getApplicationInfo().isOnBackInvokedCallbackEnabled());
}
/**
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 0756d68063f5..fd787f6ea470 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -138,7 +138,7 @@
</LinearLayout>
- <ImageView
+ <com.android.internal.widget.CachingIconView
android:id="@+id/right_icon"
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
@@ -150,6 +150,8 @@
android:clipToOutline="true"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
+ android:maxDrawableWidth="@dimen/notification_right_icon_size"
+ android:maxDrawableHeight="@dimen/notification_right_icon_size"
/>
<FrameLayout
diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml
index f163ed5f955a..8b3b795f7473 100644
--- a/core/res/res/layout/notification_template_right_icon.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<ImageView
+<com.android.internal.widget.CachingIconView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/right_icon"
android:layout_width="@dimen/notification_right_icon_size"
@@ -25,4 +25,6 @@
android:clipToOutline="true"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
+ android:maxDrawableWidth="@dimen/notification_right_icon_size"
+ android:maxDrawableHeight="@dimen/notification_right_icon_size"
/>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 2171987c081e..a8c7bf249ada 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2944,7 +2944,7 @@
<!-- System bluetooth stack package name -->
<string name="config_systemBluetoothStack" translatable="false">
- com.android.bluetooth.services
+ com.android.bluetooth
</string>
<!-- Flag indicating that the media framework should not allow changes or mute on any
@@ -5180,11 +5180,11 @@
<!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. -->
<bool name="config_letterboxIsEducationEnabled">false</bool>
- <!-- Default min aspect ratio for unresizable apps which is used when an app doesn't specify
- android:minAspectRatio in accordance with CDD 7.1.1.2 requirement:
- https://source.android.com/compatibility/12/android-12-cdd#7112_screen_aspect_ratio.
- An exception will be thrown if the given aspect ratio < 4:3. -->
- <item name="config_letterboxDefaultMinAspectRatioForUnresizableApps" format="float" type="dimen">1.5</item>
+ <!-- Default min aspect ratio for unresizable apps which are eligible for size compat mode.
+ Values <= 1.0 will be ignored. Activity min/max aspect ratio restrictions will still be
+ espected so this override can control the maximum screen area that can be occupied by
+ the app in the letterbox mode. -->
+ <item name="config_letterboxDefaultMinAspectRatioForUnresizableApps" format="float" type="dimen">0.0</item>
<!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. -->
<bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool>
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 04857ec756d2..3e4b1cc87ef8 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -161,7 +161,10 @@
<!-- ChooserActivityTest permissions-->
<uses-permission android:name="android.permission.SET_CLIP_SOURCE" />
- <application android:theme="@style/Theme" android:supportsRtl="true">
+ <application
+ android:theme="@style/Theme"
+ android:supportsRtl="true"
+ android:enableOnBackInvokedCallback="true">
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
<meta-data
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index c57aa740124c..f448cb3091e7 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -64,7 +64,7 @@ public class WindowOnBackInvokedDispatcherTest {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mDispatcher = new WindowOnBackInvokedDispatcher();
+ mDispatcher = new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */);
mDispatcher.attachToWindow(mWindowSession, mWindow);
}
diff --git a/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java b/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java
new file mode 100644
index 000000000000..7c7649813824
--- /dev/null
+++ b/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Test for verifying {@link android.os.Bundle} recycles the underlying parcel where needed.
+ *
+ * <p>Build/Install/Run:
+ * atest FrameworksMockingCoreTests:android.os.BundleRecyclingTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class BundleRecyclingTest {
+ private Parcel mParcelSpy;
+ private Bundle mBundle;
+
+ @Before
+ public void setUp() throws Exception {
+ setUpBundle(/* hasLazy */ true);
+ }
+
+ @Test
+ public void bundleClear_whenUnparcelledWithoutLazy_recyclesParcelOnce() {
+ setUpBundle(/* hasLazy */ false);
+ // Will unparcel and immediately recycle parcel
+ assertNotNull(mBundle.getString("key"));
+ verify(mParcelSpy, times(1)).recycle();
+ assertFalse(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+ }
+
+ @Test
+ public void bundleClear_whenParcelled_recyclesParcel() {
+ assertTrue(mBundle.isParcelled());
+ verify(mParcelSpy, times(0)).recycle();
+
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenUnparcelledWithLazyValueUnwrapped_recyclesParcel() {
+ // Will unparcel with a lazy value, and immediately unwrap the lazy value,
+ // with no lazy values left at the end of getParcelable
+ assertNotNull(mBundle.getParcelable("key", CustomParcelable.class));
+ verify(mParcelSpy, times(0)).recycle();
+
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenUnparcelledWithLazy_recyclesParcel() {
+ // Will unparcel but keep the CustomParcelable lazy
+ assertFalse(mBundle.isEmpty());
+ verify(mParcelSpy, times(0)).recycle();
+
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenClearedWithSharedParcel_doesNotRecycleParcel() {
+ Bundle copy = new Bundle();
+ copy.putAll(mBundle);
+
+ mBundle.clear();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ copy.clear();
+ assertTrue(copy.isDefinitelyEmpty());
+
+ verify(mParcelSpy, never()).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenClearedWithCopiedParcel_doesNotRecycleParcel() {
+ // Will unparcel but keep the CustomParcelable lazy
+ assertFalse(mBundle.isEmpty());
+
+ Bundle copy = mBundle.deepCopy();
+ copy.putAll(mBundle);
+
+ mBundle.clear();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ copy.clear();
+ assertTrue(copy.isDefinitelyEmpty());
+
+ verify(mParcelSpy, never()).recycle();
+ }
+
+ private void setUpBundle(boolean hasLazy) {
+ AtomicReference<Parcel> parcel = new AtomicReference<>();
+ StaticMockitoSession session = mockitoSession()
+ .strictness(Strictness.STRICT_STUBS)
+ .spyStatic(Parcel.class)
+ .startMocking();
+ doAnswer((Answer<Parcel>) invocationOnSpy -> {
+ Parcel spy = (Parcel) invocationOnSpy.callRealMethod();
+ spyOn(spy);
+ parcel.set(spy);
+ return spy;
+ }).when(() -> Parcel.obtain());
+
+ Bundle bundle = new Bundle();
+ bundle.setClassLoader(getClass().getClassLoader());
+ Parcel p = createBundle(hasLazy);
+ bundle.readFromParcel(p);
+ p.recycle();
+
+ session.finishMocking();
+
+ mParcelSpy = parcel.get();
+ mBundle = bundle;
+ }
+
+ /**
+ * Create a test bundle, parcel it and return the parcel.
+ */
+ private Parcel createBundle(boolean hasLazy) {
+ final Bundle source = new Bundle();
+ if (hasLazy) {
+ source.putParcelable("key", new CustomParcelable(13, "Tiramisu"));
+ } else {
+ source.putString("key", "tiramisu");
+ }
+ return getParcelledBundle(source);
+ }
+
+ /**
+ * Take a bundle, write it to a parcel and return the parcel.
+ */
+ private Parcel getParcelledBundle(Bundle bundle) {
+ final Parcel p = Parcel.obtain();
+ // Don't use p.writeParcelabe(), which would write the creator, which we don't need.
+ bundle.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ return p;
+ }
+
+ private static class CustomParcelable implements Parcelable {
+ public final int integer;
+ public final String string;
+
+ CustomParcelable(int integer, String string) {
+ this.integer = integer;
+ this.string = string;
+ }
+
+ protected CustomParcelable(Parcel in) {
+ integer = in.readInt();
+ string = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(integer);
+ out.writeString(string);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof CustomParcelable)) {
+ return false;
+ }
+ CustomParcelable that = (CustomParcelable) other;
+ return integer == that.integer && string.equals(that.string);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(integer, string);
+ }
+
+ public static final Creator<CustomParcelable> CREATOR = new Creator<CustomParcelable>() {
+ @Override
+ public CustomParcelable createFromParcel(Parcel in) {
+ return new CustomParcelable(in);
+ }
+ @Override
+ public CustomParcelable[] newArray(int size) {
+ return new CustomParcelable[size];
+ }
+ };
+ }
+}
diff --git a/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java b/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java
index fa4aa803c75e..ed857e8fc960 100644
--- a/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java
+++ b/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java
@@ -88,26 +88,15 @@ public class SizeConfigurationBucketsTest {
}
/**
- * Tests that null size configuration buckets unflips the correct configuration flags.
+ * Tests that {@code null} size configuration buckets do not unflip the configuration flags.
*/
@Test
public void testNullSizeConfigurationBuckets() {
- // Check that all 3 size configurations are filtered out of the diff if the buckets are null
- // and non-size attributes of screen layout are unchanged. Add a non-size related config
- // change (i.e. CONFIG_LOCALE) to test that the diff is not set to zero.
final int diff = CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_SCREEN_LAYOUT
| CONFIG_LOCALE;
final int filteredDiffNonSizeLayoutUnchanged = SizeConfigurationBuckets.filterDiff(diff,
Configuration.EMPTY, Configuration.EMPTY, null);
- assertEquals(CONFIG_LOCALE, filteredDiffNonSizeLayoutUnchanged);
-
- // Check that only screen size and smallest screen size are filtered out of the diff if the
- // buckets are null and non-size attributes of screen layout are changed.
- final Configuration newConfig = new Configuration();
- newConfig.screenLayout |= SCREENLAYOUT_ROUND_YES;
- final int filteredDiffNonSizeLayoutChanged = SizeConfigurationBuckets.filterDiff(diff,
- Configuration.EMPTY, newConfig, null);
- assertEquals(CONFIG_SCREEN_LAYOUT | CONFIG_LOCALE, filteredDiffNonSizeLayoutChanged);
+ assertEquals(diff, filteredDiffNonSizeLayoutUnchanged);
}
/**
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index f8da95d04411..6706e4e87e0d 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -475,12 +475,6 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ResetTargetTaskHelper.java"
},
- "-1635750891": {
- "message": "Received remote change for Display[%d], applied: [%dx%d, rot = %d]",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONFIGURATION",
- "at": "com\/android\/server\/wm\/RemoteDisplayChangeController.java"
- },
"-1633115609": {
"message": "Key dispatch not paused for screen off",
"level": "VERBOSE",
@@ -1711,6 +1705,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-417730399": {
+ "message": "Preparing to sync a window that was already in the sync, so try dropping buffer. win=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/WindowState.java"
+ },
"-415865166": {
"message": "findFocusedWindow: Found new focus @ %s",
"level": "VERBOSE",
@@ -2137,6 +2137,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
+ "-4263657": {
+ "message": "Got a buffer for request id=%d but latest request is id=%d. Since the buffer is out-of-date, drop it. win=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/WindowState.java"
+ },
"3593205": {
"message": "commitVisibility: %s: visible=%b mVisibleRequested=%b",
"level": "VERBOSE",
@@ -2599,6 +2605,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "385237117": {
+ "message": "moveFocusableActivityToTop: already on top and focused, activity=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_FOCUS",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"385595355": {
"message": "Starting animation on %s: type=%d, anim=%s",
"level": "VERBOSE",
@@ -3403,6 +3415,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1239439010": {
+ "message": "moveFocusableActivityToTop: set focused, activity=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_FOCUS",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"1252594551": {
"message": "Window types in WindowContext and LayoutParams.type should match! Type from LayoutParams is %d, but type from WindowContext is %d",
"level": "WARN",
@@ -3877,12 +3895,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowStateAnimator.java"
},
- "1764619787": {
- "message": "Remote change for Display[%d]: timeout reached",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONFIGURATION",
- "at": "com\/android\/server\/wm\/RemoteDisplayChangeController.java"
- },
"1774661765": {
"message": "Devices still not ready after waiting %d milliseconds before attempting to detect safe mode.",
"level": "WARN",
@@ -3991,12 +4003,6 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1856211951": {
- "message": "moveFocusableActivityToTop: already on top, activity=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_FOCUS",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"1856783490": {
"message": "resumeTopActivity: Restarting %s",
"level": "DEBUG",
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 1629b6ace35d..239621eeed1e 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -40,6 +40,7 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.net.Uri;
import android.os.Build;
+import android.os.Trace;
import android.system.ErrnoException;
import android.system.Os;
import android.util.DisplayMetrics;
@@ -223,13 +224,21 @@ public final class ImageDecoder implements AutoCloseable {
public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
return nCreate(mData, mOffset, mLength, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "ByteArraySource{len=" + mLength + "}";
+ }
}
private static class ByteBufferSource extends Source {
ByteBufferSource(@NonNull ByteBuffer buffer) {
mBuffer = buffer;
+ mLength = mBuffer.limit() - mBuffer.position();
}
+
private final ByteBuffer mBuffer;
+ private final int mLength;
@Override
public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
@@ -241,6 +250,11 @@ public final class ImageDecoder implements AutoCloseable {
ByteBuffer buffer = mBuffer.slice();
return nCreate(buffer, buffer.position(), buffer.limit(), preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "ByteBufferSource{len=" + mLength + "}";
+ }
}
private static class ContentResolverSource extends Source {
@@ -285,6 +299,16 @@ public final class ImageDecoder implements AutoCloseable {
return createFromAssetFileDescriptor(assetFd, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ String uri = mUri.toString();
+ if (uri.length() > 90) {
+ // We want to keep the Uri usable - usually the authority and the end is important.
+ uri = uri.substring(0, 80) + ".." + uri.substring(uri.length() - 10);
+ }
+ return "ContentResolverSource{uri=" + uri + "}";
+ }
}
@NonNull
@@ -399,6 +423,11 @@ public final class ImageDecoder implements AutoCloseable {
return createFromStream(is, false, preferAnimation, this);
}
}
+
+ @Override
+ public String toString() {
+ return "InputStream{s=" + mInputStream + "}";
+ }
}
/**
@@ -444,6 +473,11 @@ public final class ImageDecoder implements AutoCloseable {
return createFromAsset(ais, preferAnimation, this);
}
}
+
+ @Override
+ public String toString() {
+ return "AssetInputStream{s=" + mAssetInputStream + "}";
+ }
}
private static class ResourceSource extends Source {
@@ -485,6 +519,17 @@ public final class ImageDecoder implements AutoCloseable {
return createFromAsset((AssetInputStream) is, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ // Try to return a human-readable name for debugging purposes.
+ try {
+ return "Resource{name=" + mResources.getResourceName(mResId) + "}";
+ } catch (Resources.NotFoundException e) {
+ // It's ok if we don't find it, fall back to ID.
+ }
+ return "Resource{id=" + mResId + "}";
+ }
}
/**
@@ -521,6 +566,11 @@ public final class ImageDecoder implements AutoCloseable {
InputStream is = mAssets.open(mFileName);
return createFromAsset((AssetInputStream) is, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "AssetSource{file=" + mFileName + "}";
+ }
}
private static class FileSource extends Source {
@@ -534,6 +584,11 @@ public final class ImageDecoder implements AutoCloseable {
public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
return createFromFile(mFile, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "FileSource{file=" + mFile + "}";
+ }
}
private static class CallableSource extends Source {
@@ -557,6 +612,11 @@ public final class ImageDecoder implements AutoCloseable {
}
return createFromAssetFileDescriptor(assetFd, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "CallableSource{obj=" + mCallable.toString() + "}";
+ }
}
/**
@@ -1763,61 +1823,65 @@ public final class ImageDecoder implements AutoCloseable {
@NonNull
private static Drawable decodeDrawableImpl(@NonNull Source src,
@Nullable OnHeaderDecodedListener listener) throws IOException {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ImageDecoder#decodeDrawable");
try (ImageDecoder decoder = src.createImageDecoder(true /*preferAnimation*/)) {
decoder.mSource = src;
decoder.callHeaderDecoded(listener, src);
- if (decoder.mUnpremultipliedRequired) {
- // Though this could be supported (ignored) for opaque images,
- // it seems better to always report this error.
- throw new IllegalStateException("Cannot decode a Drawable " +
- "with unpremultiplied pixels!");
- }
+ try (ImageDecoderSourceTrace unused = new ImageDecoderSourceTrace(decoder)) {
+ if (decoder.mUnpremultipliedRequired) {
+ // Though this could be supported (ignored) for opaque images,
+ // it seems better to always report this error.
+ throw new IllegalStateException(
+ "Cannot decode a Drawable with unpremultiplied pixels!");
+ }
- if (decoder.mMutable) {
- throw new IllegalStateException("Cannot decode a mutable " +
- "Drawable!");
- }
+ if (decoder.mMutable) {
+ throw new IllegalStateException("Cannot decode a mutable Drawable!");
+ }
- // this call potentially manipulates the decoder so it must be performed prior to
- // decoding the bitmap and after decode set the density on the resulting bitmap
- final int srcDensity = decoder.computeDensity(src);
- if (decoder.mAnimated) {
- // AnimatedImageDrawable calls postProcessAndRelease only if
- // mPostProcessor exists.
- ImageDecoder postProcessPtr = decoder.mPostProcessor == null ?
- null : decoder;
- decoder.checkState(true);
- Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
- postProcessPtr, decoder.mDesiredWidth,
- decoder.mDesiredHeight, decoder.getColorSpacePtr(),
- decoder.checkForExtended(), srcDensity,
- src.computeDstDensity(), decoder.mCropRect,
- decoder.mInputStream, decoder.mAssetFd);
- // d has taken ownership of these objects.
- decoder.mInputStream = null;
- decoder.mAssetFd = null;
- return d;
- }
+ // this call potentially manipulates the decoder so it must be performed prior to
+ // decoding the bitmap and after decode set the density on the resulting bitmap
+ final int srcDensity = decoder.computeDensity(src);
+ if (decoder.mAnimated) {
+ // AnimatedImageDrawable calls postProcessAndRelease only if
+ // mPostProcessor exists.
+ ImageDecoder postProcessPtr = decoder.mPostProcessor == null ? null : decoder;
+ decoder.checkState(true);
+ Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
+ postProcessPtr, decoder.mDesiredWidth,
+ decoder.mDesiredHeight, decoder.getColorSpacePtr(),
+ decoder.checkForExtended(), srcDensity,
+ src.computeDstDensity(), decoder.mCropRect,
+ decoder.mInputStream, decoder.mAssetFd);
+ // d has taken ownership of these objects.
+ decoder.mInputStream = null;
+ decoder.mAssetFd = null;
+ return d;
+ }
- Bitmap bm = decoder.decodeBitmapInternal();
- bm.setDensity(srcDensity);
+ Bitmap bm = decoder.decodeBitmapInternal();
+ bm.setDensity(srcDensity);
- Resources res = src.getResources();
- byte[] np = bm.getNinePatchChunk();
- if (np != null && NinePatch.isNinePatchChunk(np)) {
- Rect opticalInsets = new Rect();
- bm.getOpticalInsets(opticalInsets);
- Rect padding = decoder.mOutPaddingRect;
- if (padding == null) {
- padding = new Rect();
+ Resources res = src.getResources();
+ byte[] np = bm.getNinePatchChunk();
+ if (np != null && NinePatch.isNinePatchChunk(np)) {
+ Rect opticalInsets = new Rect();
+ bm.getOpticalInsets(opticalInsets);
+ Rect padding = decoder.mOutPaddingRect;
+ if (padding == null) {
+ padding = new Rect();
+ }
+ nGetPadding(decoder.mNativePtr, padding);
+ return new NinePatchDrawable(res, bm, np, padding,
+ opticalInsets, null);
}
- nGetPadding(decoder.mNativePtr, padding);
- return new NinePatchDrawable(res, bm, np, padding,
- opticalInsets, null);
- }
- return new BitmapDrawable(res, bm);
+ return new BitmapDrawable(res, bm);
+ }
+ } finally {
+ // Close the ImageDecoder#decode trace.
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
@@ -1867,26 +1931,51 @@ public final class ImageDecoder implements AutoCloseable {
@NonNull
private static Bitmap decodeBitmapImpl(@NonNull Source src,
@Nullable OnHeaderDecodedListener listener) throws IOException {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ImageDecoder#decodeBitmap");
try (ImageDecoder decoder = src.createImageDecoder(false /*preferAnimation*/)) {
decoder.mSource = src;
decoder.callHeaderDecoded(listener, src);
+ try (ImageDecoderSourceTrace unused = new ImageDecoderSourceTrace(decoder)) {
+ // this call potentially manipulates the decoder so it must be performed prior to
+ // decoding the bitmap
+ final int srcDensity = decoder.computeDensity(src);
+ Bitmap bm = decoder.decodeBitmapInternal();
+ bm.setDensity(srcDensity);
- // this call potentially manipulates the decoder so it must be performed prior to
- // decoding the bitmap
- final int srcDensity = decoder.computeDensity(src);
- Bitmap bm = decoder.decodeBitmapInternal();
- bm.setDensity(srcDensity);
-
- Rect padding = decoder.mOutPaddingRect;
- if (padding != null) {
- byte[] np = bm.getNinePatchChunk();
- if (np != null && NinePatch.isNinePatchChunk(np)) {
- nGetPadding(decoder.mNativePtr, padding);
+ Rect padding = decoder.mOutPaddingRect;
+ if (padding != null) {
+ byte[] np = bm.getNinePatchChunk();
+ if (np != null && NinePatch.isNinePatchChunk(np)) {
+ nGetPadding(decoder.mNativePtr, padding);
+ }
}
+ return bm;
}
+ } finally {
+ // Close the ImageDecoder#decode trace.
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
- return bm;
+ /**
+ * This describes the decoder in traces to ease debugging. It has to be called after
+ * header has been decoded and width/height have been populated. It should be used
+ * inside a try-with-resources call to automatically complete the trace.
+ */
+ private static AutoCloseable traceDecoderSource(ImageDecoder decoder) {
+ final boolean resourceTracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_RESOURCES);
+ if (resourceTracingEnabled) {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, describeDecoderForTrace(decoder));
}
+
+ return new AutoCloseable() {
+ @Override
+ public void close() throws Exception {
+ if (resourceTracingEnabled) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
+ };
}
// This method may modify the decoder so it must be called prior to performing the decode
@@ -1994,6 +2083,66 @@ public final class ImageDecoder implements AutoCloseable {
}
}
+ /**
+ * Returns a short string describing what passed ImageDecoder is loading -
+ * it reports image dimensions, desired dimensions (if any) and source resource.
+ *
+ * The string appears in perf traces to simplify search for slow or memory intensive
+ * image loads.
+ *
+ * Example: ID#w=300;h=250;dw=150;dh=150;src=Resource{name=@resource}
+ *
+ * @hide
+ */
+ private static String describeDecoderForTrace(@NonNull ImageDecoder decoder) {
+ StringBuilder builder = new StringBuilder();
+ // Source dimensions
+ builder.append("ID#w=");
+ builder.append(decoder.mWidth);
+ builder.append(";h=");
+ builder.append(decoder.mHeight);
+ // Desired dimensions (if present)
+ if (decoder.mDesiredWidth != decoder.mWidth
+ || decoder.mDesiredHeight != decoder.mHeight) {
+ builder.append(";dw=");
+ builder.append(decoder.mDesiredWidth);
+ builder.append(";dh=");
+ builder.append(decoder.mDesiredHeight);
+ }
+ // Source description
+ builder.append(";src=");
+ builder.append(decoder.mSource);
+ return builder.toString();
+ }
+
+ /**
+ * Records a trace with information about the source being decoded - dimensions,
+ * desired dimensions and source information.
+ *
+ * It significantly eases debugging of slow resource loads on main thread and
+ * possible large memory consumers.
+ *
+ * @hide
+ */
+ private static final class ImageDecoderSourceTrace implements AutoCloseable {
+
+ private final boolean mResourceTracingEnabled;
+
+ ImageDecoderSourceTrace(ImageDecoder decoder) {
+ mResourceTracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_RESOURCES);
+ if (mResourceTracingEnabled) {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, describeDecoderForTrace(decoder));
+ }
+ }
+
+ @Override
+ public void close() {
+ if (mResourceTracingEnabled) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
+ }
+
private static native ImageDecoder nCreate(long asset,
boolean preferAnimation, Source src) throws IOException;
private static native ImageDecoder nCreate(ByteBuffer buffer, int position, int limit,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index b90b4c1b39a1..2f79caeec7ba 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -24,9 +24,9 @@ import static androidx.window.extensions.embedding.SplitContainer.getFinishSecon
import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
-import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
+import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
-import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
+import static androidx.window.extensions.embedding.SplitPresenter.getNonEmbeddedActivityBounds;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
import android.app.Activity;
@@ -389,6 +389,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* in a state that the caller shouldn't handle.
*/
@VisibleForTesting
+ @GuardedBy("mLock")
boolean resolveActivityToContainer(@NonNull Activity activity, boolean isOnReparent) {
if (isInPictureInPicture(activity) || activity.isFinishing()) {
// We don't embed activity when it is in PIP, or finishing. Return true since we don't
@@ -589,8 +590,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/** Finds the activity below the given activity. */
+ @VisibleForTesting
@Nullable
- private Activity findActivityBelow(@NonNull Activity activity) {
+ Activity findActivityBelow(@NonNull Activity activity) {
Activity activityBelow = null;
final TaskFragmentContainer container = getContainerWithActivity(activity);
if (container != null) {
@@ -614,6 +616,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Checks if there is a rule to split the two activities. If there is one, puts them into split
* and returns {@code true}. Otherwise, returns {@code false}.
*/
+ @GuardedBy("mLock")
private boolean putActivitiesIntoSplitIfNecessary(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) {
final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity);
@@ -624,25 +627,25 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
primaryActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
- && canReuseContainer(splitRule, splitContainer.getSplitRule())
- && !boundsSmallerThanMinDimensions(primaryContainer.getLastRequestedBounds(),
- getMinDimensions(primaryActivity))) {
+ && canReuseContainer(splitRule, splitContainer.getSplitRule())) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
- if (secondaryContainer == getContainerWithActivity(secondaryActivity)
- && !boundsSmallerThanMinDimensions(secondaryContainer.getLastRequestedBounds(),
- getMinDimensions(secondaryActivity))) {
+ if (secondaryContainer == getContainerWithActivity(secondaryActivity)) {
// The activity is already in the target TaskFragment.
return true;
}
secondaryContainer.addPendingAppearedActivity(secondaryActivity);
final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reparentActivityToTaskFragment(
- secondaryContainer.getTaskFragmentToken(),
- secondaryActivity.getActivityToken());
- mPresenter.applyTransaction(wct);
- return true;
+ if (mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
+ secondaryActivity, null /* secondaryIntent */)
+ != RESULT_EXPAND_FAILED_NO_TF_INFO) {
+ wct.reparentActivityToTaskFragment(
+ secondaryContainer.getTaskFragmentToken(),
+ secondaryActivity.getActivityToken());
+ mPresenter.applyTransaction(wct);
+ return true;
+ }
}
// Create new split pair.
mPresenter.createNewSplitContainer(primaryActivity, secondaryActivity, splitRule);
@@ -650,6 +653,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
private void onActivityConfigurationChanged(@NonNull Activity activity) {
+ if (activity.isFinishing()) {
+ // Do nothing if the activity is currently finishing.
+ return;
+ }
+
if (isInPictureInPicture(activity)) {
// We don't embed activity when it is in PIP.
return;
@@ -795,6 +803,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Returns a container for the new activity intent to launch into as splitting with the primary
* activity.
*/
+ @GuardedBy("mLock")
@Nullable
private TaskFragmentContainer getSecondaryContainerForSplitIfAny(
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
@@ -808,16 +817,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
&& (canReuseContainer(splitRule, splitContainer.getSplitRule())
// TODO(b/231845476) we should always respect clearTop.
- || !respectClearTop)) {
- final Rect secondaryBounds = splitContainer.getSecondaryContainer()
- .getLastRequestedBounds();
- if (secondaryBounds.isEmpty()
- || !boundsSmallerThanMinDimensions(secondaryBounds,
- getMinDimensions(intent))) {
- // Can launch in the existing secondary container if the rules share the same
- // presentation.
- return splitContainer.getSecondaryContainer();
- }
+ || !respectClearTop)
+ && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
+ null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) {
+ // Can launch in the existing secondary container if the rules share the same
+ // presentation.
+ return splitContainer.getSecondaryContainer();
}
// Create a new TaskFragment to split with the primary activity for the new activity.
return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent,
@@ -871,6 +876,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* if needed.
* @param taskId parent Task of the new TaskFragment.
*/
+ @GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) {
if (activityInTask == null) {
@@ -884,7 +890,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
pendingAppearedIntent, taskContainer, this);
if (!taskContainer.isTaskBoundsInitialized()) {
// Get the initial bounds before the TaskFragment has appeared.
- final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask);
+ final Rect taskBounds = getNonEmbeddedActivityBounds(activityInTask);
if (!taskContainer.setTaskBounds(taskBounds)) {
Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
}
@@ -1127,6 +1133,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
boolean launchPlaceholderIfNecessary(@NonNull Activity activity, boolean isOnCreated) {
+ if (activity.isFinishing()) {
+ return false;
+ }
+
final TaskFragmentContainer container = getContainerWithActivity(activity);
// Don't launch placeholder if the container is occluded.
if (container != null && container != getTopActiveContainer(container.getTaskId())) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 1b79ad999435..a89847a30d20 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -65,6 +65,41 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
})
private @interface Position {}
+ /**
+ * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
+ * Activity, Activity, Intent)}.
+ * No need to expand the splitContainer because screen is big enough to
+ * {@link #shouldShowSideBySide(Rect, SplitRule, Pair)} and minimum dimensions is satisfied.
+ */
+ static final int RESULT_NOT_EXPANDED = 0;
+ /**
+ * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
+ * Activity, Activity, Intent)}.
+ * The splitContainer should be expanded. It is usually because minimum dimensions is not
+ * satisfied.
+ * @see #shouldShowSideBySide(Rect, SplitRule, Pair)
+ */
+ static final int RESULT_EXPANDED = 1;
+ /**
+ * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
+ * Activity, Activity, Intent)}.
+ * The splitContainer should be expanded, but the client side hasn't received
+ * {@link android.window.TaskFragmentInfo} yet. Fallback to create new expanded SplitContainer
+ * instead.
+ */
+ static final int RESULT_EXPAND_FAILED_NO_TF_INFO = 2;
+
+ /**
+ * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
+ * Activity, Activity, Intent)}
+ */
+ @IntDef(value = {
+ RESULT_NOT_EXPANDED,
+ RESULT_EXPANDED,
+ RESULT_EXPAND_FAILED_NO_TF_INFO,
+ })
+ private @interface ResultCode {}
+
private final SplitController mController;
SplitPresenter(@NonNull Executor executor, SplitController controller) {
@@ -396,6 +431,44 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
super.updateWindowingMode(wct, fragmentToken, windowingMode);
}
+ /**
+ * Expands the split container if the current split bounds are smaller than the Activity or
+ * Intent that is added to the container.
+ *
+ * @return the {@link ResultCode} based on {@link #shouldShowSideBySide(Rect, SplitRule, Pair)}
+ * and if {@link android.window.TaskFragmentInfo} has reported to the client side.
+ */
+ @ResultCode
+ int expandSplitContainerIfNeeded(@NonNull WindowContainerTransaction wct,
+ @NonNull SplitContainer splitContainer, @NonNull Activity primaryActivity,
+ @Nullable Activity secondaryActivity, @Nullable Intent secondaryIntent) {
+ if (secondaryActivity == null && secondaryIntent == null) {
+ throw new IllegalArgumentException("Either secondaryActivity or secondaryIntent must be"
+ + " non-null.");
+ }
+ final Rect taskBounds = getParentContainerBounds(primaryActivity);
+ final Pair<Size, Size> minDimensionsPair;
+ if (secondaryActivity != null) {
+ minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity);
+ } else {
+ minDimensionsPair = getActivityIntentMinDimensionsPair(primaryActivity,
+ secondaryIntent);
+ }
+ // Expand the splitContainer if minimum dimensions are not satisfied.
+ if (!shouldShowSideBySide(taskBounds, splitContainer.getSplitRule(), minDimensionsPair)) {
+ // If the client side hasn't received TaskFragmentInfo yet, we can't change TaskFragment
+ // bounds. Return failure to create a new SplitContainer which fills task bounds.
+ if (splitContainer.getPrimaryContainer().getInfo() == null
+ || splitContainer.getSecondaryContainer().getInfo() == null) {
+ return RESULT_EXPAND_FAILED_NO_TF_INFO;
+ }
+ expandTaskFragment(wct, splitContainer.getPrimaryContainer().getTaskFragmentToken());
+ expandTaskFragment(wct, splitContainer.getSecondaryContainer().getTaskFragmentToken());
+ return RESULT_EXPANDED;
+ }
+ return RESULT_NOT_EXPANDED;
+ }
+
static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) {
return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */);
}
@@ -565,11 +638,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
if (container != null) {
return getParentContainerBounds(container);
}
- return getTaskBoundsFromActivity(activity);
+ // Obtain bounds from Activity instead because the Activity hasn't been embedded yet.
+ return getNonEmbeddedActivityBounds(activity);
}
+ /**
+ * Obtains the bounds from a non-embedded Activity.
+ * <p>
+ * Note that callers should use {@link #getParentContainerBounds(Activity)} instead for most
+ * cases unless we want to obtain task bounds before
+ * {@link TaskContainer#isTaskBoundsInitialized()}.
+ */
@NonNull
- static Rect getTaskBoundsFromActivity(@NonNull Activity activity) {
+ static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) {
final WindowConfiguration windowConfiguration =
activity.getResources().getConfiguration().windowConfiguration;
if (!activity.isInMultiWindowMode()) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 1ac33173668b..c4f37091a491 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -83,9 +83,9 @@ class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
}
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
if (TaskFragmentAnimationController.DEBUG) {
- Log.v(TAG, "onAnimationCancelled");
+ Log.v(TAG, "onAnimationCancelled: isKeyguardOccluded=" + isKeyguardOccluded);
}
mHandler.post(this::cancelAnimation);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index cfb32050e32f..18086f552ea3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -25,7 +25,10 @@ import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
import android.annotation.Nullable;
import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManager.AppTask;
import android.app.Application;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
@@ -180,7 +183,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
if (displayId != DEFAULT_DISPLAY) {
Log.w(TAG, "This sample doesn't support display features on secondary displays");
return features;
- } else if (activity.isInMultiWindowMode()) {
+ } else if (isTaskInMultiWindowMode(activity)) {
// It is recommended not to report any display features in multi-window mode, since it
// won't be possible to synchronize the display feature positions with window movement.
return features;
@@ -204,6 +207,32 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
/**
+ * Checks whether the task associated with the activity is in multi-window. If task info is not
+ * available it defaults to {@code true}.
+ */
+ private boolean isTaskInMultiWindowMode(@NonNull Activity activity) {
+ final ActivityManager am = activity.getSystemService(ActivityManager.class);
+ if (am == null) {
+ return true;
+ }
+
+ final List<AppTask> appTasks = am.getAppTasks();
+ final int taskId = activity.getTaskId();
+ AppTask task = null;
+ for (AppTask t : appTasks) {
+ if (t.getTaskInfo().taskId == taskId) {
+ task = t;
+ break;
+ }
+ }
+ if (task == null) {
+ // The task might be removed on the server already.
+ return true;
+ }
+ return WindowConfiguration.inMultiWindowMode(task.getTaskInfo().getWindowingMode());
+ }
+
+ /**
* Returns {@link true} if a {@link Rect} has zero width and zero height,
* {@code false} otherwise.
*/
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index 835c40365cda..effc1a3ef3ea 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock;
import android.annotation.NonNull;
import android.app.Activity;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
@@ -57,13 +58,21 @@ public class EmbeddingTestUtils {
/** Creates a rule to always split the given activity and the given intent. */
static SplitRule createSplitRule(@NonNull Activity primaryActivity,
@NonNull Intent secondaryIntent) {
+ return createSplitRule(primaryActivity, secondaryIntent, true /* clearTop */);
+ }
+
+ /** Creates a rule to always split the given activity and the given intent. */
+ static SplitRule createSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Intent secondaryIntent, boolean clearTop) {
final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent);
return new SplitPairRule.Builder(
activityPair -> false,
targetPair::equals,
w -> true)
.setSplitRatio(SPLIT_RATIO)
- .setShouldClearTop(true)
+ .setShouldClearTop(clearTop)
+ .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+ .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
.build();
}
@@ -75,6 +84,14 @@ public class EmbeddingTestUtils {
true /* clearTop */);
}
+ /** Creates a rule to always split the given activities. */
+ static SplitRule createSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity, boolean clearTop) {
+ return createSplitRule(primaryActivity, secondaryActivity,
+ DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY,
+ clearTop);
+ }
+
/** Creates a rule to always split the given activities with the given finish behaviors. */
static SplitRule createSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity, int finishPrimaryWithSecondary,
@@ -105,4 +122,12 @@ public class EmbeddingTestUtils {
false /* isTaskFragmentClearedForPip */,
new Point());
}
+
+ static ActivityInfo createActivityInfoWithMinDimensions() {
+ ActivityInfo aInfo = new ActivityInfo();
+ final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
+ aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
+ primaryBounds.width() + 1, primaryBounds.height() + 1);
+ return aInfo;
+ }
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index ef7728cec387..042547fd30f2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
@@ -34,6 +35,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -436,6 +438,50 @@ public class SplitControllerTest {
}
@Test
+ public void testResolveStartActivityIntent_shouldExpandSplitContainer() {
+ final Intent intent = new Intent().setComponent(
+ new ComponentName(ApplicationProvider.getApplicationContext(),
+ MinimumDimensionActivity.class));
+ setupSplitRule(mActivity, intent, false /* clearTop */);
+ final Activity secondaryActivity = createMockActivity();
+ addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */);
+
+ final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
+ mTransaction, TASK_ID, intent, mActivity);
+ final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
+ mActivity);
+
+ assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container));
+ assertTrue(primaryContainer.areLastRequestedBoundsEqual(null));
+ assertTrue(container.areLastRequestedBoundsEqual(null));
+ assertEquals(container, mSplitController.getContainerWithActivity(secondaryActivity));
+ }
+
+ @Test
+ public void testResolveStartActivityIntent_noInfo_shouldCreateSplitContainer() {
+ final Intent intent = new Intent().setComponent(
+ new ComponentName(ApplicationProvider.getApplicationContext(),
+ MinimumDimensionActivity.class));
+ setupSplitRule(mActivity, intent, false /* clearTop */);
+ final Activity secondaryActivity = createMockActivity();
+ addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */);
+
+ final TaskFragmentContainer secondaryContainer = mSplitController
+ .getContainerWithActivity(secondaryActivity);
+ secondaryContainer.mInfo = null;
+
+ final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
+ mTransaction, TASK_ID, intent, mActivity);
+ final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
+ mActivity);
+
+ assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container));
+ assertTrue(primaryContainer.areLastRequestedBoundsEqual(null));
+ assertTrue(container.areLastRequestedBoundsEqual(null));
+ assertNotEquals(container, secondaryContainer);
+ }
+
+ @Test
public void testPlaceActivityInTopContainer() {
mSplitController.placeActivityInTopContainer(mActivity);
@@ -787,11 +833,7 @@ public class SplitControllerTest {
final Activity activityBelow = createMockActivity();
setupSplitRule(mActivity, activityBelow);
- ActivityInfo aInfo = new ActivityInfo();
- final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
- aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
- primaryBounds.width() + 1, primaryBounds.height() + 1);
- doReturn(aInfo).when(mActivity).getActivityInfo();
+ doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo();
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
TASK_ID);
@@ -810,17 +852,12 @@ public class SplitControllerTest {
final Activity activityBelow = createMockActivity();
setupSplitRule(activityBelow, mActivity);
- ActivityInfo aInfo = new ActivityInfo();
- final Rect secondaryBounds = getSplitBounds(false /* isPrimary */);
- aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
- secondaryBounds.width() + 1, secondaryBounds.height() + 1);
- doReturn(aInfo).when(mActivity).getActivityInfo();
+ doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo();
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
TASK_ID);
container.addPendingAppearedActivity(mActivity);
- // Allow to split as primary.
boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* isOnReparent */);
@@ -828,6 +865,29 @@ public class SplitControllerTest {
assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */);
}
+ // Suppress GuardedBy warning on unit tests
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testResolveActivityToContainer_minDimensions_shouldExpandSplitContainer() {
+ final Activity primaryActivity = createMockActivity();
+ final Activity secondaryActivity = createMockActivity();
+ addSplitTaskFragments(primaryActivity, secondaryActivity, false /* clearTop */);
+
+ setupSplitRule(primaryActivity, mActivity, false /* clearTop */);
+ doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo();
+ doReturn(secondaryActivity).when(mSplitController).findActivityBelow(eq(mActivity));
+
+ clearInvocations(mSplitPresenter);
+ boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ false /* isOnReparent */);
+
+ assertTrue(result);
+ assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */);
+ assertEquals(mSplitController.getContainerWithActivity(secondaryActivity),
+ mSplitController.getContainerWithActivity(mActivity));
+ verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any());
+ }
+
@Test
public void testResolveActivityToContainer_inUnknownTaskFragment() {
doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity);
@@ -944,23 +1004,41 @@ public class SplitControllerTest {
/** Setups a rule to always split the given activities. */
private void setupSplitRule(@NonNull Activity primaryActivity,
@NonNull Intent secondaryIntent) {
- final SplitRule splitRule = createSplitRule(primaryActivity, secondaryIntent);
+ setupSplitRule(primaryActivity, secondaryIntent, true /* clearTop */);
+ }
+
+ /** Setups a rule to always split the given activities. */
+ private void setupSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Intent secondaryIntent, boolean clearTop) {
+ final SplitRule splitRule = createSplitRule(primaryActivity, secondaryIntent, clearTop);
mSplitController.setEmbeddingRules(Collections.singleton(splitRule));
}
/** Setups a rule to always split the given activities. */
private void setupSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) {
- final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity);
+ setupSplitRule(primaryActivity, secondaryActivity, true /* clearTop */);
+ }
+
+ /** Setups a rule to always split the given activities. */
+ private void setupSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity, boolean clearTop) {
+ final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity, clearTop);
mSplitController.setEmbeddingRules(Collections.singleton(splitRule));
}
/** Adds a pair of TaskFragments as split for the given activities. */
private void addSplitTaskFragments(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) {
+ addSplitTaskFragments(primaryActivity, secondaryActivity, true /* clearTop */);
+ }
+
+ /** Adds a pair of TaskFragments as split for the given activities. */
+ private void addSplitTaskFragments(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity, boolean clearTop) {
registerSplitPair(createMockTaskFragmentContainer(primaryActivity),
createMockTaskFragmentContainer(secondaryActivity),
- createSplitRule(primaryActivity, secondaryActivity));
+ createSplitRule(primaryActivity, secondaryActivity, clearTop));
}
/** Registers the two given TaskFragments as split pair. */
@@ -1011,16 +1089,18 @@ public class SplitControllerTest {
if (primaryContainer.mInfo != null) {
final Rect primaryBounds = matchParentBounds ? new Rect()
: getSplitBounds(true /* isPrimary */);
+ final int windowingMode = matchParentBounds ? WINDOWING_MODE_UNDEFINED
+ : WINDOWING_MODE_MULTI_WINDOW;
assertTrue(primaryContainer.areLastRequestedBoundsEqual(primaryBounds));
- assertTrue(primaryContainer.isLastRequestedWindowingModeEqual(
- WINDOWING_MODE_MULTI_WINDOW));
+ assertTrue(primaryContainer.isLastRequestedWindowingModeEqual(windowingMode));
}
if (secondaryContainer.mInfo != null) {
final Rect secondaryBounds = matchParentBounds ? new Rect()
: getSplitBounds(false /* isPrimary */);
+ final int windowingMode = matchParentBounds ? WINDOWING_MODE_UNDEFINED
+ : WINDOWING_MODE_MULTI_WINDOW;
assertTrue(secondaryContainer.areLastRequestedBoundsEqual(secondaryBounds));
- assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual(
- WINDOWING_MODE_MULTI_WINDOW));
+ assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual(windowingMode));
}
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index acc398a27baf..d79319666c01 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -20,11 +20,16 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START;
+import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPANDED;
+import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
+import static androidx.window.extensions.embedding.SplitPresenter.RESULT_NOT_EXPANDED;
import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition;
import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
@@ -34,6 +39,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -49,6 +55,7 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
import android.util.Size;
@@ -195,6 +202,52 @@ public class SplitPresenterTest {
splitRule, mActivity, minDimensionsPair));
}
+ @Test
+ public void testExpandSplitContainerIfNeeded() {
+ SplitContainer splitContainer = mock(SplitContainer.class);
+ Activity secondaryActivity = createMockActivity();
+ SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
+ TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID);
+ TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID);
+ doReturn(splitRule).when(splitContainer).getSplitRule();
+ doReturn(primaryTf).when(splitContainer).getPrimaryContainer();
+ doReturn(secondaryTf).when(splitContainer).getSecondaryContainer();
+
+ assertThrows(IllegalArgumentException.class, () ->
+ mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity,
+ null /* secondaryActivity */, null /* secondaryIntent */));
+
+ assertEquals(RESULT_NOT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
+ splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
+ verify(mPresenter, never()).expandTaskFragment(any(), any());
+
+ doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo();
+ assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded(
+ mTransaction, splitContainer, mActivity, secondaryActivity,
+ null /* secondaryIntent */));
+
+ primaryTf.setInfo(createMockTaskFragmentInfo(primaryTf, mActivity));
+ secondaryTf.setInfo(createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
+
+ assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
+ splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
+ verify(mPresenter).expandTaskFragment(eq(mTransaction),
+ eq(primaryTf.getTaskFragmentToken()));
+ verify(mPresenter).expandTaskFragment(eq(mTransaction),
+ eq(secondaryTf.getTaskFragmentToken()));
+
+ clearInvocations(mPresenter);
+
+ assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
+ splitContainer, mActivity, null /* secondaryActivity */,
+ new Intent(ApplicationProvider.getApplicationContext(),
+ MinimumDimensionActivity.class)));
+ verify(mPresenter).expandTaskFragment(eq(mTransaction),
+ eq(primaryTf.getTaskFragmentToken()));
+ verify(mPresenter).expandTaskFragment(eq(mTransaction),
+ eq(secondaryTf.getTaskFragmentToken()));
+ }
+
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
final Configuration activityConfig = new Configuration();
@@ -203,6 +256,7 @@ public class SplitPresenterTest {
doReturn(mActivityResources).when(activity).getResources();
doReturn(activityConfig).when(mActivityResources).getConfiguration();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
+ doReturn(mock(IBinder.class)).when(activity).getActivityToken();
return activity;
}
}
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
index 86ca65526336..cc0333efd82b 100644
--- a/libs/WindowManager/Shell/res/values-television/config.xml
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -43,4 +43,13 @@
<!-- Time (duration in milliseconds) that the shell waits for an app to close the PiP by itself
if a custom action is present before closing it. -->
<integer name="config_pipForceCloseDelay">5000</integer>
+
+ <!-- Animation duration when exit starting window: fade out icon -->
+ <integer name="starting_window_app_reveal_icon_fade_out_duration">0</integer>
+
+ <!-- Animation duration when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_delay">0</integer>
+
+ <!-- Animation duration when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_duration">0</integer>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index b2f09895d7d8..68a08513e7f5 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -157,7 +157,7 @@
<string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
<!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
- <string name="restart_button_description">Tap to restart this app and go full screen.</string>
+ <string name="restart_button_description">Tap to restart this app for a better view.</string>
<!-- Description of the camera compat button for applying stretched issues treatment in the hint for
compatibility control. [CHAR LIMIT=NONE] -->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index e71a59d26740..8c0affb0a432 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -31,13 +31,15 @@ public interface BackAnimation {
/**
* Called when a {@link MotionEvent} is generated by a back gesture.
*
- * @param event the original {@link MotionEvent}
- * @param action the original {@link KeyEvent#getAction()} when the event was dispatched to
+ * @param touchX the X touch position of the {@link MotionEvent}.
+ * @param touchY the Y touch position of the {@link MotionEvent}.
+ * @param keyAction the original {@link KeyEvent#getAction()} when the event was dispatched to
* the process. This is forwarded separately because the input pipeline may mutate
* the {#event} action state later.
* @param swipeEdge the edge from which the swipe begins.
*/
- void onBackMotion(MotionEvent event, int action, @BackEvent.SwipeEdge int swipeEdge);
+ void onBackMotion(float touchX, float touchY, int keyAction,
+ @BackEvent.SwipeEdge int swipeEdge);
/**
* Sets whether the back gesture is past the trigger threshold or not.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 0cb56d72004d..0cf2b28921e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -184,8 +184,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@Override
public void onBackMotion(
- MotionEvent event, int action, @BackEvent.SwipeEdge int swipeEdge) {
- mShellExecutor.execute(() -> onMotionEvent(event, action, swipeEdge));
+ float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
+ mShellExecutor.execute(() -> onMotionEvent(touchX, touchY, keyAction, swipeEdge));
}
@Override
@@ -256,33 +256,34 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
* Called when a new motion event needs to be transferred to this
* {@link BackAnimationController}
*/
- public void onMotionEvent(MotionEvent event, int action, @BackEvent.SwipeEdge int swipeEdge) {
+ public void onMotionEvent(float touchX, float touchY, int keyAction,
+ @BackEvent.SwipeEdge int swipeEdge) {
if (mTransitionInProgress) {
return;
}
- if (action == MotionEvent.ACTION_MOVE) {
+ if (keyAction == MotionEvent.ACTION_MOVE) {
if (!mBackGestureStarted) {
// Let the animation initialized here to make sure the onPointerDownOutsideFocus
// could be happened when ACTION_DOWN, it may change the current focus that we
// would access it when startBackNavigation.
- initAnimation(event);
+ initAnimation(touchX, touchY);
}
- onMove(event, swipeEdge);
- } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ onMove(touchX, touchY, swipeEdge);
+ } else if (keyAction == MotionEvent.ACTION_UP || keyAction == MotionEvent.ACTION_CANCEL) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW,
- "Finishing gesture with event action: %d", action);
+ "Finishing gesture with event action: %d", keyAction);
onGestureFinished();
}
}
- private void initAnimation(MotionEvent event) {
+ private void initAnimation(float touchX, float touchY) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
if (mBackGestureStarted || mBackNavigationInfo != null) {
Log.e(TAG, "Animation is being initialized but is already started.");
finishAnimation();
}
- mInitTouchLocation.set(event.getX(), event.getY());
+ mInitTouchLocation.set(touchX, touchY);
mBackGestureStarted = true;
try {
@@ -351,18 +352,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mTransaction.setVisibility(screenshotSurface, true);
}
- private void onMove(MotionEvent event, @BackEvent.SwipeEdge int swipeEdge) {
+ private void onMove(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
if (!mBackGestureStarted || mBackNavigationInfo == null) {
return;
}
- int deltaX = Math.round(event.getX() - mInitTouchLocation.x);
+ int deltaX = Math.round(touchX - mInitTouchLocation.x);
float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
int backType = mBackNavigationInfo.getType();
RemoteAnimationTarget animationTarget = mBackNavigationInfo.getDepartingAnimationTarget();
BackEvent backEvent = new BackEvent(
- event.getX(), event.getY(), progress, swipeEdge, animationTarget);
+ touchX, touchY, progress, swipeEdge, animationTarget);
IOnBackInvokedCallback targetCallback = null;
if (shouldDispatchToLauncher(backType)) {
targetCallback = mBackToLauncherCallback;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 1e369899e354..a8c1071eb69e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -68,15 +68,15 @@ import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
-import com.android.wm.shell.unfold.qualifier.UnfoldTransition;
import com.android.wm.shell.unfold.qualifier.UnfoldShellTransition;
+import com.android.wm.shell.unfold.qualifier.UnfoldTransition;
import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
@@ -218,6 +218,7 @@ public abstract class WMShellModule {
PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
+ PipTransitionState pipTransitionState,
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
@@ -227,7 +228,7 @@ public abstract class WMShellModule {
return Optional.ofNullable(PipController.create(context, displayController,
pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
pipMotionHelper,
- pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 3b3091a9caf3..bbc47e47afc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -86,12 +86,12 @@ public interface Pip {
}
/**
- * Registers the pinned stack animation listener.
+ * Set the callback when {@link PipTaskOrganizer#isInPip()} state is changed.
*
- * @param callback The callback of pinned stack animation.
+ * @param callback The callback accepts the result of {@link PipTaskOrganizer#isInPip()}
+ * when it's changed.
*/
- default void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
- }
+ default void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {}
/**
* Set the pinned stack with {@link PipAnimationController.AnimationType}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 4eba1697b595..cf2734c375f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -591,7 +591,7 @@ public class PipAnimationController {
final Rect insets = computeInsets(fraction);
getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
sourceHintRect, initialSourceValue, bounds, insets,
- isInPipDirection);
+ isInPipDirection, fraction);
if (shouldApplyCornerRadius()) {
final Rect sourceBounds = new Rect(initialContainerRect);
sourceBounds.inset(insets);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index a017a2674359..c0bc108baada 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -104,7 +104,7 @@ public class PipSurfaceTransactionHelper {
public PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx,
SurfaceControl leash, Rect sourceRectHint,
Rect sourceBounds, Rect destinationBounds, Rect insets,
- boolean isInPipDirection) {
+ boolean isInPipDirection, float fraction) {
mTmpDestinationRect.set(sourceBounds);
// Similar to {@link #scale}, we want to position the surface relative to the screen
// coordinates so offset the bounds to 0,0
@@ -116,9 +116,13 @@ public class PipSurfaceTransactionHelper {
if (isInPipDirection
&& sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) {
// scale by sourceRectHint if it's not edge-to-edge, for entering PiP transition only.
- scale = sourceBounds.width() <= sourceBounds.height()
+ final float endScale = sourceBounds.width() <= sourceBounds.height()
? (float) destinationBounds.width() / sourceRectHint.width()
: (float) destinationBounds.height() / sourceRectHint.height();
+ final float startScale = sourceBounds.width() <= sourceBounds.height()
+ ? (float) destinationBounds.width() / sourceBounds.width()
+ : (float) destinationBounds.height() / sourceBounds.height();
+ scale = (1 - fraction) * startScale + fraction * endScale;
} else {
scale = sourceBounds.width() <= sourceBounds.height()
? (float) destinationBounds.width() / sourceBounds.width()
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 bd386b5681d8..22b0ccbc8488 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
@@ -942,7 +942,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// Re-set the PIP bounds to none.
mPipBoundsState.setBounds(new Rect());
mPipUiEventLoggerLogger.setTaskInfo(null);
- mPipMenuController.detach();
+ mMainExecutor.executeDelayed(() -> mPipMenuController.detach(), 0);
if (info.displayId != Display.DEFAULT_DISPLAY && mOnDisplayIdChangeCallback != null) {
mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
@@ -1472,6 +1472,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
"%s: Abort animation, invalid leash", TAG);
return null;
}
+ if (isInPipDirection(direction)
+ && !isSourceRectHintValidForEnterPip(sourceHintRect, destinationBounds)) {
+ // The given source rect hint is too small for enter PiP animation, reset it to null.
+ sourceHintRect = null;
+ }
final int rotationDelta = mWaitForFixedRotation
? deltaRotation(mCurrentRotation, mNextRotation)
: Surface.ROTATION_0;
@@ -1546,6 +1551,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
/**
+ * This is a situation in which the source rect hint on at least one axis is smaller
+ * than the destination bounds, which represents a problem because we would have to scale
+ * up that axis to fit the bounds. So instead, just fallback to the non-source hint
+ * animation in this case.
+ *
+ * @return {@code false} if the given source is too small to use for the entering animation.
+ */
+ private boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) {
+ return sourceRectHint != null
+ && sourceRectHint.width() > destinationBounds.width()
+ && sourceRectHint.height() > destinationBounds.height();
+ }
+
+ /**
* Sync with {@link SplitScreenController} on destination bounds if PiP is going to
* split screen.
*
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 28427a808d90..05a890fc65ed 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
@@ -42,6 +42,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SP
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
import static com.android.wm.shell.transition.Transitions.isOpeningType;
+import android.animation.Animator;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
@@ -248,6 +249,13 @@ public class PipTransition extends PipTransitionController {
return false;
}
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ end();
+ }
+
/** Helper to identify whether this handler is currently the one playing an animation */
private boolean isAnimatingLocally() {
return mFinishTransaction != null;
@@ -283,6 +291,13 @@ public class PipTransition extends PipTransitionController {
}
@Override
+ public void end() {
+ Animator animator = mPipAnimationController.getCurrentAnimator();
+ if (animator == null) return;
+ animator.end();
+ }
+
+ @Override
public boolean handleRotateDisplay(int startRotation, int endRotation,
WindowContainerTransaction wct) {
if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) {
@@ -700,7 +715,7 @@ public class PipTransition extends PipTransitionController {
mSurfaceTransactionHelper
.crop(finishTransaction, leash, destinationBounds)
.round(finishTransaction, leash, true /* applyCornerRadius */);
- mPipMenuController.attach(leash);
+ mTransitions.getMainExecutor().executeDelayed(() -> mPipMenuController.attach(leash), 0);
if (taskInfo.pictureInPictureParams != null
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
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 d3f69f6762f9..90a2695bdf90 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
@@ -28,9 +28,7 @@ import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -56,7 +54,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH
protected final ShellTaskOrganizer mShellTaskOrganizer;
protected final PipMenuController mPipMenuController;
protected final Transitions mTransitions;
- private final Handler mMainHandler;
private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
protected PipTaskOrganizer mPipOrganizer;
@@ -144,7 +141,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipAnimationController = pipAnimationController;
mTransitions = transitions;
- mMainHandler = new Handler(Looper.getMainLooper());
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.addHandler(this);
}
@@ -237,6 +233,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH
@NonNull final Transitions.TransitionFinishCallback finishCallback) {
}
+ /** End the currently-playing PiP animation. */
+ public void end() {
+ }
+
/**
* Callback interface for PiP transitions (both from and to PiP mode)
*/
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 85e56b7dd99f..1a4be3b41911 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
@@ -17,12 +17,15 @@
package com.android.wm.shell.pip;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* Used to keep track of PiP leash state as it appears and animates by {@link PipTaskOrganizer} and
@@ -37,6 +40,9 @@ public class PipTransitionState {
public static final int ENTERED_PIP = 4;
public static final int EXITING_PIP = 5;
+ private final List<OnPipTransitionStateChangedListener> mOnPipTransitionStateChangedListeners =
+ new ArrayList<>();
+
/**
* If set to {@code true}, no entering PiP transition would be kicked off and most likely
* it's due to the fact that Launcher is handling the transition directly when swiping
@@ -65,7 +71,13 @@ public class PipTransitionState {
}
public void setTransitionState(@TransitionState int state) {
- mState = state;
+ if (mState != state) {
+ for (int i = 0; i < mOnPipTransitionStateChangedListeners.size(); i++) {
+ mOnPipTransitionStateChangedListeners.get(i).onPipTransitionStateChanged(
+ mState, state);
+ }
+ mState = state;
+ }
}
public @TransitionState int getTransitionState() {
@@ -73,8 +85,7 @@ public class PipTransitionState {
}
public boolean isInPip() {
- return mState >= TASK_APPEARED
- && mState != EXITING_PIP;
+ return isInPip(mState);
}
public void setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition) {
@@ -94,4 +105,23 @@ public class PipTransitionState {
return mState < ENTERING_PIP
|| mState == EXITING_PIP;
}
+
+ public void addOnPipTransitionStateChangedListener(
+ @NonNull OnPipTransitionStateChangedListener listener) {
+ mOnPipTransitionStateChangedListeners.add(listener);
+ }
+
+ public void removeOnPipTransitionStateChangedListener(
+ @NonNull OnPipTransitionStateChangedListener listener) {
+ mOnPipTransitionStateChangedListeners.remove(listener);
+ }
+
+ public static boolean isInPip(@TransitionState int state) {
+ return state >= TASK_APPEARED && state != EXITING_PIP;
+ }
+
+ public interface OnPipTransitionStateChangedListener {
+ void onPipTransitionStateChanged(@TransitionState int oldState,
+ @TransitionState int newState);
+ }
}
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 c3e6d82df781..3000998f210d 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
@@ -84,6 +84,7 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.Transitions;
@@ -128,11 +129,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb
protected PhonePipMenuController mMenuController;
protected PipTaskOrganizer mPipTaskOrganizer;
+ private PipTransitionState mPipTransitionState;
protected PinnedStackListenerForwarder.PinnedTaskListener mPinnedTaskListener =
new PipControllerPinnedTaskListener();
private boolean mIsKeyguardShowingOrAnimating;
+ private Consumer<Boolean> mOnIsInPipStateChangedListener;
+
private interface PipAnimationListener {
/**
* Notifies the listener that the Pip animation is started.
@@ -291,6 +295,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
+ PipTransitionState pipTransitionState,
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
@@ -305,7 +310,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
- phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
+ phonePipMenuController, pipTaskOrganizer, pipTransitionState,
+ pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
oneHandedController, mainExecutor)
.mImpl;
@@ -321,6 +327,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
PipTaskOrganizer pipTaskOrganizer,
+ PipTransitionState pipTransitionState,
PipTouchHandler pipTouchHandler,
PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -344,6 +351,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState = pipBoundsState;
mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
+ mPipTransitionState = pipTransitionState;
mMainExecutor = mainExecutor;
mMediaController = pipMediaController;
mMenuController = phonePipMenuController;
@@ -370,6 +378,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
false /* saveRestoreSnapFraction */);
});
+ mPipTransitionState.addOnPipTransitionStateChangedListener((oldState, newState) -> {
+ if (mOnIsInPipStateChangedListener != null) {
+ final boolean wasInPip = PipTransitionState.isInPip(oldState);
+ final boolean nowInPip = PipTransitionState.isInPip(newState);
+ if (nowInPip != wasInPip) {
+ mOnIsInPipStateChangedListener.accept(nowInPip);
+ }
+ }
+ });
mPipBoundsState.setOnMinimalSizeChangeCallback(
() -> {
// The minimal size drives the normal bounds, so they need to be recalculated.
@@ -664,6 +681,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
}
+ private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ mOnIsInPipStateChangedListener = callback;
+ if (mOnIsInPipStateChangedListener != null) {
+ callback.accept(mPipTransitionState.isInPip());
+ }
+ }
+
private void setShelfHeightLocked(boolean visible, int height) {
final int shelfHeight = visible ? height : 0;
mPipBoundsState.setShelfVisibility(visible, shelfHeight);
@@ -941,6 +965,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
+ public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ mMainExecutor.execute(() -> {
+ PipController.this.setOnIsInPipStateChangedListener(callback);
+ });
+ }
+
+ @Override
public void setPinnedStackAnimationType(int animationType) {
mMainExecutor.execute(() -> {
PipController.this.setPinnedStackAnimationType(animationType);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index f7057d454df9..e55729a883e0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -225,9 +225,25 @@ class SplitScreenTransitions {
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
- if (mergeTarget == mAnimatingTransition && mActiveRemoteHandler != null) {
+ if (mergeTarget != mAnimatingTransition) return;
+ if (mActiveRemoteHandler != null) {
mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ } else {
+ for (int i = mAnimations.size() - 1; i >= 0; --i) {
+ final Animator anim = mAnimations.get(i);
+ mTransitions.getAnimExecutor().execute(anim::end);
+ }
+ }
+ }
+
+ boolean end() {
+ // If its remote, there's nothing we can do right now.
+ if (mActiveRemoteHandler != null) return false;
+ for (int i = mAnimations.size() - 1; i >= 0; --i) {
+ final Animator anim = mAnimations.get(i);
+ mTransitions.getAnimExecutor().execute(anim::end);
}
+ return true;
}
void onTransitionMerged(@NonNull IBinder transition) {
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 6cfb700fc16a..59b0afe22acb 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
@@ -457,10 +457,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
onRemoteAnimationFinishedOrCancelled(evictWct);
try {
- adapter.getRunner().onAnimationCancelled();
+ adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
} catch (RemoteException e) {
Slog.e(TAG, "Error starting remote animation", e);
}
@@ -1521,6 +1521,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
}
+ /** Jump the current transition animation to the end. */
+ public boolean end() {
+ return mSplitTransitions.end();
+ }
+
@Override
public void onTransitionMerged(@NonNull IBinder transition) {
mSplitTransitions.onTransitionMerged(transition);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 95bc579a4a51..19d3acbf28d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -20,10 +20,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.graphics.Color.WHITE;
import static android.graphics.Color.alpha;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.ViewRootImpl.LOCAL_LAYOUT;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
@@ -53,7 +51,6 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityThread;
-import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -80,7 +77,6 @@ import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
-import android.view.WindowLayout;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.ClientWindowFrames;
@@ -212,8 +208,6 @@ public class TaskSnapshotWindow {
final IWindowSession session = WindowManagerGlobal.getWindowSession();
final SurfaceControl surfaceControl = new SurfaceControl();
final ClientWindowFrames tmpFrames = new ClientWindowFrames();
- final WindowLayout windowLayout = new WindowLayout();
- final Rect displayCutoutSafe = new Rect();
final InsetsSourceControl[] tmpControls = new InsetsSourceControl[0];
final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
@@ -238,7 +232,8 @@ public class TaskSnapshotWindow {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay");
final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
- info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls);
+ info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls,
+ new Rect());
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (res < 0) {
Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
@@ -250,25 +245,9 @@ public class TaskSnapshotWindow {
window.setOuter(snapshotSurface);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
- if (LOCAL_LAYOUT) {
- if (!surfaceControl.isValid()) {
- session.updateVisibility(window, layoutParams, View.VISIBLE,
- tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls);
- }
- tmpInsetsState.getDisplayCutoutSafe(displayCutoutSafe);
- final WindowConfiguration winConfig =
- tmpMergedConfiguration.getMergedConfiguration().windowConfiguration;
- windowLayout.computeFrames(layoutParams, tmpInsetsState, displayCutoutSafe,
- winConfig.getBounds(), winConfig.getWindowingMode(), UNSPECIFIED_LENGTH,
- UNSPECIFIED_LENGTH, info.requestedVisibilities,
- null /* attachedWindowFrame */, 1f /* compatScale */, tmpFrames);
- session.updateLayout(window, layoutParams, 0 /* flags */, tmpFrames,
- UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH);
- } else {
- session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
- tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
- tmpControls, new Bundle());
- }
+ session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
+ tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
+ tmpControls, new Bundle());
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
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 1ffe26df729f..7234d559e153 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
@@ -53,10 +53,18 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
private static class MixedTransition {
static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
+ /** The default animation for this mixed transition. */
+ static final int ANIM_TYPE_DEFAULT = 0;
+
+ /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */
+ static final int ANIM_TYPE_GOING_HOME = 1;
+
final int mType;
+ int mAnimType = 0;
final IBinder mTransition;
Transitions.TransitionFinishCallback mFinishCallback = null;
+ Transitions.TransitionHandler mLeftoversHandler = null;
/**
* Mixed transitions are made up of multiple "parts". This keeps track of how many
@@ -128,7 +136,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
MixedTransition mixed = null;
for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
if (mActiveTransitions.get(i).mTransition != transition) continue;
- mixed = mActiveTransitions.remove(i);
+ mixed = mActiveTransitions.get(i);
break;
}
if (mixed == null) return false;
@@ -137,6 +145,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
finishCallback);
} else {
+ mActiveTransitions.remove(mixed);
throw new IllegalStateException("Starting mixed animation without a known mixed type? "
+ mixed.mType);
}
@@ -178,6 +187,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
--mixed.mInFlightSubAnimations;
if (mixed.mInFlightSubAnimations > 0) return;
+ mActiveTransitions.remove(mixed);
if (isGoingHome) {
mSplitHandler.onTransitionAnimationComplete();
}
@@ -216,8 +226,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
finishCB);
// Dispatch the rest of the transition normally. This will most-likely be taken by
// recents or default handler.
- mPlayer.dispatchTransition(mixed.mTransition, everythingElse, otherStartT,
- finishTransaction, finishCB, this);
+ mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, everythingElse,
+ otherStartT, finishTransaction, finishCB, this);
} else {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just "
+ "forward animation to Pip-Handler.");
@@ -235,6 +245,32 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ for (int i = 0; i < mActiveTransitions.size(); ++i) {
+ if (mActiveTransitions.get(i) != mergeTarget) continue;
+ MixedTransition mixed = mActiveTransitions.get(i);
+ if (mixed.mInFlightSubAnimations <= 0) {
+ // Already done, so no need to end it.
+ return;
+ }
+ if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
+ if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) {
+ boolean ended = mSplitHandler.end();
+ // If split couldn't end (because it is remote), then don't end everything else
+ // since we have to play out the animation anyways.
+ if (!ended) return;
+ mPipHandler.end();
+ if (mixed.mLeftoversHandler != null) {
+ mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
+ }
+ } else {
+ mPipHandler.end();
+ }
+ } else {
+ throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ + mixed.mType);
+ }
+ }
}
@Override
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 c3eaa8ee1da0..dcd6277966dd 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
@@ -523,6 +523,18 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return true;
}
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ArrayList<Animator> anims = mAnimations.get(mergeTarget);
+ if (anims == null) return;
+ for (int i = anims.size() - 1; i >= 0; --i) {
+ final Animator anim = anims.get(i);
+ mAnimExecutor.execute(anim::end);
+ }
+ }
+
private void edgeExtendWindow(TransitionInfo.Change change,
Animation a, SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction) {
@@ -854,13 +866,19 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
});
};
va.addListener(new AnimatorListenerAdapter() {
+ private boolean mFinished = false;
+
@Override
public void onAnimationEnd(Animator animation) {
+ if (mFinished) return;
+ mFinished = true;
finisher.run();
}
@Override
public void onAnimationCancel(Animator animation) {
+ if (mFinished) return;
+ mFinished = true;
finisher.run();
}
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
index 61e11e877b90..61e92f355dc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
@@ -107,7 +107,7 @@ public class LegacyTransitions {
}
@Override
- public void onAnimationCancelled() throws RemoteException {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
mCancelled = true;
mApps = mWallpapers = mNonApps = null;
checkApply();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index d4298b8f29a0..49eca63a23ec 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -25,11 +25,13 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.testapp.Components
+import org.junit.Assert
class SplitScreenHelper(
instrumentation: Instrumentation,
@@ -187,5 +189,23 @@ class SplitScreenHelper(
SystemClock.sleep(GESTURE_STEP_MS)
}
}
+
+ fun createShortcutOnHotseatIfNotExist(
+ taplInstrumentation: LauncherInstrumentation,
+ appName: String
+ ) {
+ taplInstrumentation.workspace
+ .deleteAppIcon(taplInstrumentation.workspace.getHotseatAppIcon(0))
+ val allApps = taplInstrumentation.workspace.switchToAllApps()
+ allApps.freeze()
+ try {
+ val appIconSrc = allApps.getAppIcon(appName)
+ Assert.assertNotNull("Unable to find app icon", appIconSrc)
+ val appIconDest = appIconSrc.dragToHotseat(0)
+ Assert.assertNotNull("Unable to drag app icon on hotseat", appIconDest)
+ } finally {
+ allApps.unfreeze()
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
new file mode 100644
index 000000000000..05c6e24ee89d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen by dragging app icon from taskbar.
+ * This test is only for large screen devices.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class EnterSplitScreenByDragFromTaskbar(
+ testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(taplInstrumentation.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ taplInstrumentation.goHome()
+ SplitScreenHelper.createShortcutOnHotseatIfNotExist(
+ taplInstrumentation, secondaryApp.appName
+ )
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ }
+ transitions {
+ taplInstrumentation.launchedAppState.taskbar
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(
+ secondaryApp.component.packageName,
+ primaryApp.component.packageName
+ )
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp.component)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ testSpec.endRotation, primaryApp.component, false /* splitLeftTop */
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+ testSpec.endRotation, secondaryApp.component, true /* splitLeftTop */
+ )
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() =
+ testSpec.appWindowBecomesVisible(secondaryApp.component)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index fcfcbfa091db..e7c5cb2183db 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -298,7 +298,7 @@ public class BackAnimationControllerTest {
private void doMotionEvent(int actionDown, int coordinate) {
mController.onMotionEvent(
- MotionEvent.obtain(0, mEventTime, actionDown, coordinate, coordinate, 0),
+ coordinate, coordinate,
actionDown,
BackEvent.EDGE_LEFT);
mEventTime += 10;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index abd55dd7d606..babc9707ef9c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -53,6 +53,7 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
import org.junit.Before;
import org.junit.Test;
@@ -80,6 +81,7 @@ public class PipControllerTest extends ShellTestCase {
@Mock private PipSnapAlgorithm mMockPipSnapAlgorithm;
@Mock private PipMediaController mMockPipMediaController;
@Mock private PipTaskOrganizer mMockPipTaskOrganizer;
+ @Mock private PipTransitionState mMockPipTransitionState;
@Mock private PipTransitionController mMockPipTransitionController;
@Mock private PipTouchHandler mMockPipTouchHandler;
@Mock private PipMotionHelper mMockPipMotionHelper;
@@ -104,8 +106,8 @@ public class PipControllerTest extends ShellTestCase {
mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
- mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
- mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
+ mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mPipParamsChangedForwarder,
mMockOneHandedController, mMockExecutor);
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
@@ -138,8 +140,8 @@ public class PipControllerTest extends ShellTestCase {
mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
- mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
- mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
+ mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mPipParamsChangedForwarder,
mMockOneHandedController, mMockExecutor));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 630d0d2c827c..14d8ce4682c6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -249,7 +249,8 @@ public class StartingSurfaceDrawerTests {
any() /* window */, any() /* attrs */,
anyInt() /* viewVisibility */, anyInt() /* displayId */,
any() /* requestedVisibility */, any() /* outInputChannel */,
- any() /* outInsetsState */, any() /* outActiveControls */);
+ any() /* outInsetsState */, any() /* outActiveControls */,
+ any() /* outAttachedFrame */);
TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo,
mBinder,
snapshot, mTestExecutor, () -> {
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
index b136d5bc4db3..2bdd5c8bc977 100644
--- a/media/java/android/media/projection/IMediaProjection.aidl
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -17,7 +17,7 @@
package android.media.projection;
import android.media.projection.IMediaProjectionCallback;
-import android.window.WindowContainerToken;
+import android.os.IBinder;
/** {@hide} */
interface IMediaProjection {
@@ -31,14 +31,14 @@ interface IMediaProjection {
void unregisterCallback(IMediaProjectionCallback callback);
/**
- * Returns the {@link android.window.WindowContainerToken} identifying the task to record, or
- * {@code null} if there is none.
+ * Returns the {@link android.os.IBinder} identifying the task to record, or {@code null} if
+ * there is none.
*/
- WindowContainerToken getTaskRecordingWindowContainerToken();
+ IBinder getLaunchCookie();
/**
- * Updates the {@link android.window.WindowContainerToken} identifying the task to record, or
- * {@code null} if there is none.
+ * Updates the {@link android.os.IBinder} identifying the task to record, or {@code null} if
+ * there is none.
*/
- void setTaskRecordingWindowContainerToken(in WindowContainerToken token);
+ void setLaunchCookie(in IBinder launchCookie);
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index ba7bf3f5f5d3..ae44fc575f7c 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -25,13 +25,13 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.Log;
import android.view.ContentRecordingSession;
import android.view.Surface;
-import android.window.WindowContainerToken;
import java.util.Map;
@@ -172,18 +172,16 @@ public final class MediaProjection {
@NonNull VirtualDisplayConfig.Builder virtualDisplayConfig,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
try {
- final WindowContainerToken taskWindowContainerToken =
- mImpl.getTaskRecordingWindowContainerToken();
+ final IBinder launchCookie = mImpl.getLaunchCookie();
Context windowContext = null;
ContentRecordingSession session;
- if (taskWindowContainerToken == null) {
+ if (launchCookie == null) {
windowContext = mContext.createWindowContext(mContext.getDisplayNoVerify(),
TYPE_APPLICATION, null /* options */);
session = ContentRecordingSession.createDisplaySession(
windowContext.getWindowContextToken());
} else {
- session = ContentRecordingSession.createTaskSession(
- taskWindowContainerToken.asBinder());
+ session = ContentRecordingSession.createTaskSession(launchCookie);
}
virtualDisplayConfig.setWindowManagerMirroring(true);
final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 89e10c4b5e11..fc70ba40fb47 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -20,15 +20,19 @@ import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
+import android.os.Build;
import android.os.ParcelUuid;
import android.util.Log;
+import androidx.annotation.ChecksSdkIntAtLeast;
+
import com.android.internal.annotations.VisibleForTesting;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* CsipDeviceManager manages the set of remote CSIP Bluetooth devices.
@@ -126,32 +130,84 @@ public class CsipDeviceManager {
}
}
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
+ private static boolean isAtLeastT() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
+ }
+
// Group devices by groupId
@VisibleForTesting
void onGroupIdChanged(int groupId) {
- int firstMatchedIndex = -1;
- CachedBluetoothDevice mainDevice = null;
+ if (!isValidGroupId(groupId)) {
+ log("onGroupIdChanged: groupId is invalid");
+ return;
+ }
+ log("onGroupIdChanged: mCachedDevices list =" + mCachedDevices.toString());
+ final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+ final CachedBluetoothDeviceManager deviceManager = mBtManager.getCachedDeviceManager();
+ final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile();
+ final BluetoothDevice mainBluetoothDevice = (leAudioProfile != null && isAtLeastT()) ?
+ leAudioProfile.getConnectedGroupLeadDevice(groupId) : null;
+ CachedBluetoothDevice newMainDevice =
+ mainBluetoothDevice != null ? deviceManager.findDevice(mainBluetoothDevice) : null;
+ if (newMainDevice != null) {
+ final CachedBluetoothDevice finalNewMainDevice = newMainDevice;
+ final List<CachedBluetoothDevice> memberDevices = mCachedDevices.stream()
+ .filter(cachedDevice -> !cachedDevice.equals(finalNewMainDevice)
+ && cachedDevice.getGroupId() == groupId)
+ .collect(Collectors.toList());
+ if (memberDevices == null || memberDevices.isEmpty()) {
+ log("onGroupIdChanged: There is no member device in list.");
+ return;
+ }
+ log("onGroupIdChanged: removed from UI device =" + memberDevices
+ + ", with groupId=" + groupId + " mainDevice= " + newMainDevice);
+ for (CachedBluetoothDevice memberDeviceItem : memberDevices) {
+ Set<CachedBluetoothDevice> memberSet = memberDeviceItem.getMemberDevice();
+ if (!memberSet.isEmpty()) {
+ log("onGroupIdChanged: Transfer the member list into new main device.");
+ for (CachedBluetoothDevice memberListItem : memberSet) {
+ if (!memberListItem.equals(newMainDevice)) {
+ newMainDevice.addMemberDevice(memberListItem);
+ }
+ }
+ memberSet.clear();
+ }
- for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
- final CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
- if (cachedDevice.getGroupId() != groupId) {
- continue;
+ newMainDevice.addMemberDevice(memberDeviceItem);
+ mCachedDevices.remove(memberDeviceItem);
+ mBtManager.getEventManager().dispatchDeviceRemoved(memberDeviceItem);
}
- if (firstMatchedIndex == -1) {
- // Found the first one
- firstMatchedIndex = i;
- mainDevice = cachedDevice;
- continue;
+ if (!mCachedDevices.contains(newMainDevice)) {
+ mCachedDevices.add(newMainDevice);
+ mBtManager.getEventManager().dispatchDeviceAdded(newMainDevice);
}
+ } else {
+ log("onGroupIdChanged: There is no main device from the LE profile.");
+ int firstMatchedIndex = -1;
- log("onGroupIdChanged: removed from UI device =" + cachedDevice
- + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex);
+ for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+ final CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+ if (cachedDevice.getGroupId() != groupId) {
+ continue;
+ }
+
+ if (firstMatchedIndex == -1) {
+ // Found the first one
+ firstMatchedIndex = i;
+ newMainDevice = cachedDevice;
+ continue;
+ }
+
+ log("onGroupIdChanged: removed from UI device =" + cachedDevice
+ + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex);
- mainDevice.addMemberDevice(cachedDevice);
- mCachedDevices.remove(i);
- mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
- break;
+ newMainDevice.addMemberDevice(cachedDevice);
+ mCachedDevices.remove(i);
+ mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
+ break;
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 19df1e9c0730..0f57d8785de9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -21,6 +21,7 @@ import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_ALL;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCodecConfig;
@@ -183,6 +184,37 @@ public class LeAudioProfile implements LocalBluetoothProfile {
return mBluetoothAdapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
}
+ /**
+ * Get Lead device for the group.
+ *
+ * Lead device is the device that can be used as an active device in the system.
+ * Active devices points to the Audio Device for the Le Audio group.
+ * This method returns the Lead devices for the connected LE Audio
+ * group and this device should be used in the setActiveDevice() method by other parts
+ * of the system, which wants to set to active a particular Le Audio group.
+ *
+ * Note: getActiveDevice() returns the Lead device for the currently active LE Audio group.
+ * Note: When Lead device gets disconnected while Le Audio group is active and has more devices
+ * in the group, then Lead device will not change. If Lead device gets disconnected, for the
+ * Le Audio group which is not active, a new Lead device will be chosen
+ *
+ * @param groupId The group id.
+ * @return group lead device.
+ *
+ * @hide
+ */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ public @Nullable BluetoothDevice getConnectedGroupLeadDevice(int groupId) {
+ if (DEBUG) {
+ Log.d(TAG,"getConnectedGroupLeadDevice");
+ }
+ if (mService == null) {
+ Log.e(TAG,"No service.");
+ return null;
+ }
+ return mService.getConnectedGroupLeadDevice(groupId);
+ }
+
@Override
public boolean isEnabled(BluetoothDevice device) {
if (mService == null || device == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
index e8cbab8197b2..a040e28169e8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
@@ -32,7 +32,7 @@ public abstract class MediaManager {
private static final String TAG = "MediaManager";
protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
- protected final List<MediaDevice> mMediaDevices = new ArrayList<>();
+ protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
protected Context mContext;
protected Notification mNotification;
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f05c1e2e76f2..fa87de2b1353 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -93,6 +93,7 @@ android_library {
"SystemUISharedLib",
"SystemUI-statsd",
"SettingsLib",
+ "androidx.core_core-ktx",
"androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
"androidx.recyclerview_recyclerview",
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 3bd6d51eecc6..f9a9ef65cab6 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -52,6 +52,17 @@
]
},
{
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
// Permission indicators
"name": "CtsPermission4TestCases",
"options": [
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 5b47ae525919..fbe33565f11d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -656,7 +656,7 @@ class ActivityLaunchAnimator(
controller.onLaunchAnimationCancelled()
}
- override fun onAnimationCancelled() {
+ override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {
if (timedOut) {
return
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index 0f10589dfcf9..bd628ccb3c08 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -29,7 +29,7 @@ import java.lang.annotation.RetentionPolicy;
/**
* Interface that decides whether a touch on the phone was accidental. i.e. Pocket Dialing.
*
- * {@see com.android.systemui.classifier.FalsingManagerImpl}
+ * {@see com.android.systemui.classifier.BrightLineFalsingManager}
*/
@ProvidesInterface(version = FalsingManager.VERSION)
public interface FalsingManager {
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index eba003fd6bd1..b1f5a3f9206f 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -84,7 +84,7 @@
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"L\'administrador ha bloquejat el dispositiu"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositiu s\'ha bloquejat manualment"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"No s\'ha reconegut"</string>
- <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"Per utilitzar Desbloqueig facial, activa l\'accés a la càmera a Configuració"</string>
+ <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"Desbloqueig facial necessita accés a la càmera"</string>
<string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Introdueix el PIN de la SIM. Et queda # intent; si no l\'encertes, contacta amb l\'operador per desbloquejar el dispositiu.}other{Introdueix el PIN de la SIM. Et queden # intents.}}"</string>
<string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{La targeta SIM s\'ha desactivat. Introdueix el codi PUK per continuar. Et queda # intent; si no l\'encertes, la SIM no es podrà tornar a fer servir. Contacta amb l\'operador per obtenir informació.}other{La targeta SIM s\'ha desactivat. Introdueix el codi PUK per continuar. Et queden # intents; si no l\'encertes, la SIM no es podrà tornar a fer servir. Contacta amb l\'operador per obtenir informació.}}"</string>
<string name="clock_title_default" msgid="6342735240617459864">"Predeterminada"</string>
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index 05039d8a363b..9b4df35f1754 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -55,10 +55,10 @@
<string name="kg_wrong_pin" msgid="4160978845968732624">"Wrong PIN"</string>
<string name="kg_too_many_failed_attempts_countdown" msgid="2038195171919795529">"{count,plural, =1{Try again in # second.}other{Try again in # seconds.}}"</string>
<string name="kg_sim_pin_instructions" msgid="1942424305184242951">"Enter SIM PIN."</string>
- <string name="kg_sim_pin_instructions_multi" msgid="3639863309953109649">"Enter SIM PIN for \'<xliff:g id="CARRIER">%1$s</xliff:g>\'."</string>
+ <string name="kg_sim_pin_instructions_multi" msgid="3639863309953109649">"Enter SIM PIN for \"<xliff:g id="CARRIER">%1$s</xliff:g>\"."</string>
<string name="kg_sim_lock_esim_instructions" msgid="5577169988158738030">"<xliff:g id="PREVIOUS_MSG">%1$s</xliff:g> Disable eSIM to use device without mobile service."</string>
- <string name="kg_puk_enter_puk_hint" msgid="3005288372875367017">"SIM is now disabled. Enter PUK code to continue. Contact operator for details."</string>
- <string name="kg_puk_enter_puk_hint_multi" msgid="4876780689904862943">"SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\" is now disabled. Enter PUK code to continue. Contact operator for details."</string>
+ <string name="kg_puk_enter_puk_hint" msgid="3005288372875367017">"SIM is now disabled. Enter PUK code to continue. Contact carrier for details."</string>
+ <string name="kg_puk_enter_puk_hint_multi" msgid="4876780689904862943">"SIM \"<xliff:g id="CARRIER">%1$s</xliff:g>\" is now disabled. Enter PUK code to continue. Contact carrier for details."</string>
<string name="kg_puk_enter_pin_hint" msgid="6028432138916150399">"Enter desired PIN code"</string>
<string name="kg_enter_confirm_pin_hint" msgid="4261064020391799132">"Confirm desired PIN code"</string>
<string name="kg_sim_unlock_progress_dialog_message" msgid="4251352015304070326">"Unlocking SIM card…"</string>
@@ -67,9 +67,9 @@
<string name="kg_too_many_failed_pin_attempts_dialog_message" msgid="544687656831558971">"You have incorrectly typed your PIN <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="kg_too_many_failed_password_attempts_dialog_message" msgid="190984061975729494">"You have incorrectly typed your password <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
<string name="kg_too_many_failed_pattern_attempts_dialog_message" msgid="4252405904570284368">"You have incorrectly drawn your unlock pattern <xliff:g id="NUMBER_0">%1$d</xliff:g> times. \n\nTry again in <xliff:g id="NUMBER_1">%2$d</xliff:g> seconds."</string>
- <string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"Incorrect SIM PIN code; you must now contact your operator to unlock your device."</string>
- <string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{Incorrect SIM PIN code; you have # remaining attempt before you must contact your operator to unlock your device.}other{Incorrect SIM PIN code; you have # remaining attempts. }}"</string>
- <string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"SIM is unusable. Contact your operator."</string>
+ <string name="kg_password_wrong_pin_code_pukked" msgid="8047350661459040581">"Incorrect SIM PIN code you must now contact your carrier to unlock your device."</string>
+ <string name="kg_password_wrong_pin_code" msgid="5629415765976820357">"{count,plural, =1{Incorrect SIM PIN code, you have # remaining attempt before you must contact your carrier to unlock your device.}other{Incorrect SIM PIN code, you have # remaining attempts. }}"</string>
+ <string name="kg_password_wrong_puk_code_dead" msgid="3698285357028468617">"SIM is unusable. Contact your carrier."</string>
<string name="kg_password_wrong_puk_code" msgid="6820515467645087827">"{count,plural, =1{Incorrect SIM PUK code; you have # remaining attempt before SIM becomes permanently unusable.}other{Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}}"</string>
<string name="kg_password_pin_failed" msgid="5136259126330604009">"SIM PIN operation failed!"</string>
<string name="kg_password_puk_failed" msgid="6778867411556937118">"SIM PUK operation failed!"</string>
@@ -85,8 +85,8 @@
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
<string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"To use Face Unlock, turn on camera access in Settings"</string>
- <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Enter SIM PIN. You have # remaining attempt before you must contact your operator to unlock your device.}other{Enter SIM PIN. You have # remaining attempts.}}"</string>
- <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{SIM is now disabled. Enter PUK code to continue. You have # remaining attempt before SIM becomes permanently unusable. Contact operator for details.}other{SIM is now disabled. Enter PUK code to continue. You have # remaining attempts before SIM becomes permanently unusable. Contact operator for details.}}"</string>
+ <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Enter SIM PIN. You have # remaining attempt before you must contact your carrier to unlock your device.}other{Enter SIM PIN. You have # remaining attempts.}}"</string>
+ <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{SIM is now disabled. Enter PUK code to continue. You have # remaining attempt before SIM becomes permanently unusable. Contact carrier for details.}other{SIM is now disabled. Enter PUK code to continue. You have # remaining attempts before SIM becomes permanently unusable. Contact carrier for details.}}"</string>
<string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
<string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
<string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index 0932e93003f1..bf9491572cf8 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -84,7 +84,7 @@
<string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratzaileak blokeatu egin du gailua"</string>
<string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Eskuz blokeatu da gailua"</string>
<string name="kg_face_not_recognized" msgid="7903950626744419160">"Ez da ezagutu"</string>
- <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"Aurpegi bidez desblokeatzeko eginbidea erabiltzeko, eman kamera atzitzeko baimena ezarpenetan"</string>
+ <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"Aurpegi bidezko desblokeoak kamera atzitzeko baimena behar du"</string>
<string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{Idatzi SIMaren PINa. # saiakera geratzen zaizu gailua desblokeatzeko operadorearekin harremanetan jarri behar izan aurretik.}other{Idatzi SIMaren PINa. # saiakera gelditzen zaizkizu.}}"</string>
<string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{Orain, SIMa desgaituta dago. Aurrera egiteko, idatzi PUK kodea. # saiakera geratzen zaizu SIMa betiko ez-erabilgarri geratu aurretik. Xehetasunak lortzeko, jarri operadorearekin harremanetan.}other{Orain, SIMa desgaituta dago. Aurrera egiteko, idatzi PUK kodea. # saiakera geratzen zaizkizu SIMa betiko ez-erabilgarri geratu aurretik. Xehetasunak lortzeko, jarri operadorearekin harremanetan.}}"</string>
<string name="clock_title_default" msgid="6342735240617459864">"Lehenetsia"</string>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index cecc645bcd8a..b6ae96d1dea7 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -22,7 +22,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="keyguard_enter_your_pin" msgid="5429932527814874032">"మీ పిన్‌ని నమోదు చేయండి"</string>
<string name="keyguard_enter_your_pattern" msgid="351503370332324745">"మీ నమూనాను నమోదు చేయండి"</string>
- <string name="keyguard_enter_your_password" msgid="7225626204122735501">"మీ పాస్‌వర్డ్‌ను నమోదు చేయండి"</string>
+ <string name="keyguard_enter_your_password" msgid="7225626204122735501">"మీ పాస్‌వర్డ్‌ను ఎంటర్ చేయండి"</string>
<string name="keyguard_sim_error_message_short" msgid="633630844240494070">"చెల్లని కార్డ్."</string>
<string name="keyguard_charged" msgid="5478247181205188995">"ఛార్జ్ చేయబడింది"</string>
<string name="keyguard_plugged_in_wireless" msgid="2537874724955057383">"<xliff:g id="PERCENTAGE">%s</xliff:g> • వైర్‌ లేకుండా ఛార్జ్ అవుతోంది"</string>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 77f1803523a8..acf3e4dcf02a 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -102,12 +102,13 @@
screen. -->
<item name="half_opened_bouncer_height_ratio" type="dimen" format="float">0.0</item>
- <!-- The actual amount of translation that is applied to the bouncer when it animates from one
- side of the screen to the other in one-handed mode. Note that it will always translate from
- the side of the screen to the other (it will "jump" closer to the destination while the
- opacity is zero), but this controls how much motion will actually be applied to it while
- animating. Larger values will cause it to move "faster" while fading out/in. -->
- <dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen>
+ <!-- The actual amount of translation that is applied to the security when it animates from one
+ side of the screen to the other in one-handed or user switcher mode. Note that it will
+ always translate from the side of the screen to the other (it will "jump" closer to the
+ destination while the opacity is zero), but this controls how much motion will actually be
+ applied to it while animating. Larger values will cause it to move "faster" while
+ fading out/in. -->
+ <dimen name="security_shift_animation_translation">120dp</dimen>
<dimen name="bouncer_user_switcher_header_text_size">20sp</dimen>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
index 3f56bafef134..efbdd1af3644 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
@@ -20,5 +20,5 @@
style="@style/clock_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:format12Hour="EEE, MMM d"
- android:format24Hour="EEE, MMM d"/>
+ android:format12Hour="@string/dream_date_complication_date_format"
+ android:format24Hour="@string/dream_date_complication_date_format"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index e066d38e446f..7a57293f58bd 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -22,8 +22,8 @@
android:fontFamily="@font/clock"
android:includeFontPadding="false"
android:textColor="@android:color/white"
- android:format12Hour="h:mm"
- android:format24Hour="kk:mm"
+ android:format12Hour="@string/dream_time_complication_12_hr_time_format"
+ android:format24Hour="@string/dream_time_complication_24_hr_time_format"
android:shadowColor="@color/keyguard_shadow_color"
android:shadowRadius="?attr/shadowRadius"
android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/>
diff --git a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
index 43b16618d615..a313833e2a66 100644
--- a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
+++ b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
@@ -57,7 +57,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- style="?attr/hybridNotificationTextStyle"
+ android:paddingEnd="4dp"
+ style="@*android:style/Widget.DeviceDefault.Notification.Text"
/>
<TextView
@@ -65,6 +66,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
- style="?attr/hybridNotificationTextStyle"
+ android:paddingEnd="4dp"
+ style="@*android:style/Widget.DeviceDefault.Notification.Text"
/>
</com.android.systemui.statusbar.notification.row.HybridConversationNotificationView>
diff --git a/packages/SystemUI/res/layout/hybrid_notification.xml b/packages/SystemUI/res/layout/hybrid_notification.xml
index e8d77511e53c..9ea7be50adec 100644
--- a/packages/SystemUI/res/layout/hybrid_notification.xml
+++ b/packages/SystemUI/res/layout/hybrid_notification.xml
@@ -20,19 +20,22 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom|start"
- style="?attr/hybridNotificationStyle">
+ android:paddingStart="@*android:dimen/notification_content_margin_start"
+ android:paddingEnd="12dp">
<TextView
android:id="@+id/notification_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- style="?attr/hybridNotificationTitleStyle"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:paddingEnd="4dp"
/>
<TextView
android:id="@+id/notification_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
- style="?attr/hybridNotificationTextStyle"
+ android:paddingEnd="4dp"
+ style="@*android:style/Widget.DeviceDefault.Notification.Text"
/>
</com.android.systemui.statusbar.notification.row.HybridNotificationView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
index 4d24140abbf4..d88680669fe0 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -31,6 +31,8 @@
android:padding="@dimen/media_ttt_chip_outer_padding"
android:background="@drawable/media_ttt_chip_background"
android:layout_marginTop="20dp"
+ android:layout_marginStart="@dimen/notification_side_paddings"
+ android:layout_marginEnd="@dimen/notification_side_paddings"
android:clipToPadding="false"
android:gravity="center_vertical"
android:alpha="0.0"
@@ -46,8 +48,9 @@
<TextView
android:id="@+id/text"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
+ android:layout_weight="1"
android:textSize="@dimen/media_ttt_text_size"
android:textColor="?android:attr/textColorPrimary"
android:alpha="0.0"
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index 5e8b892018eb..2b3d11b0e191 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -14,20 +14,19 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<!-- TODO(b/203800646): layout_marginTop doesn't seem to work on some large screens. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/media_ttt_receiver_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@drawable/media_ttt_chip_background_receiver"
>
<com.android.internal.widget.CachingIconView
android:id="@+id/app_icon"
android:layout_width="@dimen/media_ttt_icon_size_receiver"
android:layout_height="@dimen/media_ttt_icon_size_receiver"
- android:layout_gravity="center"
+ android:layout_gravity="center|bottom"
+ android:alpha="0.0"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index e6af6f46ae69..94fe20955ce6 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -15,9 +15,6 @@
-->
<resources>
- <!-- Minimum margin between clock and top of screen or ambient indication -->
- <dimen name="keyguard_clock_top_margin">26dp</dimen>
-
<!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
<dimen name="large_clock_text_size">200dp</dimen>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 70a72ad23d7e..9a71995383ac 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -108,12 +108,6 @@
<attr name="android:layout" />
</declare-styleable>
- <declare-styleable name="HybridNotificationTheme">
- <attr name="hybridNotificationStyle" format="reference" />
- <attr name="hybridNotificationTitleStyle" format="reference" />
- <attr name="hybridNotificationTextStyle" format="reference" />
- </declare-styleable>
-
<declare-styleable name="PluginInflateContainer">
<attr name="viewType" format="string" />
</declare-styleable>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4d365412828e..36a2d6469ec8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1060,6 +1060,7 @@
<!-- Since the generic icon isn't circular, we need to scale it down so it still fits within
the circular chip. -->
<dimen name="media_ttt_generic_icon_size_receiver">70dp</dimen>
+ <dimen name="media_ttt_receiver_vert_translation">20dp</dimen>
<!-- Window magnification -->
<dimen name="magnification_border_drag_size">35dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d88428f972cb..343ec4f67964 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2581,4 +2581,12 @@
<!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is unknown -->
<string name="bt_le_audio_broadcast_dialog_unknown_name">Unknown</string>
+ <!-- Date format for the Dream Date Complication [CHAR LIMIT=NONE] -->
+ <string name="dream_date_complication_date_format">EEE, MMM d</string>
+
+ <!-- Time format for the Dream Time Complication for 12-hour time format [CHAR LIMIT=NONE] -->
+ <string name="dream_time_complication_12_hr_time_format">h:mm</string>
+
+ <!-- Time format for the Dream Time Complication for 24-hour time format [CHAR LIMIT=NONE] -->
+ <string name="dream_time_complication_24_hr_time_format">kk:mm</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 8b2481cff2a9..f954bc9bf17c 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -17,30 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <!-- HybridNotification themes and styles -->
-
- <style name="HybridNotification">
- <item name="hybridNotificationStyle">@style/hybrid_notification</item>
- <item name="hybridNotificationTitleStyle">@style/hybrid_notification_title</item>
- <item name="hybridNotificationTextStyle">@style/hybrid_notification_text</item>
- </style>
-
- <style name="hybrid_notification">
- <item name="android:paddingStart">@*android:dimen/notification_content_margin_start</item>
- <item name="android:paddingEnd">12dp</item>
- </style>
-
- <style name="hybrid_notification_title">
- <item name="android:paddingEnd">4dp</item>
- <item name="android:textAppearance">@*android:style/TextAppearance.DeviceDefault.Notification.Title</item>
- </style>
-
- <style name="hybrid_notification_text"
- parent="@*android:style/Widget.DeviceDefault.Notification.Text">
- <item name="android:paddingEnd">4dp</item>
- </style>
-
-
<style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
<item name="android:textSize">@dimen/status_bar_clock_size</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
diff --git a/packages/SystemUI/screenshot/Android.bp b/packages/SystemUI/screenshot/Android.bp
new file mode 100644
index 000000000000..a79fd9040db3
--- /dev/null
+++ b/packages/SystemUI/screenshot/Android.bp
@@ -0,0 +1,48 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUIScreenshotLib",
+ manifest: "AndroidManifest.xml",
+
+ srcs: [
+ // All files in this library should be in Kotlin besides some exceptions.
+ "src/**/*.kt",
+
+ // This file was forked from google3, so exceptionally it can be in Java.
+ "src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java",
+ ],
+
+ resource_dirs: [
+ "res",
+ ],
+
+ static_libs: [
+ "SystemUI-core",
+ "androidx.test.espresso.core",
+ "androidx.appcompat_appcompat",
+ "platform-screenshot-diff-core",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/screenshot/AndroidManifest.xml b/packages/SystemUI/screenshot/AndroidManifest.xml
new file mode 100644
index 000000000000..3b703be34e5d
--- /dev/null
+++ b/packages/SystemUI/screenshot/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.testing.screenshot">
+ <application>
+ <activity
+ android:name="com.android.systemui.testing.screenshot.ScreenshotActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SystemUI.Screenshot" />
+ </application>
+
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+</manifest>
diff --git a/packages/SystemUI/screenshot/res/values/themes.xml b/packages/SystemUI/screenshot/res/values/themes.xml
new file mode 100644
index 000000000000..40e50bbb6bbf
--- /dev/null
+++ b/packages/SystemUI/screenshot/res/values/themes.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <style name="Theme.SystemUI.Screenshot" parent="Theme.SystemUI">
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+
+ <!-- Make sure that device specific cutouts don't impact the outcome of screenshot tests -->
+ <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java
new file mode 100644
index 000000000000..96ec4c543474
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java
@@ -0,0 +1,193 @@
+/*
+ * 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.testing.screenshot;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.ColorRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.core.content.ContextCompat;
+import androidx.test.espresso.Espresso;
+import androidx.test.espresso.IdlingRegistry;
+import androidx.test.espresso.IdlingResource;
+
+import org.json.JSONObject;
+import org.junit.function.ThrowingRunnable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/*
+ * Note: This file was forked from
+ * google3/third_party/java_src/android_libs/material_components/screenshot_tests/java/android/
+ * support/design/scuba/color/DynamicColorsTestUtils.java.
+ */
+
+/** Utility that helps change the dynamic system colors for testing. */
+@RequiresApi(32)
+public class DynamicColorsTestUtils {
+
+ private static final String TAG = DynamicColorsTestUtils.class.getSimpleName();
+
+ private static final String THEME_CUSTOMIZATION_KEY = "theme_customization_overlay_packages";
+ private static final String THEME_CUSTOMIZATION_SYSTEM_PALETTE_KEY =
+ "android.theme.customization.system_palette";
+
+ private static final int ORANGE_SYSTEM_SEED_COLOR = 0xA66800;
+ private static final int ORANGE_EXPECTED_SYSTEM_ACCENT1_600_COLOR = -8235756;
+
+ private DynamicColorsTestUtils() {
+ }
+
+ /**
+ * Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on an orange
+ * seed color, and then wait for the change to propagate to the app by comparing
+ * android.R.color.system_accent1_600 to the expected orange value.
+ */
+ public static void updateSystemColorsToOrange() {
+ updateSystemColors(ORANGE_SYSTEM_SEED_COLOR, ORANGE_EXPECTED_SYSTEM_ACCENT1_600_COLOR);
+ }
+
+ /**
+ * Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on the provided
+ * {@code seedColor}, and then wait for the change to propagate to the app by comparing
+ * android.R.color.system_accent1_600 to {@code expectedSystemAccent1600}.
+ */
+ public static void updateSystemColors(
+ @ColorInt int seedColor, @ColorInt int expectedSystemAccent1600) {
+ Context context = getInstrumentation().getTargetContext();
+
+ int actualSystemAccent1600 =
+ ContextCompat.getColor(context, android.R.color.system_accent1_600);
+
+ if (expectedSystemAccent1600 == actualSystemAccent1600) {
+ String expectedColorString = Integer.toHexString(expectedSystemAccent1600);
+ Log.d(
+ TAG,
+ "Skipped updating system colors since system_accent1_600 is already equal to "
+ + "expected: "
+ + expectedColorString);
+ return;
+ }
+
+ updateSystemColors(seedColor);
+ }
+
+ /**
+ * Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on the provided
+ * {@code seedColor}, and then wait for the change to propagate to the app by checking
+ * android.R.color.system_accent1_600 for any change.
+ */
+ public static void updateSystemColors(@ColorInt int seedColor) {
+ Context context = getInstrumentation().getTargetContext();
+
+ // Initialize system color idling resource with original system_accent1_600 value.
+ ColorChangeIdlingResource systemColorIdlingResource =
+ new ColorChangeIdlingResource(context, android.R.color.system_accent1_600);
+
+ // Update system theme color setting to trigger fabricated resource overlay.
+ runWithShellPermissionIdentity(
+ () ->
+ Settings.Secure.putString(
+ context.getContentResolver(),
+ THEME_CUSTOMIZATION_KEY,
+ buildThemeCustomizationString(seedColor)));
+
+ // Wait for system color update to propagate to app.
+ IdlingRegistry idlingRegistry = IdlingRegistry.getInstance();
+ idlingRegistry.register(systemColorIdlingResource);
+ Espresso.onIdle();
+ idlingRegistry.unregister(systemColorIdlingResource);
+
+ Log.d(TAG,
+ Settings.Secure.getString(context.getContentResolver(), THEME_CUSTOMIZATION_KEY));
+ }
+
+ private static String buildThemeCustomizationString(@ColorInt int seedColor) {
+ String seedColorHex = Integer.toHexString(seedColor);
+ Map<String, String> themeCustomizationMap = new HashMap<>();
+ themeCustomizationMap.put(THEME_CUSTOMIZATION_SYSTEM_PALETTE_KEY, seedColorHex);
+ return new JSONObject(themeCustomizationMap).toString();
+ }
+
+ private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable) {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity();
+ try {
+ runnable.run();
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ private static class ColorChangeIdlingResource implements IdlingResource {
+
+ private final Context mContext;
+ private final int mColorResId;
+ private final int mInitialColorInt;
+
+ private ResourceCallback mResourceCallback;
+ private boolean mIdleNow;
+
+ ColorChangeIdlingResource(Context context, @ColorRes int colorResId) {
+ this.mContext = context;
+ this.mColorResId = colorResId;
+ this.mInitialColorInt = ContextCompat.getColor(context, colorResId);
+ }
+
+ @Override
+ public String getName() {
+ return ColorChangeIdlingResource.class.getName();
+ }
+
+ @Override
+ public boolean isIdleNow() {
+ if (mIdleNow) {
+ return true;
+ }
+
+ int currentColorInt = ContextCompat.getColor(mContext, mColorResId);
+
+ String initialColorString = Integer.toHexString(mInitialColorInt);
+ String currentColorString = Integer.toHexString(currentColorInt);
+ Log.d(TAG, String.format("Initial=%s, Current=%s", initialColorString,
+ currentColorString));
+
+ mIdleNow = currentColorInt != mInitialColorInt;
+ Log.d(TAG, String.format("idleNow=%b", mIdleNow));
+
+ if (mIdleNow) {
+ mResourceCallback.onTransitionToIdle();
+ }
+ return mIdleNow;
+ }
+
+ @Override
+ public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
+ this.mResourceCallback = resourceCallback;
+ }
+ }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
new file mode 100644
index 000000000000..2a55a80eb7f4
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.testing.screenshot
+
+import androidx.activity.ComponentActivity
+
+/** The Activity that is launched and whose content is set for screenshot tests. */
+class ScreenshotActivity : ComponentActivity()
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestRule.kt
new file mode 100644
index 000000000000..363ce10fa36c
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestRule.kt
@@ -0,0 +1,206 @@
+/*
+ * 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.testing.screenshot
+
+import android.app.UiModeManager
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.os.UserHandle
+import android.view.Display
+import android.view.View
+import android.view.WindowManagerGlobal
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.PathConfig
+import platform.test.screenshot.PathElementNoContext
+import platform.test.screenshot.ScreenshotTestRule
+import platform.test.screenshot.matchers.PixelPerfectMatcher
+
+/**
+ * A base rule for screenshot diff tests.
+ *
+ * This rules takes care of setting up the activity according to [testSpec] by:
+ * - emulating the display size and density.
+ * - setting the dark/light mode.
+ * - setting the system (Material You) colors to a fixed value.
+ *
+ * @see ComposeScreenshotTestRule
+ * @see ViewScreenshotTestRule
+ */
+class ScreenshotTestRule(private val testSpec: ScreenshotTestSpec) : TestRule {
+ private var currentDisplay: DisplaySpec? = null
+ private var currentGoldenIdentifier: String? = null
+
+ private val pathConfig =
+ PathConfig(
+ PathElementNoContext("model", isDir = true) {
+ currentDisplay?.name ?: error("currentDisplay is null")
+ },
+ )
+ private val defaultMatcher = PixelPerfectMatcher()
+
+ private val screenshotRule =
+ ScreenshotTestRule(
+ SystemUIGoldenImagePathManager(
+ pathConfig,
+ currentGoldenIdentifier = {
+ currentGoldenIdentifier ?: error("currentGoldenIdentifier is null")
+ },
+ )
+ )
+
+ override fun apply(base: Statement, description: Description): Statement {
+ // The statement which call beforeTest() before running the test and afterTest() afterwards.
+ val statement =
+ object : Statement() {
+ override fun evaluate() {
+ try {
+ beforeTest()
+ base.evaluate()
+ } finally {
+ afterTest()
+ }
+ }
+ }
+
+ return screenshotRule.apply(statement, description)
+ }
+
+ private fun beforeTest() {
+ // Update the system colors to a fixed color, so that tests don't depend on the host device
+ // extracted colors. Note that we don't restore the default device colors at the end of the
+ // test because changing the colors (and waiting for them to be applied) is costly and makes
+ // the screenshot tests noticeably slower.
+ DynamicColorsTestUtils.updateSystemColorsToOrange()
+
+ // Emulate the display size and density.
+ val display = testSpec.display
+ val density = display.densityDpi
+ val wm = WindowManagerGlobal.getWindowManagerService()
+ val (width, height) = getEmulatedDisplaySize()
+ wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, density, UserHandle.myUserId())
+ wm.setForcedDisplaySize(Display.DEFAULT_DISPLAY, width, height)
+
+ // Force the dark/light theme.
+ val uiModeManager =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
+ uiModeManager.setApplicationNightMode(
+ if (testSpec.isDarkTheme) {
+ UiModeManager.MODE_NIGHT_YES
+ } else {
+ UiModeManager.MODE_NIGHT_NO
+ }
+ )
+ }
+
+ private fun afterTest() {
+ // Reset the density and display size.
+ val wm = WindowManagerGlobal.getWindowManagerService()
+ wm.clearForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, UserHandle.myUserId())
+ wm.clearForcedDisplaySize(Display.DEFAULT_DISPLAY)
+
+ // Reset the dark/light theme.
+ val uiModeManager =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
+ uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_AUTO)
+ }
+
+ /**
+ * Compare the content of [view] with the golden image identified by [goldenIdentifier] in the
+ * context of [testSpec].
+ */
+ fun screenshotTest(goldenIdentifier: String, view: View) {
+ val bitmap = drawIntoBitmap(view)
+
+ // Compare bitmap against golden asset.
+ val isDarkTheme = testSpec.isDarkTheme
+ val isLandscape = testSpec.isLandscape
+ val identifierWithSpec = buildString {
+ append(goldenIdentifier)
+ if (isDarkTheme) append("_dark")
+ if (isLandscape) append("_landscape")
+ }
+
+ // TODO(b/230832101): Provide a way to pass a PathConfig and override the file name on
+ // device to assertBitmapAgainstGolden instead?
+ currentDisplay = testSpec.display
+ currentGoldenIdentifier = goldenIdentifier
+ screenshotRule.assertBitmapAgainstGolden(bitmap, identifierWithSpec, defaultMatcher)
+ currentDisplay = null
+ currentGoldenIdentifier = goldenIdentifier
+ }
+
+ /** Draw [view] into a [Bitmap]. */
+ private fun drawIntoBitmap(view: View): Bitmap {
+ val bitmap =
+ Bitmap.createBitmap(
+ view.measuredWidth,
+ view.measuredHeight,
+ Bitmap.Config.ARGB_8888,
+ )
+ val canvas = Canvas(bitmap)
+ view.draw(canvas)
+ return bitmap
+ }
+
+ /** Get the emulated display size for [testSpec]. */
+ private fun getEmulatedDisplaySize(): Pair<Int, Int> {
+ val display = testSpec.display
+ val isPortraitNaturalPosition = display.width < display.height
+ return if (testSpec.isLandscape) {
+ if (isPortraitNaturalPosition) {
+ display.height to display.width
+ } else {
+ display.width to display.height
+ }
+ } else {
+ if (isPortraitNaturalPosition) {
+ display.width to display.height
+ } else {
+ display.height to display.width
+ }
+ }
+ }
+}
+
+private class SystemUIGoldenImagePathManager(
+ pathConfig: PathConfig,
+ private val currentGoldenIdentifier: () -> String,
+) :
+ GoldenImagePathManager(
+ appContext = InstrumentationRegistry.getInstrumentation().context,
+ deviceLocalPath =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/sysui_screenshots",
+ pathConfig = pathConfig,
+ ) {
+ // This string is appended to all actual/expected screenshots on the device. We append the
+ // golden identifier so that our pull_golden.py scripts can map a screenshot on device to its
+ // asset (and automatically update it, if necessary).
+ override fun toString() = currentGoldenIdentifier()
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestSpec.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestSpec.kt
new file mode 100644
index 000000000000..7fc624554738
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestSpec.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.testing.screenshot
+
+/** The specification of a device display to be used in a screenshot test. */
+data class DisplaySpec(
+ val name: String,
+ val width: Int,
+ val height: Int,
+ val densityDpi: Int,
+)
+
+/** The specification of a screenshot diff test. */
+class ScreenshotTestSpec(
+ val display: DisplaySpec,
+ val isDarkTheme: Boolean = false,
+ val isLandscape: Boolean = false,
+) {
+ companion object {
+ /**
+ * Return a list of [ScreenshotTestSpec] for each of the [displays].
+ *
+ * If [isDarkTheme] is null, this will create a spec for both light and dark themes, for
+ * each of the orientation.
+ *
+ * If [isLandscape] is null, this will create a spec for both portrait and landscape, for
+ * each of the light/dark themes.
+ */
+ fun forDisplays(
+ vararg displays: DisplaySpec,
+ isDarkTheme: Boolean? = null,
+ isLandscape: Boolean? = null,
+ ): List<ScreenshotTestSpec> {
+ return displays.flatMap { display ->
+ buildList {
+ fun addDisplay(isLandscape: Boolean) {
+ if (isDarkTheme != true) {
+ add(ScreenshotTestSpec(display, isDarkTheme = false, isLandscape))
+ }
+
+ if (isDarkTheme != false) {
+ add(ScreenshotTestSpec(display, isDarkTheme = true, isLandscape))
+ }
+ }
+
+ if (isLandscape != true) {
+ addDisplay(isLandscape = false)
+ }
+
+ if (isLandscape != false) {
+ addDisplay(isLandscape = true)
+ }
+ }
+ }
+ }
+ }
+
+ override fun toString(): String = buildString {
+ // This string is appended to PNGs stored in the device, so let's keep it simple.
+ append(display.name)
+ if (isDarkTheme) append("_dark")
+ if (isLandscape) append("_landscape")
+ }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
new file mode 100644
index 000000000000..35812e39677d
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -0,0 +1,80 @@
+package com.android.systemui.testing.screenshot
+
+import android.app.Activity
+import android.app.Dialog
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import org.junit.Assert.assertEquals
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/** A rule for View screenshot diff tests. */
+class ViewScreenshotTestRule(testSpec: ScreenshotTestSpec) : TestRule {
+ private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
+ private val screenshotRule = ScreenshotTestRule(testSpec)
+
+ private val delegate = RuleChain.outerRule(screenshotRule).around(activityRule)
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return delegate.apply(base, description)
+ }
+
+ /**
+ * Compare the content of the view provided by [viewProvider] with the golden image identified
+ * by [goldenIdentifier] in the context of [testSpec].
+ */
+ fun screenshotTest(
+ goldenIdentifier: String,
+ layoutParams: LayoutParams =
+ LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT),
+ viewProvider: (Activity) -> View,
+ ) {
+ activityRule.scenario.onActivity { activity ->
+ // Make sure that the activity draws full screen and fits the whole display instead of
+ // the system bars.
+ activity.window.setDecorFitsSystemWindows(false)
+ activity.setContentView(viewProvider(activity), layoutParams)
+ }
+
+ // We call onActivity again because it will make sure that our Activity is done measuring,
+ // laying out and drawing its content (that we set in the previous onActivity lambda).
+ activityRule.scenario.onActivity { activity ->
+ // Check that the content is what we expected.
+ val content = activity.requireViewById<ViewGroup>(android.R.id.content)
+ assertEquals(1, content.childCount)
+ screenshotRule.screenshotTest(goldenIdentifier, content.getChildAt(0))
+ }
+ }
+
+ /**
+ * Compare the content of the dialog provided by [dialogProvider] with the golden image
+ * identified by [goldenIdentifier] in the context of [testSpec].
+ */
+ fun dialogScreenshotTest(
+ goldenIdentifier: String,
+ dialogProvider: (Activity) -> Dialog,
+ ) {
+ var dialog: Dialog? = null
+ activityRule.scenario.onActivity { activity ->
+ // Make sure that the dialog draws full screen and fits the whole display instead of the
+ // system bars.
+ dialog =
+ dialogProvider(activity).apply {
+ window.setDecorFitsSystemWindows(false)
+ show()
+ }
+ }
+
+ // We call onActivity again because it will make sure that our Dialog is done measuring,
+ // laying out and drawing its content (that we set in the previous onActivity lambda).
+ activityRule.scenario.onActivity {
+ // Check that the content is what we expected.
+ val dialog = dialog ?: error("dialog is null")
+ screenshotRule.screenshotTest(goldenIdentifier, dialog.window.decorView)
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 88fe03465405..203b236fcdd6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -80,7 +80,8 @@ public class PipSurfaceTransactionHelper {
public PictureInPictureSurfaceTransaction scaleAndCrop(
SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect sourceRectHint, Rect sourceBounds, Rect destinationBounds, Rect insets) {
+ Rect sourceRectHint, Rect sourceBounds, Rect destinationBounds, Rect insets,
+ float progress) {
mTmpSourceRectF.set(sourceBounds);
mTmpDestinationRect.set(sourceBounds);
mTmpDestinationRect.inset(insets);
@@ -93,9 +94,13 @@ public class PipSurfaceTransactionHelper {
: (float) destinationBounds.height() / sourceBounds.height();
} else {
// scale by sourceRectHint if it's not edge-to-edge
- scale = sourceRectHint.width() <= sourceRectHint.height()
+ final float endScale = sourceRectHint.width() <= sourceRectHint.height()
? (float) destinationBounds.width() / sourceRectHint.width()
: (float) destinationBounds.height() / sourceRectHint.height();
+ final float startScale = sourceRectHint.width() <= sourceRectHint.height()
+ ? (float) destinationBounds.width() / sourceBounds.width()
+ : (float) destinationBounds.height() / sourceBounds.height();
+ scale = (1 - progress) * startScale + progress * endScale;
}
final float left = destinationBounds.left - (insets.left + sourceBounds.left) * scale;
final float top = destinationBounds.top - (insets.top + sourceBounds.top) * scale;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 76a09b38036c..9265f07ad284 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -105,7 +105,7 @@ public class RemoteAnimationAdapterCompat {
}
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
remoteAnimationAdapter.onAnimationCancelled();
}
};
@@ -114,6 +114,8 @@ public class RemoteAnimationAdapterCompat {
private static IRemoteTransition.Stub wrapRemoteTransition(
final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
return new IRemoteTransition.Stub() {
+ final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
+
@Override
public void startAnimation(IBinder token, TransitionInfo info,
SurfaceControl.Transaction t,
@@ -218,9 +220,9 @@ public class RemoteAnimationAdapterCompat {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
info.getChanges().get(i).getLeash().release();
}
- for (int i = leashMap.size() - 1; i >= 0; --i) {
- leashMap.valueAt(i).release();
- }
+ // Don't release here since launcher might still be using them. Instead
+ // let launcher release them (eg. via RemoteAnimationTargets)
+ leashMap.clear();
try {
finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
} catch (RemoteException e) {
@@ -229,19 +231,32 @@ public class RemoteAnimationAdapterCompat {
}
}
};
+ synchronized (mFinishRunnables) {
+ mFinishRunnables.put(token, animationFinishedCallback);
+ }
// TODO(bc-unlcok): Pass correct transit type.
- remoteAnimationAdapter.onAnimationStart(
- TRANSIT_OLD_NONE,
- appsCompat, wallpapersCompat, nonAppsCompat,
- animationFinishedCallback);
+ remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE,
+ appsCompat, wallpapersCompat, nonAppsCompat, () -> {
+ synchronized (mFinishRunnables) {
+ if (mFinishRunnables.remove(token) == null) return;
+ }
+ animationFinishedCallback.run();
+ });
}
@Override
public void mergeAnimation(IBinder token, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) {
- // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, ignore
- // any incoming merges.
+ // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
+ // to legacy cancel.
+ final Runnable finishRunnable;
+ synchronized (mFinishRunnables) {
+ finishRunnable = mFinishRunnables.remove(mergeTarget);
+ }
+ if (finishRunnable == null) return;
+ remoteAnimationAdapter.onAnimationCancelled();
+ finishRunnable.run();
}
};
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 4ce110bf7c47..249133a9a63b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -141,8 +141,10 @@ public class RemoteAnimationTargetCompat {
final int mode = change.getMode();
t.reparent(leash, info.getRootLeash());
- t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
- change.getStartAbsBounds().top - info.getRootOffset().y);
+ final Rect absBounds =
+ (mode == TRANSIT_OPEN) ? change.getEndAbsBounds() : change.getStartAbsBounds();
+ t.setPosition(leash, absBounds.left - info.getRootOffset().x,
+ absBounds.top - info.getRootOffset().y);
// Put all the OPEN/SHOW on top
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 83780c8a176d..29e912fdab32 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -59,6 +59,7 @@ public class KeyguardPasswordViewController
private final boolean mShowImeAtScreenOn;
private EditText mPasswordEntry;
private ImageView mSwitchImeButton;
+ private boolean mPaused;
private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> {
// Check if this was the result of hitting the enter key
@@ -202,6 +203,7 @@ public class KeyguardPasswordViewController
@Override
public void onResume(int reason) {
super.onResume(reason);
+ mPaused = false;
if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
showInput();
}
@@ -223,6 +225,11 @@ public class KeyguardPasswordViewController
@Override
public void onPause() {
+ if (mPaused) {
+ return;
+ }
+ mPaused = true;
+
if (!mPasswordEntry.isVisibleToUser()) {
// Reset all states directly and then hide IME when the screen turned off.
super.onPause();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 65c415b5272b..8fb622a8c55e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -94,6 +94,7 @@ import com.android.systemui.util.settings.GlobalSettings;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
public class KeyguardSecurityContainer extends FrameLayout {
static final int USER_TYPE_PRIMARY = 1;
@@ -128,12 +129,12 @@ public class KeyguardSecurityContainer extends FrameLayout {
private static final long IME_DISAPPEAR_DURATION_MS = 125;
- // The duration of the animation to switch bouncer sides.
- private static final long BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS = 500;
+ // The duration of the animation to switch security sides.
+ private static final long SECURITY_SHIFT_ANIMATION_DURATION_MS = 500;
- // How much of the switch sides animation should be dedicated to fading the bouncer out. The
+ // How much of the switch sides animation should be dedicated to fading the security out. The
// remainder will fade it back in again.
- private static final float BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION = 0.2f;
+ private static final float SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f;
@VisibleForTesting
KeyguardSecurityViewFlipper mSecurityViewFlipper;
@@ -375,10 +376,23 @@ public class KeyguardSecurityContainer extends FrameLayout {
mViewMode.updatePositionByTouchX(x);
}
- /** Returns whether the inner SecurityViewFlipper is left-aligned when in one-handed mode. */
- public boolean isOneHandedModeLeftAligned() {
- return mCurrentMode == MODE_ONE_HANDED
- && ((OneHandedViewMode) mViewMode).isLeftAligned();
+ public boolean isSidedSecurityMode() {
+ return mViewMode instanceof SidedSecurityMode;
+ }
+
+ /** Returns whether the inner SecurityViewFlipper is left-aligned when in sided mode. */
+ public boolean isSecurityLeftAligned() {
+ return mViewMode instanceof SidedSecurityMode
+ && ((SidedSecurityMode) mViewMode).isLeftAligned();
+ }
+
+ /**
+ * Returns whether the touch happened on the other side of security (like bouncer) when in
+ * sided mode.
+ */
+ public boolean isTouchOnTheOtherSideOfSecurity(MotionEvent ev) {
+ return mViewMode instanceof SidedSecurityMode
+ && ((SidedSecurityMode) mViewMode).isTouchOnTheOtherSideOfSecurity(ev);
}
public void onPause() {
@@ -437,12 +451,15 @@ public class KeyguardSecurityContainer extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
- mDoubleTapDetector.onTouchEvent(event);
boolean result = mMotionEventListeners.stream()
.anyMatch(listener -> listener.onTouchEvent(event))
|| super.onTouchEvent(event);
+ // double tap detector should be called after listeners handle touches as listeners are
+ // helping with ignoring falsing. Otherwise falsing will be activated for some double taps
+ mDoubleTapDetector.onTouchEvent(event);
+
switch (action) {
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
@@ -487,11 +504,16 @@ public class KeyguardSecurityContainer extends FrameLayout {
private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDoubleTap(MotionEvent e) {
- if (!mIsDragging) {
- mViewMode.handleDoubleTap(e);
- }
+ return handleDoubleTap(e);
+ }
+ }
+
+ @VisibleForTesting boolean handleDoubleTap(MotionEvent e) {
+ if (!mIsDragging) {
+ mViewMode.handleDoubleTap(e);
return true;
}
+ return false;
}
void addMotionEventListener(Gefingerpoken listener) {
@@ -771,6 +793,195 @@ public class KeyguardSecurityContainer extends FrameLayout {
}
/**
+ * Base class for modes which support having on left/right side of the screen, used for large
+ * screen devices
+ */
+ abstract static class SidedSecurityMode implements ViewMode {
+ @Nullable private ValueAnimator mRunningSecurityShiftAnimator;
+ private KeyguardSecurityViewFlipper mViewFlipper;
+ private ViewGroup mView;
+ private GlobalSettings mGlobalSettings;
+ private int mDefaultSideSetting;
+
+ public void init(ViewGroup v, KeyguardSecurityViewFlipper viewFlipper,
+ GlobalSettings globalSettings, boolean leftAlignedByDefault) {
+ mView = v;
+ mViewFlipper = viewFlipper;
+ mGlobalSettings = globalSettings;
+ mDefaultSideSetting =
+ leftAlignedByDefault ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
+ : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT;
+ }
+
+ /**
+ * Determine if a double tap on this view is on the other side. If so, will animate
+ * positions and record the preference to always show on this side.
+ */
+ @Override
+ public void handleDoubleTap(MotionEvent event) {
+ boolean currentlyLeftAligned = isLeftAligned();
+ // Did the tap hit the "other" side of the bouncer?
+ if (isTouchOnTheOtherSideOfSecurity(event, currentlyLeftAligned)) {
+ boolean willBeLeftAligned = !currentlyLeftAligned;
+ updateSideSetting(willBeLeftAligned);
+
+ int keyguardState = willBeLeftAligned
+ ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT
+ : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT;
+ SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState);
+
+ updateSecurityViewLocation(willBeLeftAligned, /* animate= */ true);
+ }
+ }
+
+ private boolean isTouchOnTheOtherSideOfSecurity(MotionEvent ev, boolean leftAligned) {
+ float x = ev.getX();
+ return (leftAligned && (x > mView.getWidth() / 2f))
+ || (!leftAligned && (x < mView.getWidth() / 2f));
+ }
+
+ public boolean isTouchOnTheOtherSideOfSecurity(MotionEvent ev) {
+ return isTouchOnTheOtherSideOfSecurity(ev, isLeftAligned());
+ }
+
+ protected abstract void updateSecurityViewLocation(boolean leftAlign, boolean animate);
+
+ protected void translateSecurityViewLocation(boolean leftAlign, boolean animate) {
+ translateSecurityViewLocation(leftAlign, animate, i -> {});
+ }
+
+ /**
+ * Moves the inner security view to the correct location with animation. This is triggered
+ * when the user double taps on the side of the screen that is not currently occupied by
+ * the security view.
+ */
+ protected void translateSecurityViewLocation(boolean leftAlign, boolean animate,
+ Consumer<Float> securityAlphaListener) {
+ if (mRunningSecurityShiftAnimator != null) {
+ mRunningSecurityShiftAnimator.cancel();
+ mRunningSecurityShiftAnimator = null;
+ }
+
+ int targetTranslation = leftAlign
+ ? 0 : mView.getMeasuredWidth() - mViewFlipper.getWidth();
+
+ if (animate) {
+ // This animation is a bit fun to implement. The bouncer needs to move, and fade
+ // in/out at the same time. The issue is, the bouncer should only move a short
+ // amount (120dp or so), but obviously needs to go from one side of the screen to
+ // the other. This needs a pretty custom animation.
+ //
+ // This works as follows. It uses a ValueAnimation to simply drive the animation
+ // progress. This animator is responsible for both the translation of the bouncer,
+ // and the current fade. It will fade the bouncer out while also moving it along the
+ // 120dp path. Once the bouncer is fully faded out though, it will "snap" the
+ // bouncer closer to its destination, then fade it back in again. The effect is that
+ // the bouncer will move from 0 -> X while fading out, then
+ // (destination - X) -> destination while fading back in again.
+ // TODO(b/208250221): Make this animation properly abortable.
+ Interpolator positionInterpolator = AnimationUtils.loadInterpolator(
+ mView.getContext(), android.R.interpolator.fast_out_extra_slow_in);
+ Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
+ Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
+
+ mRunningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mRunningSecurityShiftAnimator.setDuration(SECURITY_SHIFT_ANIMATION_DURATION_MS);
+ mRunningSecurityShiftAnimator.setInterpolator(Interpolators.LINEAR);
+
+ int initialTranslation = (int) mViewFlipper.getTranslationX();
+ int totalTranslation = (int) mView.getResources().getDimension(
+ R.dimen.security_shift_animation_translation);
+
+ final boolean shouldRestoreLayerType = mViewFlipper.hasOverlappingRendering()
+ && mViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE;
+ if (shouldRestoreLayerType) {
+ mViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
+ }
+
+ float initialAlpha = mViewFlipper.getAlpha();
+
+ mRunningSecurityShiftAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mRunningSecurityShiftAnimator = null;
+ }
+ });
+ mRunningSecurityShiftAnimator.addUpdateListener(animation -> {
+ float switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION;
+ boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;
+
+ int currentTranslation = (int) (positionInterpolator.getInterpolation(
+ animation.getAnimatedFraction()) * totalTranslation);
+ int translationRemaining = totalTranslation - currentTranslation;
+
+ // Flip the sign if we're going from right to left.
+ if (leftAlign) {
+ currentTranslation = -currentTranslation;
+ translationRemaining = -translationRemaining;
+ }
+
+ float opacity;
+ if (isFadingOut) {
+ // The bouncer fades out over the first X%.
+ float fadeOutFraction = MathUtils.constrainedMap(
+ /* rangeMin= */1.0f,
+ /* rangeMax= */0.0f,
+ /* valueMin= */0.0f,
+ /* valueMax= */switchPoint,
+ animation.getAnimatedFraction());
+ opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
+
+ // When fading out, the alpha needs to start from the initial opacity of the
+ // view flipper, otherwise we get a weird bit of jank as it ramps back to
+ // 100%.
+ mViewFlipper.setAlpha(opacity * initialAlpha);
+
+ // Animate away from the source.
+ mViewFlipper.setTranslationX(initialTranslation + currentTranslation);
+ } else {
+ // And in again over the remaining (100-X)%.
+ float fadeInFraction = MathUtils.constrainedMap(
+ /* rangeMin= */0.0f,
+ /* rangeMax= */1.0f,
+ /* valueMin= */switchPoint,
+ /* valueMax= */1.0f,
+ animation.getAnimatedFraction());
+
+ opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
+ mViewFlipper.setAlpha(opacity);
+
+ // Fading back in, animate towards the destination.
+ mViewFlipper.setTranslationX(targetTranslation - translationRemaining);
+ }
+ securityAlphaListener.accept(opacity);
+
+ if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
+ mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
+ }
+ });
+
+ mRunningSecurityShiftAnimator.start();
+ } else {
+ mViewFlipper.setTranslationX(targetTranslation);
+ }
+ }
+
+
+ boolean isLeftAligned() {
+ return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
+ mDefaultSideSetting)
+ == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
+ }
+
+ protected void updateSideSetting(boolean leftAligned) {
+ mGlobalSettings.putInt(
+ Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
+ leftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
+ : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
+ }
+ }
+
+ /**
* Default bouncer is centered within the space
*/
static class DefaultViewMode implements ViewMode {
@@ -802,7 +1013,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
* User switcher mode will display both the current user icon as well as
* a user switcher, in both portrait and landscape modes.
*/
- static class UserSwitcherViewMode implements ViewMode {
+ static class UserSwitcherViewMode extends SidedSecurityMode {
private ViewGroup mView;
private ViewGroup mUserSwitcherViewGroup;
private KeyguardSecurityViewFlipper mViewFlipper;
@@ -814,11 +1025,15 @@ public class KeyguardSecurityContainer extends FrameLayout {
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
this::setupUserSwitcher;
+ private float mAnimationLastAlpha = 1f;
+ private boolean mAnimationWaitsToShift = true;
+
@Override
public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
@NonNull UserSwitcherController userSwitcherController) {
+ init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */false);
mView = v;
mViewFlipper = viewFlipper;
mFalsingManager = falsingManager;
@@ -832,9 +1047,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
true);
mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher);
}
-
updateSecurityViewLocation();
-
mUserSwitcher = mView.findViewById(R.id.user_switcher_header);
setupUserSwitcher();
mUserSwitcherController.addUserSwitchCallback(mUserSwitchCallback);
@@ -1030,18 +1243,65 @@ public class KeyguardSecurityContainer extends FrameLayout {
@Override
public void updateSecurityViewLocation() {
- int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans);
+ updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
+ }
+
+ public void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
+ setYTranslation();
+ setGravity();
+ setXTranslation(leftAlign, animate);
+ }
+ private void setXTranslation(boolean leftAlign, boolean animate) {
+ if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+ mUserSwitcherViewGroup.setTranslationX(0);
+ mViewFlipper.setTranslationX(0);
+ } else {
+ int switcherTargetTranslation = leftAlign
+ ? mView.getMeasuredWidth() - mViewFlipper.getWidth() : 0;
+ if (animate) {
+ mAnimationWaitsToShift = true;
+ mAnimationLastAlpha = 1f;
+ translateSecurityViewLocation(leftAlign, animate, securityAlpha -> {
+ // During the animation security view fades out - alpha goes from 1 to
+ // (almost) 0 - and then fades in - alpha grows back to 1.
+ // If new alpha is bigger than previous one it means we're at inflection
+ // point and alpha is zero or almost zero. That's when we want to do
+ // translation of user switcher, so that it's not visible to the user.
+ boolean fullyFadeOut = securityAlpha == 0.0f
+ || securityAlpha > mAnimationLastAlpha;
+ if (fullyFadeOut && mAnimationWaitsToShift) {
+ mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation);
+ mAnimationWaitsToShift = false;
+ }
+ mUserSwitcherViewGroup.setAlpha(securityAlpha);
+ mAnimationLastAlpha = securityAlpha;
+ });
+ } else {
+ translateSecurityViewLocation(leftAlign, animate);
+ mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation);
+ }
+ }
+
+ }
+
+ private void setGravity() {
if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL);
updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL);
+ updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL);
+ } else {
+ // horizontal gravity is the same because we translate these views anyway
+ updateViewGravity(mViewFlipper, Gravity.LEFT | Gravity.BOTTOM);
+ updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL);
+ }
+ }
+ private void setYTranslation() {
+ int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans);
+ if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
mUserSwitcherViewGroup.setTranslationY(yTrans);
mViewFlipper.setTranslationY(-yTrans);
} else {
- updateViewGravity(mViewFlipper, Gravity.RIGHT | Gravity.BOTTOM);
- updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL);
-
// Attempt to reposition a bit higher to make up for this frame being a bit lower
// on the device
mUserSwitcherViewGroup.setTranslationY(-yTrans);
@@ -1060,20 +1320,18 @@ public class KeyguardSecurityContainer extends FrameLayout {
* Logic to enabled one-handed bouncer mode. Supports animating the bouncer
* between alternate sides of the display.
*/
- static class OneHandedViewMode implements ViewMode {
- @Nullable private ValueAnimator mRunningOneHandedAnimator;
+ static class OneHandedViewMode extends SidedSecurityMode {
private ViewGroup mView;
private KeyguardSecurityViewFlipper mViewFlipper;
- private GlobalSettings mGlobalSettings;
@Override
public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
@NonNull UserSwitcherController userSwitcherController) {
+ init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */true);
mView = v;
mViewFlipper = viewFlipper;
- mGlobalSettings = globalSettings;
updateSecurityViewGravity();
updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
@@ -1107,159 +1365,13 @@ public class KeyguardSecurityContainer extends FrameLayout {
updateSecurityViewLocation(isTouchOnLeft, /* animate= */false);
}
- boolean isLeftAligned() {
- return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
- Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT)
- == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
- }
-
- private void updateSideSetting(boolean leftAligned) {
- mGlobalSettings.putInt(
- Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
- leftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
- : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
- }
-
- /**
- * Determine if a double tap on this view is on the other side. If so, will animate
- * positions and record the preference to always show on this side.
- */
- @Override
- public void handleDoubleTap(MotionEvent event) {
- float x = event.getX();
- boolean currentlyLeftAligned = isLeftAligned();
- // Did the tap hit the "other" side of the bouncer?
- if ((currentlyLeftAligned && (x > mView.getWidth() / 2f))
- || (!currentlyLeftAligned && (x < mView.getWidth() / 2f))) {
-
- boolean willBeLeftAligned = !currentlyLeftAligned;
- updateSideSetting(willBeLeftAligned);
-
- int keyguardState = willBeLeftAligned
- ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT
- : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT;
- SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState);
-
- updateSecurityViewLocation(willBeLeftAligned, true /* animate */);
- }
- }
-
@Override
public void updateSecurityViewLocation() {
updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
}
- /**
- * Moves the inner security view to the correct location (in one handed mode) with
- * animation. This is triggered when the user taps on the side of the screen that is not
- * currently occupied by the security view.
- */
- private void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
- if (mRunningOneHandedAnimator != null) {
- mRunningOneHandedAnimator.cancel();
- mRunningOneHandedAnimator = null;
- }
-
- int targetTranslation = leftAlign
- ? 0 : (int) (mView.getMeasuredWidth() - mViewFlipper.getWidth());
-
- if (animate) {
- // This animation is a bit fun to implement. The bouncer needs to move, and fade
- // in/out at the same time. The issue is, the bouncer should only move a short
- // amount (120dp or so), but obviously needs to go from one side of the screen to
- // the other. This needs a pretty custom animation.
- //
- // This works as follows. It uses a ValueAnimation to simply drive the animation
- // progress. This animator is responsible for both the translation of the bouncer,
- // and the current fade. It will fade the bouncer out while also moving it along the
- // 120dp path. Once the bouncer is fully faded out though, it will "snap" the
- // bouncer closer to its destination, then fade it back in again. The effect is that
- // the bouncer will move from 0 -> X while fading out, then
- // (destination - X) -> destination while fading back in again.
- // TODO(b/208250221): Make this animation properly abortable.
- Interpolator positionInterpolator = AnimationUtils.loadInterpolator(
- mView.getContext(), android.R.interpolator.fast_out_extra_slow_in);
- Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
- Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
-
- mRunningOneHandedAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
- mRunningOneHandedAnimator.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS);
- mRunningOneHandedAnimator.setInterpolator(Interpolators.LINEAR);
-
- int initialTranslation = (int) mViewFlipper.getTranslationX();
- int totalTranslation = (int) mView.getResources().getDimension(
- R.dimen.one_handed_bouncer_move_animation_translation);
-
- final boolean shouldRestoreLayerType = mViewFlipper.hasOverlappingRendering()
- && mViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE;
- if (shouldRestoreLayerType) {
- mViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
- }
-
- float initialAlpha = mViewFlipper.getAlpha();
-
- mRunningOneHandedAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mRunningOneHandedAnimator = null;
- }
- });
- mRunningOneHandedAnimator.addUpdateListener(animation -> {
- float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION;
- boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;
-
- int currentTranslation = (int) (positionInterpolator.getInterpolation(
- animation.getAnimatedFraction()) * totalTranslation);
- int translationRemaining = totalTranslation - currentTranslation;
-
- // Flip the sign if we're going from right to left.
- if (leftAlign) {
- currentTranslation = -currentTranslation;
- translationRemaining = -translationRemaining;
- }
-
- if (isFadingOut) {
- // The bouncer fades out over the first X%.
- float fadeOutFraction = MathUtils.constrainedMap(
- /* rangeMin= */1.0f,
- /* rangeMax= */0.0f,
- /* valueMin= */0.0f,
- /* valueMax= */switchPoint,
- animation.getAnimatedFraction());
- float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
-
- // When fading out, the alpha needs to start from the initial opacity of the
- // view flipper, otherwise we get a weird bit of jank as it ramps back to
- // 100%.
- mViewFlipper.setAlpha(opacity * initialAlpha);
-
- // Animate away from the source.
- mViewFlipper.setTranslationX(initialTranslation + currentTranslation);
- } else {
- // And in again over the remaining (100-X)%.
- float fadeInFraction = MathUtils.constrainedMap(
- /* rangeMin= */0.0f,
- /* rangeMax= */1.0f,
- /* valueMin= */switchPoint,
- /* valueMax= */1.0f,
- animation.getAnimatedFraction());
-
- float opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
- mViewFlipper.setAlpha(opacity);
-
- // Fading back in, animate towards the destination.
- mViewFlipper.setTranslationX(targetTranslation - translationRemaining);
- }
-
- if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
- mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
- }
- });
-
- mRunningOneHandedAnimator.start();
- } else {
- mViewFlipper.setTranslationX(targetTranslation);
- }
+ protected void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
+ translateSecurityViewLocation(leftAlign, animate);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 19a2d9ed5b7b..61e262440607 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -116,12 +116,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
// If we're in one handed mode, the user can tap on the opposite side of the screen
// to move the bouncer across. In that case, inhibit the falsing (otherwise the taps
// to move the bouncer to each screen side can end up closing it instead).
- if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) {
- boolean isLeftAligned = mView.isOneHandedModeLeftAligned();
- if ((isLeftAligned && ev.getX() > mView.getWidth() / 2f)
- || (!isLeftAligned && ev.getX() <= mView.getWidth() / 2f)) {
- mFalsingCollector.avoidGesture();
- }
+ if (mView.isTouchOnTheOtherSideOfSecurity(ev)) {
+ mFalsingCollector.avoidGesture();
}
if (mTouchDown != null) {
@@ -169,8 +165,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
int bouncerSide = SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__DEFAULT;
- if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) {
- bouncerSide = mView.isOneHandedModeLeftAligned()
+ if (mView.isSidedSecurityMode()) {
+ bouncerSide = mView.isSecurityLeftAligned()
? SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__LEFT
: SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__RIGHT;
}
@@ -367,8 +363,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
public void onResume(int reason) {
if (mCurrentSecurityMode != SecurityMode.None) {
int state = SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN;
- if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) {
- state = mView.isOneHandedModeLeftAligned()
+ if (mView.isSidedSecurityMode()) {
+ state = mView.isSecurityLeftAligned()
? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_LEFT
: SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_RIGHT;
}
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 61b1b66338e8..c5955860aebf 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -27,12 +27,15 @@ import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
+import android.hardware.biometrics.BiometricSourceType
import android.view.View
import androidx.core.graphics.ColorUtils
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.animation.Interpolators
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import java.util.concurrent.Executor
/**
* When the face is enrolled, we use this view to show the face scanning animation and the camera
@@ -42,7 +45,8 @@ class FaceScanningOverlay(
context: Context,
pos: Int,
val statusBarStateController: StatusBarStateController,
- val keyguardUpdateMonitor: KeyguardUpdateMonitor
+ val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ val mainExecutor: Executor
) : ScreenDecorations.DisplayCutoutView(context, pos) {
private var showScanningAnim = false
private val rimPaint = Paint()
@@ -54,11 +58,26 @@ class FaceScanningOverlay(
com.android.systemui.R.attr.wallpaperTextColorAccent)
private var cameraProtectionAnimator: ValueAnimator? = null
var hideOverlayRunnable: Runnable? = null
+ var faceAuthSucceeded = false
init {
visibility = View.INVISIBLE // only show this view when face scanning is happening
}
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ mainExecutor.execute {
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ }
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ mainExecutor.execute {
+ keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
+ }
+ }
+
override fun setColor(color: Int) {
cameraProtectionColor = color
invalidate()
@@ -108,7 +127,6 @@ class FaceScanningOverlay(
if (showScanningAnimNow == showScanningAnim) {
return
}
-
showScanningAnim = showScanningAnimNow
updateProtectionBoundingPath()
// Delay the relayout until the end of the animation when hiding,
@@ -120,13 +138,20 @@ class FaceScanningOverlay(
cameraProtectionAnimator?.cancel()
cameraProtectionAnimator = ValueAnimator.ofFloat(cameraProtectionProgress,
- if (showScanningAnimNow) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE).apply {
- startDelay = if (showScanningAnim) 0 else PULSE_DISAPPEAR_DURATION
- duration = if (showScanningAnim) PULSE_APPEAR_DURATION else
- CAMERA_PROTECTION_DISAPPEAR_DURATION
- interpolator = if (showScanningAnim) Interpolators.STANDARD else
- Interpolators.EMPHASIZED
-
+ if (showScanningAnimNow) SHOW_CAMERA_PROTECTION_SCALE
+ else HIDDEN_CAMERA_PROTECTION_SCALE).apply {
+ startDelay =
+ if (showScanningAnim) 0
+ else if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION
+ else PULSE_ERROR_DISAPPEAR_DURATION
+ duration =
+ if (showScanningAnim) CAMERA_PROTECTION_APPEAR_DURATION
+ else if (faceAuthSucceeded) CAMERA_PROTECTION_SUCCESS_DISAPPEAR_DURATION
+ else CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION
+ interpolator =
+ if (showScanningAnim) Interpolators.STANDARD_ACCELERATE
+ else if (faceAuthSucceeded) Interpolators.STANDARD
+ else Interpolators.STANDARD_DECELERATE
addUpdateListener(ValueAnimator.AnimatorUpdateListener {
animation: ValueAnimator ->
cameraProtectionProgress = animation.animatedValue as Float
@@ -143,47 +168,73 @@ class FaceScanningOverlay(
}
}
})
- start()
}
rimAnimator?.cancel()
rimAnimator = AnimatorSet().apply {
- val rimAppearOrDisappearAnimator = ValueAnimator.ofFloat(rimProgress,
- if (showScanningAnim) PULSE_RADIUS_OUT else (PULSE_RADIUS_IN * 1.15f)).apply {
- duration = if (showScanningAnim) PULSE_APPEAR_DURATION else PULSE_DISAPPEAR_DURATION
- interpolator = Interpolators.STANDARD
- addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- rimProgress = animation.animatedValue as Float
- invalidate()
- })
- }
if (showScanningAnim) {
- // appear and then pulse in/out
- playSequentially(rimAppearOrDisappearAnimator,
+ val rimAppearAnimator = ValueAnimator.ofFloat(SHOW_CAMERA_PROTECTION_SCALE,
+ PULSE_RADIUS_OUT).apply {
+ duration = PULSE_APPEAR_DURATION
+ interpolator = Interpolators.STANDARD_DECELERATE
+ addUpdateListener(ValueAnimator.AnimatorUpdateListener {
+ animation: ValueAnimator ->
+ rimProgress = animation.animatedValue as Float
+ invalidate()
+ })
+ }
+
+ // animate in camera protection, rim, and then pulse in/out
+ playSequentially(cameraProtectionAnimator, rimAppearAnimator,
createPulseAnimator(), createPulseAnimator(),
createPulseAnimator(), createPulseAnimator(),
createPulseAnimator(), createPulseAnimator())
} else {
- val opacityAnimator = ValueAnimator.ofInt(255, 0).apply {
- duration = PULSE_DISAPPEAR_DURATION
- interpolator = Interpolators.LINEAR
+ val rimDisappearAnimator = ValueAnimator.ofFloat(
+ rimProgress,
+ if (faceAuthSucceeded) PULSE_RADIUS_SUCCESS
+ else SHOW_CAMERA_PROTECTION_SCALE
+ ).apply {
+ duration =
+ if (faceAuthSucceeded) PULSE_SUCCESS_DISAPPEAR_DURATION
+ else PULSE_ERROR_DISAPPEAR_DURATION
+ interpolator =
+ if (faceAuthSucceeded) Interpolators.STANDARD_DECELERATE
+ else Interpolators.STANDARD
addUpdateListener(ValueAnimator.AnimatorUpdateListener {
animation: ValueAnimator ->
- rimPaint.alpha = animation.animatedValue as Int
+ rimProgress = animation.animatedValue as Float
invalidate()
})
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ rimProgress = HIDDEN_RIM_SCALE
+ invalidate()
+ }
+ })
}
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- rimProgress = HIDDEN_RIM_SCALE
- rimPaint.alpha = 255
- invalidate()
+ if (faceAuthSucceeded) {
+ val successOpacityAnimator = ValueAnimator.ofInt(255, 0).apply {
+ duration = PULSE_SUCCESS_DISAPPEAR_DURATION
+ interpolator = Interpolators.LINEAR
+ addUpdateListener(ValueAnimator.AnimatorUpdateListener {
+ animation: ValueAnimator ->
+ rimPaint.alpha = animation.animatedValue as Int
+ invalidate()
+ })
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ rimPaint.alpha = 255
+ invalidate()
+ }
+ })
}
- })
-
- // disappear
- playTogether(rimAppearOrDisappearAnimator, opacityAnimator)
+ val rimSuccessAnimator = AnimatorSet()
+ rimSuccessAnimator.playTogether(rimDisappearAnimator, successOpacityAnimator)
+ playTogether(rimSuccessAnimator, cameraProtectionAnimator)
+ } else {
+ playTogether(rimDisappearAnimator, cameraProtectionAnimator)
+ }
}
addListener(object : AnimatorListenerAdapter() {
@@ -253,15 +304,72 @@ class FaceScanningOverlay(
}
}
+ private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricAuthenticated(
+ userId: Int,
+ biometricSourceType: BiometricSourceType?,
+ isStrongBiometric: Boolean
+ ) {
+ if (biometricSourceType == BiometricSourceType.FACE) {
+ post {
+ faceAuthSucceeded = true
+ enableShowProtection(true)
+ }
+ }
+ }
+
+ override fun onBiometricAcquired(
+ biometricSourceType: BiometricSourceType?,
+ acquireInfo: Int
+ ) {
+ if (biometricSourceType == BiometricSourceType.FACE) {
+ post {
+ faceAuthSucceeded = false // reset
+ }
+ }
+ }
+
+ override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
+ if (biometricSourceType == BiometricSourceType.FACE) {
+ post {
+ faceAuthSucceeded = false
+ enableShowProtection(false)
+ }
+ }
+ }
+
+ override fun onBiometricError(
+ msgId: Int,
+ errString: String?,
+ biometricSourceType: BiometricSourceType?
+ ) {
+ if (biometricSourceType == BiometricSourceType.FACE) {
+ post {
+ faceAuthSucceeded = false
+ enableShowProtection(false)
+ }
+ }
+ }
+ }
+
companion object {
private const val HIDDEN_RIM_SCALE = HIDDEN_CAMERA_PROTECTION_SCALE
+ private const val SHOW_CAMERA_PROTECTION_SCALE = 1f
+
+ private const val PULSE_RADIUS_IN = 1.1f
+ private const val PULSE_RADIUS_OUT = 1.125f
+ private const val PULSE_RADIUS_SUCCESS = 1.25f
+
+ private const val CAMERA_PROTECTION_APPEAR_DURATION = 250L
+ private const val PULSE_APPEAR_DURATION = 250L // without start delay
- private const val PULSE_APPEAR_DURATION = 350L
private const val PULSE_DURATION_INWARDS = 500L
private const val PULSE_DURATION_OUTWARDS = 500L
- private const val PULSE_DISAPPEAR_DURATION = 850L
- private const val CAMERA_PROTECTION_DISAPPEAR_DURATION = 700L // excluding start delay
- private const val PULSE_RADIUS_IN = 1.15f
- private const val PULSE_RADIUS_OUT = 1.25f
+
+ private const val PULSE_SUCCESS_DISAPPEAR_DURATION = 400L
+ private const val CAMERA_PROTECTION_SUCCESS_DISAPPEAR_DURATION = 500L // without start delay
+
+ private const val PULSE_ERROR_DISAPPEAR_DURATION = 200L
+ private const val CAMERA_PROTECTION_ERROR_DISAPPEAR_DURATION = 300L // without start delay
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index ec4cf2fd8bd4..24b893340ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -510,6 +510,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
mKeyguardViewManager.isBouncerInTransit() ? BouncerPanelExpansionCalculator
.aboutToShowBouncerProgress(fraction) : fraction;
updateAlpha();
+ updatePauseAuth();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index aeda20f16515..afc58ef70ee1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -45,6 +45,7 @@ import android.content.pm.ShortcutManager;
import android.content.res.Resources;
import android.hardware.SensorManager;
import android.hardware.SensorPrivacyManager;
+import android.hardware.camera2.CameraManager;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.ColorDisplayManager;
@@ -60,6 +61,7 @@ import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
import android.os.BatteryStats;
+import android.os.PowerExemptionManager;
import android.os.PowerManager;
import android.os.ServiceManager;
import android.os.UserManager;
@@ -377,6 +379,13 @@ public class FrameworkServicesModule {
/** */
@Provides
+ @Singleton
+ static PowerExemptionManager providePowerExemptionManager(Context context) {
+ return context.getSystemService(PowerExemptionManager.class);
+ }
+
+ /** */
+ @Provides
@Main
public SharedPreferences provideSharePreferences(Context context) {
return Prefs.get(context);
@@ -545,4 +554,10 @@ public class FrameworkServicesModule {
static SafetyCenterManager provideSafetyCenterManager(Context context) {
return context.getSystemService(SafetyCenterManager.class);
}
+
+ @Provides
+ @Singleton
+ static CameraManager provideCameraManager(Context context) {
+ return context.getSystemService(CameraManager.class);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index adc0096eed37..81d3d6caebd7 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -32,10 +32,12 @@ import android.widget.FrameLayout
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.FaceScanningOverlay
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import java.util.concurrent.Executor
import javax.inject.Inject
@SysUISingleton
@@ -44,6 +46,7 @@ class FaceScanningProviderFactory @Inject constructor(
private val context: Context,
private val statusBarStateController: StatusBarStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ @Main private val mainExecutor: Executor,
private val featureFlags: FeatureFlags
) : DecorProviderFactory() {
private val display = context.display
@@ -82,7 +85,9 @@ class FaceScanningProviderFactory @Inject constructor(
bound.baseOnRotation0(displayInfo.rotation),
authController,
statusBarStateController,
- keyguardUpdateMonitor)
+ keyguardUpdateMonitor,
+ mainExecutor
+ )
)
}
}
@@ -102,7 +107,8 @@ class FaceScanningOverlayProviderImpl(
override val alignedBound: Int,
private val authController: AuthController,
private val statusBarStateController: StatusBarStateController,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val mainExecutor: Executor
) : BoundDecorProvider() {
override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
@@ -127,7 +133,9 @@ class FaceScanningOverlayProviderImpl(
context,
alignedBound,
statusBarStateController,
- keyguardUpdateMonitor)
+ keyguardUpdateMonitor,
+ mainExecutor
+ )
view.id = viewId
FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT).let {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index fc71e2fb2329..69e41ba9b284 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -101,6 +101,9 @@ public class DreamOverlayStateController implements
public void addComplication(Complication complication) {
mExecutor.execute(() -> {
if (mComplications.add(complication)) {
+ if (DEBUG) {
+ Log.d(TAG, "addComplication: added " + complication);
+ }
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
@@ -112,6 +115,9 @@ public class DreamOverlayStateController implements
public void removeComplication(Complication complication) {
mExecutor.execute(() -> {
if (mComplications.remove(complication)) {
+ if (DEBUG) {
+ Log.d(TAG, "removeComplication: removed " + complication);
+ }
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
index 486fc893732f..be94e5031917 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
@@ -82,6 +82,7 @@ public class SmartSpaceComplication implements Complication {
mSmartSpaceController.addListener(mSmartspaceListener);
} else {
mSmartSpaceController.removeListener(mSmartspaceListener);
+ mDreamOverlayStateController.removeComplication(mComplication);
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index c1173aea8b15..fd6cfc0700ad 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -21,6 +21,7 @@ import static com.android.systemui.dreams.complication.dagger.ComplicationModule
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Debug;
import android.util.Log;
import android.view.View;
@@ -44,7 +45,8 @@ import javax.inject.Named;
* a {@link ComplicationLayoutEngine}.
*/
public class ComplicationHostViewController extends ViewController<ConstraintLayout> {
- public static final String TAG = "ComplicationHostVwCtrl";
+ private static final String TAG = "ComplicationHostVwCtrl";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ComplicationLayoutEngine mLayoutEngine;
private final LifecycleOwner mLifecycleOwner;
@@ -90,6 +92,11 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
}
private void updateComplications(Collection<ComplicationViewModel> complications) {
+ if (DEBUG) {
+ Log.d(TAG, "updateComplications called. Callers = " + Debug.getCallers(25));
+ Log.d(TAG, " mComplications = " + mComplications.toString());
+ Log.d(TAG, " complications = " + complications.toString());
+ }
final Collection<ComplicationId> ids = complications.stream()
.map(complicationViewModel -> complicationViewModel.getId())
.collect(Collectors.toSet());
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index ded61a8b80d9..9cd149b9bfee 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -54,7 +54,7 @@ import javax.inject.Named;
*/
@DreamOverlayComponent.DreamOverlayScope
public class ComplicationLayoutEngine implements Complication.VisibilityController {
- public static final String TAG = "ComplicationLayoutEngine";
+ public static final String TAG = "ComplicationLayoutEng";
/**
* {@link ViewEntry} is an internal container, capturing information necessary for working with
@@ -529,7 +529,7 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll
*/
public void addComplication(ComplicationId id, View view,
ComplicationLayoutParams lp, @Complication.Category int category) {
- Log.d(TAG, "engine: " + this + " addComplication");
+ Log.d(TAG, "@" + Integer.toHexString(this.hashCode()) + " addComplication: " + id);
// If the complication is present, remove.
if (mEntries.containsKey(id)) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
index f0239371ee63..00cf58c0c665 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
@@ -64,4 +64,9 @@ public class ComplicationViewModel extends ViewModel {
public void exitDream() {
mHost.requestExitDream();
}
+
+ @Override
+ public String toString() {
+ return mId + "=" + mComplication.toString();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 9789cef949d3..63f63a5093d2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -145,9 +145,6 @@ class DreamSmartspaceController @Inject constructor(
if (view !is View) {
return null
}
-
- view.setIsDreaming(true)
-
return view
} else {
null
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 47c678b4b8cf..47a68bbbc8c0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -60,6 +60,9 @@ public class Flags {
public static final ResourceBooleanFlag NOTIFICATION_DRAG_TO_CONTENTS =
new ResourceBooleanFlag(108, R.bool.config_notificationToContents);
+ public static final BooleanFlag REMOVE_UNRANKED_NOTIFICATIONS =
+ new BooleanFlag(109, false);
+
/***************************************/
// 200 - keyguard/lockscreen
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 550f393f4ca3..ab30db297fce 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -54,6 +54,7 @@ import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -1044,7 +1045,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
@Override
public boolean showBeforeProvisioning() {
- return false;
+ return Build.isDebuggable() && mGlobalSettings.getInt(
+ Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index b96eee717260..95b3b3f6452f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -55,6 +55,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Trace;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.view.IRemoteAnimationFinishedCallback;
@@ -157,7 +158,7 @@ public class KeyguardService extends Service {
Rect localBounds = new Rect(change.getEndAbsBounds());
localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
- out.add(new RemoteAnimationTarget(
+ final RemoteAnimationTarget target = new RemoteAnimationTarget(
taskId,
newModeToLegacyMode(change.getMode()),
change.getLeash(),
@@ -168,7 +169,15 @@ public class KeyguardService extends Service {
info.getChanges().size() - i,
new Point(), localBounds, new Rect(change.getEndAbsBounds()),
windowConfiguration, isNotInRecents, null /* startLeash */,
- change.getStartAbsBounds(), taskInfo, false /* allowEnterPip */));
+ change.getStartAbsBounds(), taskInfo, false /* allowEnterPip */);
+ // Use hasAnimatingParent to mark the anything below root task
+ if (taskId != -1 && change.getParent() != null) {
+ final TransitionInfo.Change parentChange = info.getChange(change.getParent());
+ if (parentChange != null && parentChange.getTaskInfo() != null) {
+ target.hasAnimatingParent = true;
+ }
+ }
+ out.add(target);
}
return out.toArray(new RemoteAnimationTarget[out.size()]);
}
@@ -189,8 +198,12 @@ public class KeyguardService extends Service {
}
}
+ // Wrap Keyguard going away animation
private static IRemoteTransition wrap(IRemoteAnimationRunner runner) {
return new IRemoteTransition.Stub() {
+ final ArrayMap<IBinder, IRemoteTransitionFinishedCallback> mFinishCallbacks =
+ new ArrayMap<>();
+
@Override
public void startAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)
@@ -200,16 +213,37 @@ public class KeyguardService extends Service {
final RemoteAnimationTarget[] wallpapers = wrap(info, true /* wallpapers */);
final RemoteAnimationTarget[] nonApps = new RemoteAnimationTarget[0];
- // TODO: Remove this, and update alpha value in the IAnimationRunner.
- for (TransitionInfo.Change change : info.getChanges()) {
- t.setAlpha(change.getLeash(), 1.0f);
+ // Sets the alpha to 0 for the opening root task for fade in animation. And since
+ // the fade in animation can only apply on the first opening app, so set alpha to 1
+ // for anything else.
+ boolean foundOpening = false;
+ for (RemoteAnimationTarget target : apps) {
+ if (target.taskId != -1
+ && target.mode == RemoteAnimationTarget.MODE_OPENING
+ && !target.hasAnimatingParent) {
+ if (foundOpening) {
+ Log.w(TAG, "More than one opening target");
+ t.setAlpha(target.leash, 1.0f);
+ continue;
+ }
+ t.setAlpha(target.leash, 0.0f);
+ foundOpening = true;
+ } else {
+ t.setAlpha(target.leash, 1.0f);
+ }
}
t.apply();
+ synchronized (mFinishCallbacks) {
+ mFinishCallbacks.put(transition, finishCallback);
+ }
runner.onAnimationStart(getTransitionOldType(info.getType(), info.getFlags(), apps),
apps, wallpapers, nonApps,
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() throws RemoteException {
+ synchronized (mFinishCallbacks) {
+ if (mFinishCallbacks.remove(transition) == null) return;
+ }
Slog.d(TAG, "Finish IRemoteAnimationRunner.");
finishCallback.onTransitionFinished(null /* wct */, null /* t */);
}
@@ -220,7 +254,20 @@ public class KeyguardService extends Service {
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) {
-
+ try {
+ final IRemoteTransitionFinishedCallback origFinishCB;
+ synchronized (mFinishCallbacks) {
+ origFinishCB = mFinishCallbacks.remove(transition);
+ }
+ if (origFinishCB == null) {
+ // already finished (or not started yet), so do nothing.
+ return;
+ }
+ runner.onAnimationCancelled(false /* isKeyguardOccluded */);
+ origFinishCB.onTransitionFinished(null /* wct */, null /* t */);
+ } catch (RemoteException e) {
+ // nothing, we'll just let it finish on its own I guess.
+ }
}
};
}
@@ -349,7 +396,7 @@ public class KeyguardService extends Service {
}
@Override // Binder interface
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
mKeyguardViewMediator.cancelKeyguardExitAnimation();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 99b57200a397..382323f3aed9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -26,6 +26,7 @@ import android.os.Handler
import android.os.RemoteException
import android.util.Log
import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
import androidx.annotation.VisibleForTesting
@@ -293,6 +294,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
private val handler = Handler()
+ private val tmpFloat = FloatArray(9)
+
init {
with(surfaceBehindAlphaAnimator) {
duration = SURFACE_BEHIND_SWIPE_FADE_DURATION_MS
@@ -723,13 +726,27 @@ class KeyguardUnlockAnimationController @Inject constructor(
if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount
else surfaceBehindAlpha
- applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceBehindRemoteAnimationTarget!!.leash)
- .withMatrix(surfaceBehindMatrix)
- .withCornerRadius(roundedCornerRadius)
- .withAlpha(animationAlpha)
- .build())
+ // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is unable
+ // to draw
+ val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget?.leash
+ if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true) {
+ with(SurfaceControl.Transaction()) {
+ setMatrix(sc, surfaceBehindMatrix, tmpFloat)
+ setCornerRadius(sc, roundedCornerRadius)
+ setAlpha(sc, animationAlpha)
+ apply()
+ }
+ } else {
+ applyParamsToSurface(
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceBehindRemoteAnimationTarget!!.leash)
+ .withMatrix(surfaceBehindMatrix)
+ .withCornerRadius(roundedCornerRadius)
+ .withAlpha(animationAlpha)
+ .build()
+ )
+ }
}
/**
@@ -744,8 +761,11 @@ class KeyguardUnlockAnimationController @Inject constructor(
handler.removeCallbacksAndMessages(null)
// Make sure we made the surface behind fully visible, just in case. It should already be
- // fully visible. If the launcher is doing its own animation, let it continue without
- // forcing it to 1f.
+ // fully visible. The exit animation is finished, and we should not hold the leash anymore,
+ // so forcing it to 1f.
+ surfaceBehindAlphaAnimator.cancel()
+ surfaceBehindEntryAnimator.cancel()
+ surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f)
launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
@@ -910,4 +930,4 @@ class KeyguardUnlockAnimationController @Inject constructor(
return context.resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty()
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index f9a1c66af41d..340cde11c6e7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -843,7 +843,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
@Override
public void onLaunchAnimationCancelled() {
- setOccluded(true /* occluded */, false /* animate */);
Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: "
+ mOccluded);
}
@@ -911,12 +910,12 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
private final Matrix mUnoccludeMatrix = new Matrix();
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
if (mUnoccludeAnimator != null) {
mUnoccludeAnimator.cancel();
}
- setOccluded(false /* isOccluded */, false /* animate */);
+ setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
+ mOccluded);
}
@@ -2503,10 +2502,18 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
mInteractionJankMonitor.begin(
createInteractionJankMonitorConf("DismissPanel"));
+ // Apply the opening animation on root task if exists
+ RemoteAnimationTarget aniTarget = apps[0];
+ for (RemoteAnimationTarget tmpTarget : apps) {
+ if (tmpTarget.taskId != -1 && !tmpTarget.hasAnimatingParent) {
+ aniTarget = tmpTarget;
+ break;
+ }
+ }
// Pass the surface and metadata to the unlock animation controller.
mKeyguardUnlockAnimationControllerLazy.get()
.notifyStartSurfaceBehindRemoteAnimation(
- apps[0], startTime, mSurfaceBehindRemoteAnimationRequested);
+ aniTarget, startTime, mSurfaceBehindRemoteAnimationRequested);
} else {
mInteractionJankMonitor.begin(
createInteractionJankMonitorConf("RemoteAnimationDisabled"));
@@ -3167,9 +3174,9 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
}
@Override
- public void onAnimationCancelled() throws RemoteException {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
if (mRunner != null) {
- mRunner.onAnimationCancelled();
+ mRunner.onAnimationCancelled(isKeyguardOccluded);
}
}
@@ -3210,9 +3217,12 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
}
@Override
- public void onAnimationCancelled() throws RemoteException {
- super.onAnimationCancelled();
- Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: " + mOccluded);
+ public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
+ super.onAnimationCancelled(isKeyguardOccluded);
+ setOccluded(isKeyguardOccluded /* occluded */, false /* animate */);
+
+ Log.d(TAG, "Occlude animation cancelled by WM. "
+ + "Setting occluded state to: " + mOccluded);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 90cca15ddf21..d0da18aaba05 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -27,6 +27,8 @@ import com.android.systemui.log.LogBufferFactory;
import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.log.LogcatEchoTrackerDebug;
import com.android.systemui.log.LogcatEchoTrackerProd;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.util.Compile;
import dagger.Module;
import dagger.Provides;
@@ -48,8 +50,14 @@ public class LogModule {
@Provides
@SysUISingleton
@NotificationLog
- public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) {
- return factory.create("NotifLog", 1000 /* maxSize */, false /* systrace */);
+ public static LogBuffer provideNotificationsLogBuffer(
+ LogBufferFactory factory,
+ NotifPipelineFlags notifPipelineFlags) {
+ int maxSize = 1000;
+ if (Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()) {
+ maxSize *= 10;
+ }
+ return factory.create("NotifLog", maxSize, false /* systrace */);
}
/** Provides a logging buffer for logs related to heads up presentation of notifications. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index ed5c1933af25..2f732de50ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -23,6 +23,7 @@ import android.annotation.IntDef
import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
+import android.util.Log
import android.util.MathUtils
import android.view.View
import android.view.ViewGroup
@@ -48,6 +49,8 @@ import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.traceSection
import javax.inject.Inject
+private val TAG: String = MediaHierarchyManager::class.java.simpleName
+
/**
* Similarly to isShown but also excludes views that have 0 alpha
*/
@@ -964,6 +967,14 @@ class MediaHierarchyManager @Inject constructor(
top,
left + currentBounds.width(),
top + currentBounds.height())
+
+ if (mediaFrame.childCount > 0) {
+ val child = mediaFrame.getChildAt(0)
+ if (mediaFrame.height < child.height) {
+ Log.wtf(TAG, "mediaFrame height is too small for child: " +
+ "${mediaFrame.height} vs ${child.height}")
+ }
+ }
}
if (isCrossFadeAnimatorRunning) {
// When cross-fading with an animation, we only notify the media carousel of the
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index f0ce30d2dc66..f2f275323d58 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -285,6 +285,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
Log.d(TAG, "This device is already connected! : " + device.getName());
return;
}
+ mController.setTemporaryAllowListExceptionIfNeeded(device);
mCurrentActivePosition = -1;
mController.connectDevice(device);
device.setState(MediaDeviceState.STATE_CONNECTING);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index ccc0a3db0611..bec67397a926 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -69,11 +69,13 @@ public abstract class MediaOutputBaseAdapter extends
View mHolderView;
boolean mIsDragging;
int mCurrentActivePosition;
+ private boolean mIsInitVolumeFirstTime;
public MediaOutputBaseAdapter(MediaOutputController controller) {
mController = controller;
mIsDragging = false;
mCurrentActivePosition = -1;
+ mIsInitVolumeFirstTime = true;
}
@Override
@@ -275,7 +277,7 @@ public abstract class MediaOutputBaseAdapter extends
mSeekBar.setMaxVolume(device.getMaxVolume());
final int currentVolume = device.getCurrentVolume();
if (mSeekBar.getVolume() != currentVolume) {
- if (isCurrentSeekbarInvisible) {
+ if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) {
animateCornerAndVolume(mSeekBar.getProgress(),
MediaOutputSeekbar.scaleVolumeToProgress(currentVolume));
} else {
@@ -284,6 +286,9 @@ public abstract class MediaOutputBaseAdapter extends
}
}
}
+ if (mIsInitVolumeFirstTime) {
+ mIsInitVolumeFirstTime = false;
+ }
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
index 38005db28cf6..0fa326573c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
@@ -19,6 +19,7 @@ package com.android.systemui.media.dialog
import android.content.Context
import android.media.AudioManager
import android.media.session.MediaSessionManager
+import android.os.PowerExemptionManager
import android.view.View
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.LocalBluetoothManager
@@ -43,7 +44,8 @@ class MediaOutputBroadcastDialogFactory @Inject constructor(
private val uiEventLogger: UiEventLogger,
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
- private val audioManager: AudioManager
+ private val audioManager: AudioManager,
+ private val powerExemptionManager: PowerExemptionManager
) {
var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
@@ -54,7 +56,8 @@ class MediaOutputBroadcastDialogFactory @Inject constructor(
val controller = MediaOutputController(context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
- dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager)
+ dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
+ powerExemptionManager)
val dialog =
MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 0b4b03619a13..247ffa7c2ef9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -44,6 +44,7 @@ import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.IBinder;
+import android.os.PowerExemptionManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -101,6 +102,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String PAGE_CONNECTED_DEVICES_KEY =
"top_level_connected_devices";
+ private static final long ALLOWLIST_DURATION_MS = 20000;
+ private static final String ALLOWLIST_REASON = "mediaoutput:remote_transfer";
+
private final String mPackageName;
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
@@ -114,6 +118,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
private final AudioManager mAudioManager;
+ private final PowerExemptionManager mPowerExemptionManager;
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager;
private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>();
@@ -147,7 +152,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
CommonNotifCollection notifCollection,
DialogLaunchAnimator dialogLaunchAnimator,
Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional,
- AudioManager audioManager) {
+ AudioManager audioManager,
+ PowerExemptionManager powerExemptionManager) {
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
@@ -155,6 +161,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
mActivityStarter = starter;
mNotifCollection = notifCollection;
mAudioManager = audioManager;
+ mPowerExemptionManager = powerExemptionManager;
InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
@@ -776,7 +783,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager),
- mAudioManager);
+ mAudioManager, mPowerExemptionManager);
MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
broadcastSender, controller);
mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
@@ -822,6 +829,17 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
broadcast.setBroadcastCode(broadcastCode.getBytes(StandardCharsets.UTF_8));
}
+ void setTemporaryAllowListExceptionIfNeeded(MediaDevice targetDevice) {
+ if (mPowerExemptionManager == null || mPackageName == null) {
+ Log.w(TAG, "powerExemptionManager or package name is null");
+ return;
+ }
+ mPowerExemptionManager.addToTemporaryAllowList(mPackageName,
+ PowerExemptionManager.REASON_MEDIA_NOTIFICATION_TRANSFER,
+ ALLOWLIST_REASON,
+ ALLOWLIST_DURATION_MS);
+ }
+
String getBroadcastMetadata() {
LocalBluetoothLeBroadcast broadcast =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 8701d4aba720..8249a7c0779c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -19,6 +19,7 @@ package com.android.systemui.media.dialog
import android.content.Context
import android.media.AudioManager
import android.media.session.MediaSessionManager
+import android.os.PowerExemptionManager
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
@@ -45,7 +46,8 @@ class MediaOutputDialogFactory @Inject constructor(
private val uiEventLogger: UiEventLogger,
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
- private val audioManager: AudioManager
+ private val audioManager: AudioManager,
+ private val powerExemptionManager: PowerExemptionManager
) {
companion object {
private const val INTERACTION_JANK_TAG = "media_output"
@@ -60,8 +62,8 @@ class MediaOutputDialogFactory @Inject constructor(
val controller = MediaOutputController(
context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
- dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager
- )
+ dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
+ powerExemptionManager)
val dialog =
MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger)
mediaOutputDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
index 7c0481049098..2c35db337cda 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
@@ -37,11 +37,6 @@ public class MediaDreamComplication implements Complication {
}
@Override
- public int getRequiredTypeAvailability() {
- return COMPLICATION_TYPE_CAST_INFO;
- }
-
- @Override
public ViewHolder createView(ComplicationViewModel model) {
return mComponentFactory.create().getViewHolder();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
index 3cc99a8ef77e..e95976f555f8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
@@ -27,4 +27,4 @@ interface ChipInfoCommon {
fun getTimeoutMs(): Long
}
-const val DEFAULT_TIMEOUT_MILLIS = 3000L
+const val DEFAULT_TIMEOUT_MILLIS = 4000L
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 7cc52e428218..0f1cdcc3fa5b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -25,13 +25,14 @@ import android.graphics.drawable.Drawable
import android.os.PowerManager
import android.os.SystemClock
import android.util.Log
-import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
-import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
-import android.widget.LinearLayout
+import android.view.accessibility.AccessibilityManager
+import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
+import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
+import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
@@ -56,16 +57,20 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
private val windowManager: WindowManager,
private val viewUtil: ViewUtil,
@Main private val mainExecutor: DelayableExecutor,
+ private val accessibilityManager: AccessibilityManager,
private val tapGestureDetector: TapGestureDetector,
private val powerManager: PowerManager,
@LayoutRes private val chipLayoutRes: Int
) {
- /** The window layout parameters we'll use when attaching the view to a window. */
+
+ /**
+ * Window layout params that will be used as a starting point for the [windowLayoutParams] of
+ * all subclasses.
+ */
@SuppressLint("WrongConstant") // We're allowed to use TYPE_VOLUME_OVERLAY
- private val windowLayoutParams = WindowManager.LayoutParams().apply {
+ internal val commonWindowLayoutParams = WindowManager.LayoutParams().apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
- gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = WINDOW_TITLE
@@ -73,6 +78,14 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
setTrustedOverlay()
}
+ /**
+ * The window layout parameters we'll use when attaching the view to a window.
+ *
+ * Subclasses must override this to provide their specific layout params, and they should use
+ * [commonWindowLayoutParams] as part of their layout params.
+ */
+ internal abstract val windowLayoutParams: WindowManager.LayoutParams
+
/** The chip view currently being displayed. Null if the chip is not being displayed. */
private var chipView: ViewGroup? = null
@@ -110,10 +123,16 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
}
// Cancel and re-set the chip timeout each time we get a new state.
+ val timeout = accessibilityManager.getRecommendedTimeoutMillis(
+ chipInfo.getTimeoutMs().toInt(),
+ // Not all chips have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
+ // include it just to be safe.
+ FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
+ )
cancelChipViewTimeout?.run()
cancelChipViewTimeout = mainExecutor.executeDelayed(
{ removeChip(MediaTttRemovalReason.REASON_TIMEOUT) },
- chipInfo.getTimeoutMs()
+ timeout.toLong()
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 072263fcf38c..f9818f0ad8be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -24,8 +24,11 @@ import android.media.MediaRoute2Info
import android.os.Handler
import android.os.PowerManager
import android.util.Log
+import android.view.Gravity
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
@@ -35,6 +38,7 @@ import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCom
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
@@ -52,6 +56,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
windowManager: WindowManager,
viewUtil: ViewUtil,
mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
tapGestureDetector: TapGestureDetector,
powerManager: PowerManager,
@Main private val mainHandler: Handler,
@@ -62,10 +67,16 @@ class MediaTttChipControllerReceiver @Inject constructor(
windowManager,
viewUtil,
mainExecutor,
+ accessibilityManager,
tapGestureDetector,
powerManager,
R.layout.media_ttt_chip_receiver
) {
+ override val windowLayoutParams = commonWindowLayoutParams.apply {
+ height = getWindowHeight()
+ gravity = Gravity.BOTTOM.or(Gravity.CENTER_HORIZONTAL)
+ }
+
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
@@ -128,6 +139,19 @@ class MediaTttChipControllerReceiver @Inject constructor(
)
}
+ override fun animateChipIn(chipView: ViewGroup) {
+ val appIconView = chipView.requireViewById<View>(R.id.app_icon)
+ appIconView.animate()
+ .translationYBy(-1 * getTranslationAmount().toFloat())
+ .setDuration(30.frames)
+ .start()
+ appIconView.animate()
+ .alpha(1f)
+ .setDuration(5.frames)
+ .start()
+
+ }
+
override fun getIconSize(isAppIcon: Boolean): Int? =
context.resources.getDimensionPixelSize(
if (isAppIcon) {
@@ -136,6 +160,17 @@ class MediaTttChipControllerReceiver @Inject constructor(
R.dimen.media_ttt_generic_icon_size_receiver
}
)
+
+ private fun getWindowHeight(): Int {
+ return context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver) +
+ // Make the window large enough to accommodate the animation amount
+ getTranslationAmount()
+ }
+
+ /** Returns the amount that the chip will be translated by in its intro animation. */
+ private fun getTranslationAmount(): Int {
+ return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
+ }
}
data class ChipReceiverInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 54b4380e2443..797a7701413b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -21,9 +21,11 @@ import android.content.Context
import android.media.MediaRoute2Info
import android.os.PowerManager
import android.util.Log
+import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
@@ -53,6 +55,7 @@ class MediaTttChipControllerSender @Inject constructor(
windowManager: WindowManager,
viewUtil: ViewUtil,
@Main mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
tapGestureDetector: TapGestureDetector,
powerManager: PowerManager,
private val uiEventLogger: MediaTttSenderUiEventLogger
@@ -62,10 +65,15 @@ class MediaTttChipControllerSender @Inject constructor(
windowManager,
viewUtil,
mainExecutor,
+ accessibilityManager,
tapGestureDetector,
powerManager,
R.layout.media_ttt_chip
) {
+ override val windowLayoutParams = commonWindowLayoutParams.apply {
+ gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
+ }
+
private var currentlyDisplayedChipState: ChipStateSender? = null
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index f1dd5ffd2077..8179d1763ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -291,7 +291,8 @@ class BackPanelController private constructor(
override fun onMotionEvent(event: MotionEvent) {
backAnimation?.onBackMotion(
- event,
+ event.x,
+ event.y,
event.actionMasked,
if (mView.isLeftPanel) BackEvent.EDGE_LEFT else BackEvent.EDGE_RIGHT
)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index e210d685a370..3039d9d56c80 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -84,13 +84,16 @@ import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.back.BackAnimation;
+import com.android.wm.shell.pip.Pip;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import javax.inject.Inject;
@@ -147,16 +150,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mPackageName = "_UNKNOWN";
}
}
-
- @Override
- public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
- mIsInPipMode = true;
- }
-
- @Override
- public void onActivityUnpinned() {
- mIsInPipMode = false;
- }
};
private DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
@@ -188,6 +181,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private final ViewConfiguration mViewConfiguration;
private final WindowManager mWindowManager;
private final IWindowManager mWindowManagerService;
+ private final Optional<Pip> mPipOptional;
private final FalsingManager mFalsingManager;
// Activities which should not trigger Back gesture.
private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>();
@@ -218,6 +212,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
// We temporarily disable back gesture when user is quickswitching
// between apps of different orientations
private boolean mDisabledForQuickstep;
+ // This gets updated when the value of PipTransitionState#isInPip changes.
+ private boolean mIsInPip;
private final PointF mDownPoint = new PointF();
private final PointF mEndPoint = new PointF();
@@ -233,7 +229,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private boolean mIsNavBarShownTransiently;
private boolean mIsBackGestureAllowed;
private boolean mGestureBlockingActivityRunning;
- private boolean mIsInPipMode;
private boolean mIsNewBackAffordanceEnabled;
private InputMonitor mInputMonitor;
@@ -302,6 +297,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
}
};
+ private final Consumer<Boolean> mOnIsInPipStateChangedListener =
+ (isInPip) -> mIsInPip = isInPip;
EdgeBackGestureHandler(
Context context,
@@ -316,6 +313,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
ViewConfiguration viewConfiguration,
WindowManager windowManager,
IWindowManager windowManagerService,
+ Optional<Pip> pipOptional,
FalsingManager falsingManager,
LatencyTracker latencyTracker,
FeatureFlags featureFlags) {
@@ -332,6 +330,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mViewConfiguration = viewConfiguration;
mWindowManager = windowManager;
mWindowManagerService = windowManagerService;
+ mPipOptional = pipOptional;
mFalsingManager = falsingManager;
mLatencyTracker = latencyTracker;
mFeatureFlags = featureFlags;
@@ -491,6 +490,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mPluginManager.removePluginListener(this);
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
+ mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(null));
try {
mWindowManagerService.unregisterSystemGestureExclusionListener(
@@ -508,6 +508,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
mMainExecutor::execute, mOnPropertiesChangedListener);
+ mPipOptional.ifPresent(
+ pip -> pip.setOnIsInPipStateChangedListener(mOnIsInPipStateChangedListener));
try {
mWindowManagerService.registerSystemGestureExclusionListener(
@@ -680,7 +682,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private boolean isWithinTouchRegion(int x, int y) {
// If the point is inside the PiP or Nav bar overlay excluded bounds, then ignore the back
// gesture
- final boolean isInsidePip = mIsInPipMode && mPipExcludedBounds.contains(x, y);
+ final boolean isInsidePip = mIsInPip && mPipExcludedBounds.contains(x, y);
if (isInsidePip || mNavBarOverlayExcludedBounds.contains(x, y)) {
return false;
}
@@ -933,7 +935,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
pw.println(" mInRejectedExclusion=" + mInRejectedExclusion);
pw.println(" mExcludeRegion=" + mExcludeRegion);
pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion);
- pw.println(" mIsInPipMode=" + mIsInPipMode);
+ pw.println(" mIsInPip=" + mIsInPip);
pw.println(" mPipExcludedBounds=" + mPipExcludedBounds);
pw.println(" mNavBarOverlayExcludedBounds=" + mNavBarOverlayExcludedBounds);
pw.println(" mEdgeWidthLeft=" + mEdgeWidthLeft);
@@ -1002,6 +1004,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private final ViewConfiguration mViewConfiguration;
private final WindowManager mWindowManager;
private final IWindowManager mWindowManagerService;
+ private final Optional<Pip> mPipOptional;
private final FalsingManager mFalsingManager;
private final LatencyTracker mLatencyTracker;
private final FeatureFlags mFeatureFlags;
@@ -1018,6 +1021,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
ViewConfiguration viewConfiguration,
WindowManager windowManager,
IWindowManager windowManagerService,
+ Optional<Pip> pipOptional,
FalsingManager falsingManager,
LatencyTracker latencyTracker,
FeatureFlags featureFlags) {
@@ -1032,6 +1036,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mViewConfiguration = viewConfiguration;
mWindowManager = windowManager;
mWindowManagerService = windowManagerService;
+ mPipOptional = pipOptional;
mFalsingManager = falsingManager;
mLatencyTracker = latencyTracker;
mFeatureFlags = featureFlags;
@@ -1052,6 +1057,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mViewConfiguration,
mWindowManager,
mWindowManagerService,
+ mPipOptional,
mFalsingManager,
mLatencyTracker,
mFeatureFlags);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index a74c59618c95..eba9d3fdcab8 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -486,7 +486,7 @@ public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPl
public void onMotionEvent(MotionEvent event) {
if (mBackAnimation != null) {
mBackAnimation.onBackMotion(
- event,
+ event.getX(), event.getY(),
event.getActionMasked(),
mIsLeftPanel ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 892c28306f46..0288c9fce64a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -342,7 +342,8 @@ class FgsManagerController @Inject constructor(
}
val addedPackages = runningServiceTokens.keys.filter {
- it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true
+ currentProfileIds.contains(it.userId) &&
+ it.uiControl != UIControl.HIDE_ENTRY && runningApps[it]?.stopped != true
}
val removedPackages = runningApps.keys.filter { !runningServiceTokens.containsKey(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 5d2060d8043e..7b1ddd62ec6e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -139,12 +139,11 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
void updateResources(QSPanelController qsPanelController,
QuickStatusBarHeaderController quickStatusBarHeaderController) {
- int bottomPadding = getResources().getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
mQSPanelContainer.setPaddingRelative(
mQSPanelContainer.getPaddingStart(),
QSUtils.getQsHeaderSystemIconsAreaHeight(mContext),
mQSPanelContainer.getPaddingEnd(),
- bottomPadding);
+ mQSPanelContainer.getPaddingBottom());
int horizontalMargins = getResources().getDimensionPixelSize(R.dimen.qs_horizontal_margin);
int horizontalPadding = getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 41724ef62683..324c01959084 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -362,11 +362,11 @@ public class QSPanel extends LinearLayout implements Tunable {
protected void updatePadding() {
final Resources res = mContext.getResources();
int paddingTop = res.getDimensionPixelSize(R.dimen.qs_panel_padding_top);
- // Bottom padding only when there's a new footer with its height.
+ int paddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
setPaddingRelative(getPaddingStart(),
paddingTop,
getPaddingEnd(),
- getPaddingBottom());
+ paddingBottom);
}
void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index fbdabc74aba4..a1c66b35bacc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -396,7 +396,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
}
void saveTilesToSettings(List<String> tileSpecs) {
- if (tileSpecs.contains("work")) Log.wtfStack(TAG, "Saving work tile");
mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
null /* tag */, false /* default */, mCurrentUser,
true /* overrideable by restore */);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 5b6e5ce95b14..c213f192291a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -142,7 +142,7 @@ public class ScreenshotController {
}
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
index d3ae198e8e35..236ba1f92d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
@@ -56,6 +56,8 @@ interface SmartspaceViewComponent {
):
BcSmartspaceDataPlugin.SmartspaceView {
val ssView = plugin.getView(parent)
+ // Currently, this is only used to provide SmartspaceView on Dream surface.
+ ssView.setIsDreaming(true)
ssView.registerDataProvider(plugin)
ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
@@ -81,4 +83,4 @@ interface SmartspaceViewComponent {
return ssView
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index 6cfbb43fa25a..07455a02b2cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -66,7 +66,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* @param entry entry to show
*/
public void showNotification(@NonNull NotificationEntry entry) {
- mLogger.logShowNotification(entry.getKey());
+ mLogger.logShowNotification(entry);
addAlertEntry(entry);
updateNotification(entry.getKey(), true /* alert */);
entry.setInterruption();
@@ -320,7 +320,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* @param updatePostTime whether or not to refresh the post time
*/
public void updateEntry(boolean updatePostTime) {
- mLogger.logUpdateEntry(mEntry.getKey(), updatePostTime);
+ mLogger.logUpdateEntry(mEntry, updatePostTime);
long currentTime = mClock.currentTimeMillis();
mEarliestRemovaltime = currentTime + mMinimumDisplayTime;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 86f9fa155179..9e029095ea6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar;
import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_MANAGEMENT_DISCLOSURE;
+import static android.app.admin.DevicePolicyResources.Strings.SystemUi.KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
@@ -407,7 +408,7 @@ public class KeyguardIndicationController {
organizationName);
} else {
return mDevicePolicyManager.getResources().getString(
- KEYGUARD_MANAGEMENT_DISCLOSURE,
+ KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE,
() -> packageResources.getString(
R.string.do_disclosure_with_name, organizationName),
organizationName);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
index 5df593b64c24..558bcac681d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
@@ -61,7 +61,7 @@ class WiredChargingRippleController @Inject constructor(
private val systemClock: SystemClock,
private val uiEventLogger: UiEventLogger
) {
- private var pluggedIn: Boolean? = null
+ private var pluggedIn: Boolean = false
private val rippleEnabled: Boolean = featureFlags.isEnabled(Flags.CHARGING_RIPPLE) &&
!SystemProperties.getBoolean("persist.debug.suppress-charging-ripple", false)
private var normalizedPortPosX: Float = context.resources.getFloat(
@@ -99,15 +99,17 @@ class WiredChargingRippleController @Inject constructor(
nowPluggedIn: Boolean,
charging: Boolean
) {
- // Suppresses the ripple when the state change comes from wireless charging.
- if (batteryController.isPluggedInWireless) {
+ // Suppresses the ripple when the state change comes from wireless charging or
+ // its dock.
+ if (batteryController.isPluggedInWireless ||
+ batteryController.isChargingSourceDock) {
return
}
- val wasPluggedIn = pluggedIn
- pluggedIn = nowPluggedIn
- if ((wasPluggedIn == null || !wasPluggedIn) && nowPluggedIn) {
+
+ if (!pluggedIn && nowPluggedIn) {
startRippleWithDebounce()
}
+ pluggedIn = nowPluggedIn
}
}
batteryController.addCallback(batteryStateChangeCallback)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
index a0ccd5726c75..1be4c04ef804 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
@@ -80,7 +80,7 @@ public class DynamicPrivacyController implements KeyguardStateController.Callbac
@VisibleForTesting
boolean isDynamicPrivacyEnabled() {
- return !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
+ return mLockscreenUserManager.userAllowsNotificationsInPublic(
mLockscreenUserManager.getCurrentUserId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index 478f7aa8ce09..c4947ca2dd90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -56,4 +56,7 @@ class NotifPipelineFlags @Inject constructor(
fun isSmartspaceDedupingEnabled(): Boolean =
featureFlags.isEnabled(Flags.SMARTSPACE) &&
featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING)
-} \ No newline at end of file
+
+ fun removeUnrankedNotifs(): Boolean =
+ featureFlags.isEnabled(Flags.REMOVE_UNRANKED_NOTIFICATIONS)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
index 2397005a1a61..52dcf02e3a19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
@@ -17,18 +17,32 @@
package com.android.systemui.statusbar.notification
import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.util.Compile
import javax.inject.Inject
/** Logger for [NotificationEntryManager]. */
class NotificationEntryManagerLogger @Inject constructor(
+ notifPipelineFlags: NotifPipelineFlags,
@NotificationLog private val buffer: LogBuffer
) {
+ private val devLoggingEnabled by lazy { notifPipelineFlags.isDevLoggingEnabled() }
+
+ private inline fun devLog(
+ level: LogLevel,
+ initializer: LogMessage.() -> Unit,
+ noinline printer: LogMessage.() -> String
+ ) {
+ if (Compile.IS_DEBUG && devLoggingEnabled) buffer.log(TAG, level, initializer, printer)
+ }
+
fun logNotifAdded(key: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
}, {
"NOTIF ADDED $str1"
@@ -36,7 +50,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logNotifUpdated(key: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
}, {
"NOTIF UPDATED $str1"
@@ -44,7 +58,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logInflationAborted(key: String, status: String, reason: String) {
- buffer.log(TAG, DEBUG, {
+ devLog(DEBUG, {
str1 = key
str2 = status
str3 = reason
@@ -54,7 +68,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logNotifInflated(key: String, isNew: Boolean) {
- buffer.log(TAG, DEBUG, {
+ devLog(DEBUG, {
str1 = key
bool1 = isNew
}, {
@@ -63,7 +77,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logRemovalIntercepted(key: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
}, {
"NOTIF REMOVE INTERCEPTED for $str1"
@@ -71,7 +85,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logLifetimeExtended(key: String, extenderName: String, status: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
str2 = extenderName
str3 = status
@@ -81,7 +95,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logNotifRemoved(key: String, removedByUser: Boolean) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
bool1 = removedByUser
}, {
@@ -90,7 +104,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logFilterAndSort(reason: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = reason
}, {
"FILTER AND SORT reason=$str1"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
index 63c37e9584d6..ed80f33be701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
@@ -9,4 +9,6 @@ juliacr@google.com
juliatuttle@google.com
lynhan@google.com
steell@google.com
-yurilin@google.com \ No newline at end of file
+yurilin@google.com
+
+per-file MediaNotificationProcessor.java = ethibodeau@google.com, asc@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index 792ff8d20b97..f6a572ec6ce6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection;
+import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED;
@@ -52,7 +53,7 @@ public class ListDumper {
sb,
true,
includeRecordKeeping,
- interactionTracker.hasUserInteractedWith(entry.getKey()));
+ interactionTracker.hasUserInteractedWith(logKey(entry)));
if (entry instanceof GroupEntry) {
GroupEntry ge = (GroupEntry) entry;
NotificationEntry summary = ge.getSummary();
@@ -63,7 +64,7 @@ public class ListDumper {
sb,
true,
includeRecordKeeping,
- interactionTracker.hasUserInteractedWith(summary.getKey()));
+ interactionTracker.hasUserInteractedWith(logKey(summary)));
}
List<NotificationEntry> children = ge.getChildren();
for (int childIndex = 0; childIndex < children.size(); childIndex++) {
@@ -74,7 +75,7 @@ public class ListDumper {
sb,
true,
includeRecordKeeping,
- interactionTracker.hasUserInteractedWith(child.getKey()));
+ interactionTracker.hasUserInteractedWith(logKey(child)));
}
}
}
@@ -116,11 +117,11 @@ public class ListDumper {
sb.append(indent)
.append("[").append(index).append("] ")
.append(index.length() == 1 ? " " : "")
- .append(entry.getKey());
+ .append(logKey(entry));
if (includeParent) {
sb.append(" (parent=")
- .append(entry.getParent() != null ? entry.getParent().getKey() : null)
+ .append(logKey(entry.getParent()))
.append(")");
NotificationEntry notifEntry = entry.getRepresentativeEntry();
@@ -185,8 +186,8 @@ public class ListDumper {
if (notifEntry.getAttachState().getSuppressedChanges().getParent() != null) {
rksb.append("suppressedParent=")
- .append(notifEntry.getAttachState().getSuppressedChanges()
- .getParent().getKey())
+ .append(logKey(notifEntry.getAttachState().getSuppressedChanges()
+ .getParent()))
.append(" ");
}
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 ecce1ba25702..d99b5f9df12a 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
@@ -89,6 +89,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.In
import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLoggerKt;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifEvent;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -110,6 +111,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
@@ -157,6 +159,8 @@ public class NotifCollection implements Dumpable {
private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
+ private Set<String> mNotificationsWithoutRankings = Collections.emptySet();
+
private Queue<NotifEvent> mEventQueue = new ArrayDeque<>();
private boolean mAttached = false;
@@ -267,13 +271,14 @@ public class NotifCollection implements Dumpable {
requireNonNull(stats);
NotificationEntry storedEntry = mNotificationSet.get(entry.getKey());
if (storedEntry == null) {
- mLogger.logNonExistentNotifDismissed(entry.getKey());
+ mLogger.logNonExistentNotifDismissed(entry);
continue;
}
if (entry != storedEntry) {
throw mEulogizer.record(
new IllegalStateException("Invalid entry: "
- + "different stored and dismissed entries for " + entry.getKey()));
+ + "different stored and dismissed entries for " + logKey(entry)
+ + " stored=@" + Integer.toHexString(storedEntry.hashCode())));
}
if (entry.getDismissState() == DISMISSED) {
@@ -282,7 +287,7 @@ public class NotifCollection implements Dumpable {
updateDismissInterceptors(entry);
if (isDismissIntercepted(entry)) {
- mLogger.logNotifDismissedIntercepted(entry.getKey());
+ mLogger.logNotifDismissedIntercepted(entry);
continue;
}
@@ -299,13 +304,13 @@ public class NotifCollection implements Dumpable {
stats.notificationVisibility);
} catch (RemoteException e) {
// system process is dead if we're here.
- mLogger.logRemoteExceptionOnNotificationClear(entry.getKey(), e);
+ mLogger.logRemoteExceptionOnNotificationClear(entry, e);
}
}
}
locallyDismissNotifications(entriesToLocallyDismiss);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("dismissNotifications");
}
/**
@@ -342,14 +347,14 @@ public class NotifCollection implements Dumpable {
// interceptors the chance to filter the notification
updateDismissInterceptors(entry);
if (isDismissIntercepted(entry)) {
- mLogger.logNotifClearAllDismissalIntercepted(entry.getKey());
+ mLogger.logNotifClearAllDismissalIntercepted(entry);
}
entries.remove(i);
}
}
locallyDismissNotifications(entries);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("dismissAllNotifications");
}
/**
@@ -363,7 +368,7 @@ public class NotifCollection implements Dumpable {
NotificationEntry entry = entries.get(i);
entry.setDismissState(DISMISSED);
- mLogger.logNotifDismissed(entry.getKey());
+ mLogger.logNotifDismissed(entry);
if (isCanceled(entry)) {
canceledEntries.add(entry);
@@ -396,7 +401,7 @@ public class NotifCollection implements Dumpable {
postNotification(sbn, requireRanking(rankingMap, sbn.getKey()));
applyRanking(rankingMap);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationPosted");
}
private void onNotificationGroupPosted(List<CoalescedEvent> batch) {
@@ -407,7 +412,7 @@ public class NotifCollection implements Dumpable {
for (CoalescedEvent event : batch) {
postNotification(event.getSbn(), event.getRanking());
}
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationGroupPosted");
}
private void onNotificationRemoved(
@@ -416,26 +421,26 @@ public class NotifCollection implements Dumpable {
int reason) {
Assert.isMainThread();
- mLogger.logNotifRemoved(sbn.getKey(), reason);
+ mLogger.logNotifRemoved(sbn, reason);
final NotificationEntry entry = mNotificationSet.get(sbn.getKey());
if (entry == null) {
// TODO (b/160008901): Throw an exception here
- mLogger.logNoNotificationToRemoveWithKey(sbn.getKey(), reason);
+ mLogger.logNoNotificationToRemoveWithKey(sbn, reason);
return;
}
entry.mCancellationReason = reason;
tryRemoveNotification(entry);
applyRanking(rankingMap);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationRemoved");
}
private void onNotificationRankingUpdate(RankingMap rankingMap) {
Assert.isMainThread();
mEventQueue.add(new RankingUpdatedEvent(rankingMap));
applyRanking(rankingMap);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationRankingUpdate");
}
private void onNotificationChannelModified(
@@ -445,7 +450,7 @@ public class NotifCollection implements Dumpable {
int modificationType) {
Assert.isMainThread();
mEventQueue.add(new ChannelChangedEvent(pkgName, user, channel, modificationType));
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationChannelModified");
}
private void onNotificationsInitialized() {
@@ -464,7 +469,7 @@ public class NotifCollection implements Dumpable {
mEventQueue.add(new BindEntryEvent(entry, sbn));
mNotificationSet.put(sbn.getKey(), entry);
- mLogger.logNotifPosted(sbn.getKey());
+ mLogger.logNotifPosted(entry);
mEventQueue.add(new EntryAddedEvent(entry));
} else {
@@ -483,7 +488,7 @@ public class NotifCollection implements Dumpable {
entry.setSbn(sbn);
mEventQueue.add(new BindEntryEvent(entry, sbn));
- mLogger.logNotifUpdated(sbn.getKey());
+ mLogger.logNotifUpdated(entry);
mEventQueue.add(new EntryUpdatedEvent(entry, true /* fromSystem */));
}
}
@@ -498,12 +503,12 @@ public class NotifCollection implements Dumpable {
if (mNotificationSet.get(entry.getKey()) != entry) {
throw mEulogizer.record(
new IllegalStateException("No notification to remove with key "
- + entry.getKey()));
+ + logKey(entry)));
}
if (!isCanceled(entry)) {
throw mEulogizer.record(
- new IllegalStateException("Cannot remove notification " + entry.getKey()
+ new IllegalStateException("Cannot remove notification " + logKey(entry)
+ ": has not been marked for removal"));
}
@@ -514,7 +519,7 @@ public class NotifCollection implements Dumpable {
}
if (!isLifetimeExtended(entry)) {
- mLogger.logNotifReleased(entry.getKey());
+ mLogger.logNotifReleased(entry);
mNotificationSet.remove(entry.getKey());
cancelDismissInterception(entry);
mEventQueue.add(new EntryRemovedEvent(entry, entry.mCancellationReason));
@@ -559,6 +564,7 @@ public class NotifCollection implements Dumpable {
}
private void applyRanking(@NonNull RankingMap rankingMap) {
+ ArrayMap<String, NotificationEntry> currentEntriesWithoutRankings = null;
for (NotificationEntry entry : mNotificationSet.values()) {
if (!isCanceled(entry)) {
@@ -580,14 +586,31 @@ public class NotifCollection implements Dumpable {
}
}
} else {
- mLogger.logRankingMissing(entry.getKey(), rankingMap);
+ if (currentEntriesWithoutRankings == null) {
+ currentEntriesWithoutRankings = new ArrayMap<>();
+ }
+ currentEntriesWithoutRankings.put(entry.getKey(), entry);
}
}
}
+ NotifCollectionLoggerKt.maybeLogInconsistentRankings(
+ mLogger,
+ mNotificationsWithoutRankings,
+ currentEntriesWithoutRankings,
+ rankingMap
+ );
+ mNotificationsWithoutRankings = currentEntriesWithoutRankings == null
+ ? Collections.emptySet() : currentEntriesWithoutRankings.keySet();
+ if (currentEntriesWithoutRankings != null && mNotifPipelineFlags.removeUnrankedNotifs()) {
+ for (NotificationEntry entry : currentEntriesWithoutRankings.values()) {
+ entry.mCancellationReason = REASON_UNKNOWN;
+ tryRemoveNotification(entry);
+ }
+ }
mEventQueue.add(new RankingAppliedEvent());
}
- private void dispatchEventsAndRebuildList() {
+ private void dispatchEventsAndRebuildList(String reason) {
Trace.beginSection("NotifCollection.dispatchEventsAndRebuildList");
mAmDispatchingToOtherCode = true;
while (!mEventQueue.isEmpty()) {
@@ -596,7 +619,7 @@ public class NotifCollection implements Dumpable {
mAmDispatchingToOtherCode = false;
if (mBuildListener != null) {
- mBuildListener.onBuildList(mReadOnlyNotificationSet);
+ mBuildListener.onBuildList(mReadOnlyNotificationSet, reason);
}
Trace.endSection();
}
@@ -627,14 +650,11 @@ public class NotifCollection implements Dumpable {
extender.getName(), logKey, collectionEntryIs)));
}
- mLogger.logLifetimeExtensionEnded(
- entry.getKey(),
- extender,
- entry.mLifetimeExtenders.size());
+ mLogger.logLifetimeExtensionEnded(entry, extender, entry.mLifetimeExtenders.size());
if (!isLifetimeExtended(entry)) {
if (tryRemoveNotification(entry)) {
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onEndLifetimeExtension");
}
}
}
@@ -657,7 +677,7 @@ public class NotifCollection implements Dumpable {
mAmDispatchingToOtherCode = true;
for (NotifLifetimeExtender extender : mLifetimeExtenders) {
if (extender.maybeExtendLifetime(entry, entry.mCancellationReason)) {
- mLogger.logLifetimeExtended(entry.getKey(), extender);
+ mLogger.logLifetimeExtended(entry, extender);
entry.mLifetimeExtenders.add(extender);
}
}
@@ -838,6 +858,11 @@ public class NotifCollection implements Dumpable {
entries,
true,
"\t\t"));
+
+ pw.println("\n\tmNotificationsWithoutRankings: " + mNotificationsWithoutRankings.size());
+ for (String key : mNotificationsWithoutRankings) {
+ pw.println("\t * : " + key);
+ }
}
private final BatchableNotificationHandler mNotifHandler = new BatchableNotificationHandler() {
@@ -916,21 +941,21 @@ public class NotifCollection implements Dumpable {
// Make sure we have the notification to update
NotificationEntry entry = mNotificationSet.get(sbn.getKey());
if (entry == null) {
- mLogger.logNotifInternalUpdateFailed(sbn.getKey(), name, reason);
+ mLogger.logNotifInternalUpdateFailed(sbn, name, reason);
return;
}
- mLogger.logNotifInternalUpdate(sbn.getKey(), name, reason);
+ mLogger.logNotifInternalUpdate(entry, name, reason);
// First do the pieces of postNotification which are not about assuming the notification
// was sent by the app
entry.setSbn(sbn);
mEventQueue.add(new BindEntryEvent(entry, sbn));
- mLogger.logNotifUpdated(sbn.getKey());
+ mLogger.logNotifUpdated(entry);
mEventQueue.add(new EntryUpdatedEvent(entry, false /* fromSystem */));
// Skip the applyRanking step and go straight to dispatching the events
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("updateNotificationInternally");
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index aedbd1b56622..0a16fb65b1ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -175,6 +175,10 @@ public final class NotificationEntry extends ListEntry {
public boolean mRemoteEditImeAnimatingAway;
public boolean mRemoteEditImeVisible;
private boolean mExpandAnimationRunning;
+ /**
+ * Flag to determine if the entry is blockable by DnD filters
+ */
+ private boolean mBlockable;
/**
* @param sbn the StatusBarNotification from system server
@@ -253,6 +257,7 @@ public final class NotificationEntry extends ListEntry {
}
mRanking = ranking.withAudiblyAlertedInfo(mRanking);
+ updateIsBlockable();
}
/*
@@ -781,15 +786,20 @@ public final class NotificationEntry extends ListEntry {
* or is not in an allowList).
*/
public boolean isBlockable() {
+ return mBlockable;
+ }
+
+ private void updateIsBlockable() {
if (getChannel() == null) {
- return false;
+ mBlockable = false;
+ return;
}
if (getChannel().isImportanceLockedByCriticalDeviceFunction()
&& !getChannel().isBlockable()) {
- return false;
+ mBlockable = false;
+ return;
}
-
- return true;
+ mBlockable = true;
}
private boolean shouldSuppressVisualEffect(int effect) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index df2fe4e8511f..93761f580dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -304,11 +304,11 @@ public class ShadeListBuilder implements Dumpable {
private final CollectionReadyForBuildListener mReadyForBuildListener =
new CollectionReadyForBuildListener() {
@Override
- public void onBuildList(Collection<NotificationEntry> entries) {
+ public void onBuildList(Collection<NotificationEntry> entries, String reason) {
Assert.isMainThread();
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
- mLogger.logOnBuildList();
+ mLogger.logOnBuildList(reason);
mAllEntries = entries;
mChoreographer.schedule();
}
@@ -456,7 +456,8 @@ public class ShadeListBuilder implements Dumpable {
mLogger.logEndBuildList(
mIterationCount,
mReadOnlyNotifList.size(),
- countChildren(mReadOnlyNotifList));
+ countChildren(mReadOnlyNotifList),
+ /* enforcedVisualStability */ !mNotifStabilityManager.isEveryChangeAllowed());
if (mAlwaysLogList || mIterationCount % 10 == 0) {
Trace.beginSection("ShadeListBuilder.logFinalList");
mLogger.logFinalList(mNotifList);
@@ -579,11 +580,7 @@ public class ShadeListBuilder implements Dumpable {
if (existingSummary == null) {
group.setSummary(entry);
} else {
- mLogger.logDuplicateSummary(
- mIterationCount,
- group.getKey(),
- existingSummary.getKey(),
- entry.getKey());
+ mLogger.logDuplicateSummary(mIterationCount, group, existingSummary, entry);
// Use whichever one was posted most recently
if (entry.getSbn().getPostTime()
@@ -990,7 +987,7 @@ public class ShadeListBuilder implements Dumpable {
// Check for suppressed order changes
if (!getStabilityManager().isEveryChangeAllowed()) {
mForceReorderable = true;
- boolean isSorted = isSorted(mNotifList, mTopLevelComparator);
+ boolean isSorted = isShadeSorted();
mForceReorderable = false;
if (!isSorted) {
getStabilityManager().onEntryReorderSuppressed();
@@ -999,9 +996,23 @@ public class ShadeListBuilder implements Dumpable {
Trace.endSection();
}
+ private boolean isShadeSorted() {
+ if (!isSorted(mNotifList, mTopLevelComparator)) {
+ return false;
+ }
+ for (ListEntry entry : mNotifList) {
+ if (entry instanceof GroupEntry) {
+ if (!isSorted(((GroupEntry) entry).getChildren(), mGroupChildrenComparator)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
/** Determine whether the items in the list are sorted according to the comparator */
@VisibleForTesting
- public static <T> boolean isSorted(List<T> items, Comparator<T> comparator) {
+ public static <T> boolean isSorted(List<T> items, Comparator<? super T> comparator) {
if (items.size() <= 1) {
return true;
}
@@ -1070,7 +1081,7 @@ public class ShadeListBuilder implements Dumpable {
if (!Objects.equals(curr, prev)) {
mLogger.logEntryAttachStateChanged(
mIterationCount,
- entry.getKey(),
+ entry,
prev.getParent(),
curr.getParent());
@@ -1209,7 +1220,7 @@ public class ShadeListBuilder implements Dumpable {
};
- private final Comparator<ListEntry> mGroupChildrenComparator = (o1, o2) -> {
+ private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
int cmp = Integer.compare(index1, index2);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index ff1c70ca740b..ef63be0633bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -16,15 +16,15 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import com.android.keyguard.KeyguardUpdateMonitor;
+import androidx.annotation.NonNull;
+
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import javax.inject.Inject;
@@ -36,27 +36,21 @@ import javax.inject.Inject;
@CoordinatorScope
public class KeyguardCoordinator implements Coordinator {
private static final String TAG = "KeyguardCoordinator";
- private final StatusBarStateController mStatusBarStateController;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final HighPriorityProvider mHighPriorityProvider;
- private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
+ private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
private final SharedCoordinatorLogger mLogger;
+ private final StatusBarStateController mStatusBarStateController;
@Inject
public KeyguardCoordinator(
- StatusBarStateController statusBarStateController,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- HighPriorityProvider highPriorityProvider,
- SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider,
KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
- SharedCoordinatorLogger logger) {
- mStatusBarStateController = statusBarStateController;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mHighPriorityProvider = highPriorityProvider;
- mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider;
+ SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider,
+ SharedCoordinatorLogger logger,
+ StatusBarStateController statusBarStateController) {
mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
+ mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider;
mLogger = logger;
+ mStatusBarStateController = statusBarStateController;
}
@Override
@@ -72,7 +66,7 @@ public class KeyguardCoordinator implements Coordinator {
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@Override
- public boolean shouldFilterOut(NotificationEntry entry, long now) {
+ public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) {
return mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry);
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 8f37bafa45e6..023c4ef2b8b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -360,13 +360,13 @@ public class PreparationCoordinator implements Coordinator {
}
private void abortInflation(NotificationEntry entry, String reason) {
- mLogger.logInflationAborted(entry.getKey(), reason);
+ mLogger.logInflationAborted(entry, reason);
mNotifInflater.abortInflation(entry);
mInflatingNotifs.remove(entry);
}
private void onInflationFinished(NotificationEntry entry, NotifViewController controller) {
- mLogger.logNotifInflated(entry.getKey());
+ mLogger.logNotifInflated(entry);
mInflatingNotifs.remove(entry);
mViewBarn.registerViewForEntry(entry, controller);
mInflationStates.put(entry, STATE_INFLATED);
@@ -398,20 +398,20 @@ public class PreparationCoordinator implements Coordinator {
return false;
}
if (isBeyondGroupInitializationWindow(group, now)) {
- mLogger.logGroupInflationTookTooLong(group.getKey());
+ mLogger.logGroupInflationTookTooLong(group);
return false;
}
if (mInflatingNotifs.contains(group.getSummary())) {
- mLogger.logDelayingGroupRelease(group.getKey(), group.getSummary().getKey());
+ mLogger.logDelayingGroupRelease(group, group.getSummary());
return true;
}
for (NotificationEntry child : group.getChildren()) {
if (mInflatingNotifs.contains(child) && !child.wasAttachedInPreviousPass()) {
- mLogger.logDelayingGroupRelease(group.getKey(), child.getKey());
+ mLogger.logDelayingGroupRelease(group, child);
return true;
}
}
- mLogger.logDoneWaitingForGroupInflation(group.getKey());
+ mLogger.logDoneWaitingForGroupInflation(group);
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index f8352500923e..30f13152126c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -19,48 +19,51 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class PreparationCoordinatorLogger @Inject constructor(
@NotificationLog private val buffer: LogBuffer
) {
- fun logNotifInflated(key: String) {
+ fun logNotifInflated(entry: NotificationEntry) {
buffer.log(TAG, LogLevel.DEBUG, {
- str1 = key
+ str1 = entry.logKey
}, {
"NOTIF INFLATED $str1"
})
}
- fun logInflationAborted(key: String, reason: String) {
+ fun logInflationAborted(entry: NotificationEntry, reason: String) {
buffer.log(TAG, LogLevel.DEBUG, {
- str1 = key
+ str1 = entry.logKey
str2 = reason
}, {
"NOTIF INFLATION ABORTED $str1 reason=$str2"
})
}
- fun logDoneWaitingForGroupInflation(groupKey: String) {
+ fun logDoneWaitingForGroupInflation(group: GroupEntry) {
buffer.log(TAG, LogLevel.DEBUG, {
- str1 = groupKey
+ str1 = group.logKey
}, {
"Finished inflating all members of group $str1, releasing group"
})
}
- fun logGroupInflationTookTooLong(groupKey: String) {
+ fun logGroupInflationTookTooLong(group: GroupEntry) {
buffer.log(TAG, LogLevel.WARNING, {
- str1 = groupKey
+ str1 = group.logKey
}, {
"Group inflation took too long for $str1, releasing children early"
})
}
- fun logDelayingGroupRelease(groupKey: String, childKey: String) {
+ fun logDelayingGroupRelease(group: GroupEntry, child: NotificationEntry) {
buffer.log(TAG, LogLevel.DEBUG, {
- str1 = groupKey
- str2 = childKey
+ str1 = group.logKey
+ str2 = child.logKey
}, {
"Delaying release of group $str1 because child $str2 is still inflating"
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 57fd1975e13a..5ac481341d43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -20,7 +20,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.SectionClassifier;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -28,6 +27,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.AlertingHeader;
@@ -51,7 +51,7 @@ public class RankingCoordinator implements Coordinator {
public static final boolean SHOW_ALL_SECTIONS = false;
private final StatusBarStateController mStatusBarStateController;
private final HighPriorityProvider mHighPriorityProvider;
- private final SectionClassifier mSectionClassifier;
+ private final SectionStyleProvider mSectionStyleProvider;
private final NodeController mSilentNodeController;
private final SectionHeaderController mSilentHeaderController;
private final NodeController mAlertingHeaderController;
@@ -62,13 +62,13 @@ public class RankingCoordinator implements Coordinator {
public RankingCoordinator(
StatusBarStateController statusBarStateController,
HighPriorityProvider highPriorityProvider,
- SectionClassifier sectionClassifier,
+ SectionStyleProvider sectionStyleProvider,
@AlertingHeader NodeController alertingHeaderController,
@SilentHeader SectionHeaderController silentHeaderController,
@SilentHeader NodeController silentNodeController) {
mStatusBarStateController = statusBarStateController;
mHighPriorityProvider = highPriorityProvider;
- mSectionClassifier = sectionClassifier;
+ mSectionStyleProvider = sectionStyleProvider;
mAlertingHeaderController = alertingHeaderController;
mSilentNodeController = silentNodeController;
mSilentHeaderController = silentHeaderController;
@@ -77,7 +77,7 @@ public class RankingCoordinator implements Coordinator {
@Override
public void attach(NotifPipeline pipeline) {
mStatusBarStateController.addCallback(mStatusBarStateCallback);
- mSectionClassifier.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner));
+ mSectionStyleProvider.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner));
pipeline.addPreGroupFilter(mSuspendedFilter);
pipeline.addPreGroupFilter(mDndVisualEffectsFilter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index 4e9d3ac07a96..1494574b26f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -19,11 +19,11 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.content.Context
import com.android.systemui.R
import com.android.systemui.statusbar.notification.AssistantFeedbackController
-import com.android.systemui.statusbar.notification.SectionClassifier
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.collection.render.NotifRowController
import javax.inject.Inject
@@ -35,7 +35,7 @@ import javax.inject.Inject
class RowAppearanceCoordinator @Inject internal constructor(
context: Context,
private var mAssistantFeedbackController: AssistantFeedbackController,
- private var mSectionClassifier: SectionClassifier
+ private var mSectionStyleProvider: SectionStyleProvider
) : Coordinator {
private var entryToExpand: NotificationEntry? = null
@@ -55,7 +55,7 @@ class RowAppearanceCoordinator @Inject internal constructor(
private fun onBeforeRenderList(list: List<ListEntry>) {
entryToExpand = list.firstOrNull()?.representativeEntry?.takeIf { entry ->
- !mSectionClassifier.isMinimizedSection(entry.section!!)
+ !mSectionStyleProvider.isMinimizedSection(entry.section!!)
}
}
@@ -68,4 +68,4 @@ class RowAppearanceCoordinator @Inject internal constructor(
// Show the "alerted" bell icon
controller.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
index 3475fcf7c12f..ee0b00807e27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
@@ -31,6 +31,7 @@ class NotifUiAdjustment internal constructor(
val smartActions: List<Notification.Action>,
val smartReplies: List<CharSequence>,
val isConversation: Boolean,
+ val isSnoozeEnabled: Boolean,
val isMinimized: Boolean,
val needsRedaction: Boolean,
) {
@@ -42,6 +43,7 @@ class NotifUiAdjustment internal constructor(
): Boolean = when {
oldAdjustment === newAdjustment -> false
oldAdjustment.isConversation != newAdjustment.isConversation -> true
+ oldAdjustment.isSnoozeEnabled != newAdjustment.isSnoozeEnabled -> true
oldAdjustment.isMinimized != newAdjustment.isMinimized -> true
oldAdjustment.needsRedaction != newAdjustment.needsRedaction -> true
areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions) -> true
@@ -57,9 +59,9 @@ class NotifUiAdjustment internal constructor(
first.size != second.size -> true
else -> first.asSequence().zip(second.asSequence()).any {
(!TextUtils.equals(it.first.title, it.second.title)) ||
- (areDifferent(it.first.getIcon(), it.second.getIcon())) ||
- (it.first.actionIntent != it.second.actionIntent) ||
- (areDifferent(it.first.remoteInputs, it.second.remoteInputs))
+ (areDifferent(it.first.getIcon(), it.second.getIcon())) ||
+ (it.first.actionIntent != it.second.actionIntent) ||
+ (areDifferent(it.first.remoteInputs, it.second.remoteInputs))
}
}
@@ -78,7 +80,7 @@ class NotifUiAdjustment internal constructor(
first.size != second.size -> true
else -> first.asSequence().zip(second.asSequence()).any {
(!TextUtils.equals(it.first.label, it.second.label)) ||
- (areDifferent(it.first.choices, it.second.choices))
+ (areDifferent(it.first.choices, it.second.choices))
}
}
@@ -94,4 +96,4 @@ class NotifUiAdjustment internal constructor(
}
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index f7b6376c717c..745d6fe1d624 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -16,12 +16,18 @@
package com.android.systemui.statusbar.notification.collection.inflation
+import android.database.ContentObserver
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.notification.SectionClassifier
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.util.ListenerSet
+import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
/**
@@ -30,14 +36,23 @@ import javax.inject.Inject
*/
@SysUISingleton
class NotifUiAdjustmentProvider @Inject constructor(
+ @Main private val handler: Handler,
+ private val secureSettings: SecureSettings,
private val lockscreenUserManager: NotificationLockscreenUserManager,
- private val sectionClassifier: SectionClassifier,
+ private val sectionStyleProvider: SectionStyleProvider
) {
private val dirtyListeners = ListenerSet<Runnable>()
+ private var isSnoozeEnabled = false
fun addDirtyListener(listener: Runnable) {
if (dirtyListeners.isEmpty()) {
lockscreenUserManager.addNotificationStateChangedListener(notifStateChangedListener)
+ updateSnoozeEnabled()
+ secureSettings.registerContentObserverForUser(
+ SHOW_NOTIFICATION_SNOOZE,
+ settingsObserver,
+ UserHandle.USER_ALL
+ )
}
dirtyListeners.addIfAbsent(listener)
}
@@ -46,6 +61,7 @@ class NotifUiAdjustmentProvider @Inject constructor(
dirtyListeners.remove(listener)
if (dirtyListeners.isEmpty()) {
lockscreenUserManager.removeNotificationStateChangedListener(notifStateChangedListener)
+ secureSettings.unregisterContentObserver(settingsObserver)
}
}
@@ -54,10 +70,21 @@ class NotifUiAdjustmentProvider @Inject constructor(
dirtyListeners.forEach(Runnable::run)
}
+ private val settingsObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean) {
+ updateSnoozeEnabled()
+ dirtyListeners.forEach(Runnable::run)
+ }
+ }
+
+ private fun updateSnoozeEnabled() {
+ isSnoozeEnabled = secureSettings.getInt(SHOW_NOTIFICATION_SNOOZE, 0) == 1
+ }
+
private fun isEntryMinimized(entry: NotificationEntry): Boolean {
val section = entry.section ?: error("Entry must have a section to determine if minimized")
val parent = entry.parent ?: error("Entry must have a parent to determine if minimized")
- val isMinimizedSection = sectionClassifier.isMinimizedSection(section)
+ val isMinimizedSection = sectionStyleProvider.isMinimizedSection(section)
val isTopLevelEntry = parent == GroupEntry.ROOT_ENTRY
val isGroupSummary = parent.summary == entry
return isMinimizedSection && (isTopLevelEntry || isGroupSummary)
@@ -73,7 +100,8 @@ class NotifUiAdjustmentProvider @Inject constructor(
smartActions = entry.ranking.smartActions,
smartReplies = entry.ranking.smartReplies,
isConversation = entry.ranking.isConversation,
+ isSnoozeEnabled = isSnoozeEnabled,
isMinimized = isEntryMinimized(entry),
needsRedaction = lockscreenUserManager.needsRedaction(entry),
)
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index f8bf85f92eae..10a627d65b83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -21,29 +21,42 @@ import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.util.Compile
import javax.inject.Inject
class ShadeListBuilderLogger @Inject constructor(
+ notifPipelineFlags: NotifPipelineFlags,
@NotificationLog private val buffer: LogBuffer
) {
- fun logOnBuildList() {
+ fun logOnBuildList(reason: String?) {
buffer.log(TAG, INFO, {
+ str1 = reason
}, {
- "Request received from NotifCollection"
+ "Request received from NotifCollection for $str1"
})
}
- fun logEndBuildList(buildId: Int, topLevelEntries: Int, numChildren: Int) {
+ fun logEndBuildList(
+ buildId: Int,
+ topLevelEntries: Int,
+ numChildren: Int,
+ enforcedVisualStability: Boolean
+ ) {
buffer.log(TAG, INFO, {
long1 = buildId.toLong()
int1 = topLevelEntries
int2 = numChildren
+ bool1 = enforcedVisualStability
}, {
- "(Build $long1) Build complete ($int1 top-level entries, $int2 children)"
+ "(Build $long1) Build complete ($int1 top-level entries, $int2 children)" +
+ " enforcedVisualStability=$bool1"
})
}
@@ -110,12 +123,17 @@ class ShadeListBuilderLogger @Inject constructor(
})
}
- fun logDuplicateSummary(buildId: Int, groupKey: String, existingKey: String, newKey: String) {
+ fun logDuplicateSummary(
+ buildId: Int,
+ group: GroupEntry,
+ existingSummary: NotificationEntry,
+ newSummary: NotificationEntry
+ ) {
buffer.log(TAG, WARNING, {
long1 = buildId.toLong()
- str1 = groupKey
- str2 = existingKey
- str3 = newKey
+ str1 = group.logKey
+ str2 = existingSummary.logKey
+ str3 = newSummary.logKey
}, {
"""(Build $long1) Duplicate summary for group "$str1": "$str2" vs. "$str3""""
})
@@ -124,7 +142,7 @@ class ShadeListBuilderLogger @Inject constructor(
fun logDuplicateTopLevelKey(buildId: Int, topLevelKey: String) {
buffer.log(TAG, WARNING, {
long1 = buildId.toLong()
- str1 = topLevelKey
+ str1 = logKey(topLevelKey)
}, {
"(Build $long1) Duplicate top-level key: $str1"
})
@@ -132,15 +150,15 @@ class ShadeListBuilderLogger @Inject constructor(
fun logEntryAttachStateChanged(
buildId: Int,
- key: String,
+ entry: ListEntry,
prevParent: GroupEntry?,
newParent: GroupEntry?
) {
buffer.log(TAG, INFO, {
long1 = buildId.toLong()
- str1 = key
- str2 = prevParent?.key
- str3 = newParent?.key
+ str1 = entry.logKey
+ str2 = prevParent?.logKey
+ str3 = newParent?.logKey
}, {
val action = if (str2 == null && str3 != null) {
@@ -160,8 +178,8 @@ class ShadeListBuilderLogger @Inject constructor(
fun logParentChanged(buildId: Int, prevParent: GroupEntry?, newParent: GroupEntry?) {
buffer.log(TAG, INFO, {
long1 = buildId.toLong()
- str1 = prevParent?.key
- str2 = newParent?.key
+ str1 = prevParent?.logKey
+ str2 = newParent?.logKey
}, {
if (str1 == null && str2 != null) {
"(Build $long1) Parent is {$str2}"
@@ -180,8 +198,8 @@ class ShadeListBuilderLogger @Inject constructor(
) {
buffer.log(TAG, INFO, {
long1 = buildId.toLong()
- str1 = suppressedParent?.key
- str2 = keepingParent?.key
+ str1 = suppressedParent?.logKey
+ str2 = keepingParent?.logKey
}, {
"(Build $long1) Change of parent to '$str1' suppressed; keeping parent '$str2'"
})
@@ -193,7 +211,7 @@ class ShadeListBuilderLogger @Inject constructor(
) {
buffer.log(TAG, INFO, {
long1 = buildId.toLong()
- str1 = keepingParent?.key
+ str1 = keepingParent?.logKey
}, {
"(Build $long1) Group pruning suppressed; keeping parent '$str1'"
})
@@ -273,6 +291,8 @@ class ShadeListBuilderLogger @Inject constructor(
})
}
+ val logRankInFinalList = Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()
+
fun logFinalList(entries: List<ListEntry>) {
if (entries.isEmpty()) {
buffer.log(TAG, DEBUG, {}, { "(empty list)" })
@@ -281,26 +301,32 @@ class ShadeListBuilderLogger @Inject constructor(
val entry = entries[i]
buffer.log(TAG, DEBUG, {
int1 = i
- str1 = entry.key
+ str1 = entry.logKey
+ bool1 = logRankInFinalList
+ int2 = entry.representativeEntry!!.ranking.rank
}, {
- "[$int1] $str1"
+ "[$int1] $str1".let { if (bool1) "$it rank=$int2" else it }
})
if (entry is GroupEntry) {
entry.summary?.let {
buffer.log(TAG, DEBUG, {
- str1 = it.key
+ str1 = it.logKey
+ bool1 = logRankInFinalList
+ int2 = it.ranking.rank
}, {
- " [*] $str1 (summary)"
+ " [*] $str1 (summary)".let { if (bool1) "$it rank=$int2" else it }
})
}
for (j in entry.children.indices) {
val child = entry.children[j]
buffer.log(TAG, DEBUG, {
int1 = j
- str1 = child.key
+ str1 = child.logKey
+ bool1 = logRankInFinalList
+ int2 = child.ranking.rank
}, {
- " [$int1] $str1"
+ " [$int1] $str1".let { if (bool1) "$it rank=$int2" else it }
})
}
}
@@ -308,7 +334,7 @@ class ShadeListBuilderLogger @Inject constructor(
}
fun logPipelineRunSuppressed() =
- buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." }
+ buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." }
}
-private const val TAG = "ShadeListBuilder" \ No newline at end of file
+private const val TAG = "ShadeListBuilder"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
index 4023474bf6a7..941b2ae4f771 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
@@ -29,5 +29,5 @@ public interface CollectionReadyForBuildListener {
* Called by the NotifCollection to indicate that something in the collection has changed and
* that the list builder should regenerate the list.
*/
- void onBuildList(Collection<NotificationEntry> entries);
+ void onBuildList(Collection<NotificationEntry> entries, String reason);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index ac0b1ee6c442..ebcac6b6afb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.notifcollection
import android.os.RemoteException
import android.service.notification.NotificationListenerService
import android.service.notification.NotificationListenerService.RankingMap
+import android.service.notification.StatusBarNotification
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.ERROR
@@ -65,9 +66,9 @@ fun cancellationReasonDebugString(@CancellationReason reason: Int) =
class NotifCollectionLogger @Inject constructor(
@NotificationLog private val buffer: LogBuffer
) {
- fun logNotifPosted(key: String) {
+ fun logNotifPosted(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"POSTED $str1"
})
@@ -75,49 +76,49 @@ class NotifCollectionLogger @Inject constructor(
fun logNotifGroupPosted(groupKey: String, batchSize: Int) {
buffer.log(TAG, INFO, {
- str1 = groupKey
+ str1 = logKey(groupKey)
int1 = batchSize
}, {
"POSTED GROUP $str1 ($int1 events)"
})
}
- fun logNotifUpdated(key: String) {
+ fun logNotifUpdated(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"UPDATED $str1"
})
}
- fun logNotifRemoved(key: String, @CancellationReason reason: Int) {
+ fun logNotifRemoved(sbn: StatusBarNotification, @CancellationReason reason: Int) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = sbn.logKey
int1 = reason
}, {
"REMOVED $str1 reason=${cancellationReasonDebugString(int1)}"
})
}
- fun logNotifReleased(key: String) {
+ fun logNotifReleased(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"RELEASED $str1"
})
}
- fun logNotifDismissed(key: String) {
+ fun logNotifDismissed(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"DISMISSED $str1"
})
}
- fun logNonExistentNotifDismissed(key: String) {
+ fun logNonExistentNotifDismissed(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"DISMISSED Non Existent $str1"
})
@@ -125,7 +126,7 @@ class NotifCollectionLogger @Inject constructor(
fun logChildDismissed(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = entry.key
+ str1 = entry.logKey
}, {
"CHILD DISMISSED (inferred): $str1"
})
@@ -141,31 +142,31 @@ class NotifCollectionLogger @Inject constructor(
fun logDismissOnAlreadyCanceledEntry(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = entry.key
+ str1 = entry.logKey
}, {
"Dismiss on $str1, which was already canceled. Trying to remove..."
})
}
- fun logNotifDismissedIntercepted(key: String) {
+ fun logNotifDismissedIntercepted(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"DISMISS INTERCEPTED $str1"
})
}
- fun logNotifClearAllDismissalIntercepted(key: String) {
+ fun logNotifClearAllDismissalIntercepted(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"CLEAR ALL DISMISSAL INTERCEPTED $str1"
})
}
- fun logNotifInternalUpdate(key: String, name: String, reason: String) {
+ fun logNotifInternalUpdate(entry: NotificationEntry, name: String, reason: String) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
str2 = name
str3 = reason
}, {
@@ -173,9 +174,9 @@ class NotifCollectionLogger @Inject constructor(
})
}
- fun logNotifInternalUpdateFailed(key: String, name: String, reason: String) {
+ fun logNotifInternalUpdateFailed(sbn: StatusBarNotification, name: String, reason: String) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = sbn.logKey
str2 = name
str3 = reason
}, {
@@ -183,26 +184,49 @@ class NotifCollectionLogger @Inject constructor(
})
}
- fun logNoNotificationToRemoveWithKey(key: String, @CancellationReason reason: Int) {
+ fun logNoNotificationToRemoveWithKey(
+ sbn: StatusBarNotification,
+ @CancellationReason reason: Int
+ ) {
buffer.log(TAG, ERROR, {
- str1 = key
+ str1 = sbn.logKey
int1 = reason
}, {
"No notification to remove with key $str1 reason=${cancellationReasonDebugString(int1)}"
})
}
- fun logRankingMissing(key: String, rankingMap: RankingMap) {
- buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" })
- buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" })
- for (entry in rankingMap.orderedKeys) {
- buffer.log(TAG, DEBUG, { str1 = entry }, { " $str1" })
- }
+ fun logMissingRankings(
+ newlyInconsistentEntries: List<NotificationEntry>,
+ totalInconsistent: Int,
+ rankingMap: RankingMap
+ ) {
+ buffer.log(TAG, WARNING, {
+ int1 = totalInconsistent
+ int2 = newlyInconsistentEntries.size
+ str1 = newlyInconsistentEntries.joinToString { it.logKey ?: "null" }
+ }, {
+ "Ranking update is missing ranking for $int1 entries ($int2 new): $str1"
+ })
+ buffer.log(TAG, DEBUG, {
+ str1 = rankingMap.orderedKeys.map { logKey(it) ?: "null" }.toString()
+ }, {
+ "Ranking map contents: $str1"
+ })
+ }
+
+ fun logRecoveredRankings(newlyConsistentKeys: List<String>) {
+ buffer.log(TAG, INFO, {
+ int1 = newlyConsistentKeys.size
+ str1 = newlyConsistentKeys.joinToString { logKey(it) ?: "null" }
+ }, {
+ "Ranking update now contains rankings for $int1 previously inconsistent entries: $str1"
+ })
}
- fun logRemoteExceptionOnNotificationClear(key: String, e: RemoteException) {
+ fun logRemoteExceptionOnNotificationClear(entry: NotificationEntry, e: RemoteException) {
buffer.log(TAG, WTF, {
- str1 = key
+ str1 = entry.logKey
str2 = e.toString()
}, {
"RemoteException while attempting to clear $str1:\n$str2"
@@ -217,9 +241,9 @@ class NotifCollectionLogger @Inject constructor(
})
}
- fun logLifetimeExtended(key: String, extender: NotifLifetimeExtender) {
+ fun logLifetimeExtended(entry: NotificationEntry, extender: NotifLifetimeExtender) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
str2 = extender.name
}, {
"LIFETIME EXTENDED: $str1 by $str2"
@@ -227,12 +251,12 @@ class NotifCollectionLogger @Inject constructor(
}
fun logLifetimeExtensionEnded(
- key: String,
+ entry: NotificationEntry,
extender: NotifLifetimeExtender,
totalExtenders: Int
) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
str2 = extender.name
int1 = totalExtenders
}, {
@@ -338,4 +362,29 @@ class NotifCollectionLogger @Inject constructor(
}
}
+fun maybeLogInconsistentRankings(
+ logger: NotifCollectionLogger,
+ oldKeysWithoutRankings: Set<String>,
+ newEntriesWithoutRankings: Map<String, NotificationEntry>?,
+ rankingMap: RankingMap
+) {
+ if (oldKeysWithoutRankings.isEmpty() && newEntriesWithoutRankings == null) return
+ val newlyConsistent: List<String> = oldKeysWithoutRankings
+ .mapNotNull { key ->
+ key.takeIf { key !in (newEntriesWithoutRankings ?: emptyMap()) }
+ .takeIf { key in rankingMap.orderedKeys }
+ }.sorted()
+ if (newlyConsistent.isNotEmpty()) {
+ logger.logRecoveredRankings(newlyConsistent)
+ }
+ val newlyInconsistent: List<NotificationEntry> = newEntriesWithoutRankings
+ ?.mapNotNull { (key, entry) ->
+ entry.takeIf { key !in oldKeysWithoutRankings }
+ }?.sortedBy { it.key } ?: emptyList()
+ if (newlyInconsistent.isNotEmpty()) {
+ val totalInconsistent: Int = newEntriesWithoutRankings?.size ?: 0
+ logger.logMissingRankings(newlyInconsistent, totalInconsistent, rankingMap)
+ }
+}
+
private const val TAG = "NotifCollection"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
index 68bdd18c9881..82c7aae08f6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification
+package com.android.systemui.statusbar.notification.collection.provider
import android.content.Context
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
/**
@@ -34,7 +34,7 @@ import javax.inject.Inject
class SectionHeaderVisibilityProvider @Inject constructor(
context: Context
) {
- var neverShowSectionHeaders = context.resources.getBoolean(R.bool.config_notification_never_show_section_headers)
- private set
+ val neverShowSectionHeaders =
+ context.resources.getBoolean(R.bool.config_notification_never_show_section_headers)
var sectionHeadersVisible = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionClassifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
index 1f2d0fe6c46e..7b9483022fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionClassifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification
+package com.android.systemui.statusbar.notification.collection.provider
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
@@ -26,7 +26,7 @@ import javax.inject.Inject
* NOTE: This class exists to avoid putting metadata like "isMinimized" on the NotifSection
*/
@SysUISingleton
-class SectionClassifier @Inject constructor() {
+class SectionStyleProvider @Inject constructor() {
private lateinit var lowPrioritySections: Set<NotifSectioner>
/**
@@ -43,4 +43,4 @@ class SectionClassifier @Inject constructor() {
fun isMinimizedSection(section: NotifSection): Boolean {
return lowPrioritySections.contains(section.sectioner)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
index 8be710c8842c..d234e54e6725 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
@@ -16,12 +16,12 @@
package com.android.systemui.statusbar.notification.collection.render
-import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.util.Compile
import com.android.systemui.util.traceSection
@@ -107,4 +107,4 @@ class NodeSpecBuilder(
.apply { entry.children.forEach { children.add(buildNotifNode(this, it)) } }
else -> throw RuntimeException("Unexpected entry: $entry")
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
index 3501b44a2c2e..38e3d496a60c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
@@ -19,20 +19,24 @@ package com.android.systemui.statusbar.notification.collection.render
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.util.Compile
import javax.inject.Inject
class NodeSpecBuilderLogger @Inject constructor(
+ notifPipelineFlags: NotifPipelineFlags,
@NotificationLog private val buffer: LogBuffer
) {
+ private val devLoggingEnabled by lazy { notifPipelineFlags.isDevLoggingEnabled() }
+
fun logBuildNodeSpec(
oldSections: Set<NotifSection?>,
newHeaders: Map<NotifSection?, NodeController?>,
newCounts: Map<NotifSection?, Int>,
newSectionOrder: List<NotifSection?>
) {
- if (!Compile.IS_DEBUG)
+ if (!(Compile.IS_DEBUG && devLoggingEnabled))
return
buffer.log(TAG, LogLevel.DEBUG, {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index 6ed81078c3a4..51dc72848d9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -19,10 +19,10 @@ package com.android.systemui.statusbar.notification.collection.render
import android.content.Context
import android.view.View
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
-import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.util.traceSection
import dagger.assisted.Assisted
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
index 19cf9dc135b1..5ef2b9e55d9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
@@ -83,7 +83,7 @@ public class HeadsUpViewBinder {
params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
CancellationSignal signal = mStage.requestRebind(entry, en -> {
- mLogger.entryBoundSuccessfully(entry.getKey());
+ mLogger.entryBoundSuccessfully(entry);
en.getRow().setUsesIncreasedHeadsUpHeight(params.useIncreasedHeadsUpHeight());
// requestRebing promises that if we called cancel before this callback would be
// invoked, then we will not enter this callback, and because we always cancel before
@@ -94,7 +94,7 @@ public class HeadsUpViewBinder {
}
});
abortBindCallback(entry);
- mLogger.startBindingHun(entry.getKey());
+ mLogger.startBindingHun(entry);
mOngoingBindCallbacks.put(entry, signal);
}
@@ -105,7 +105,7 @@ public class HeadsUpViewBinder {
public void abortBindCallback(NotificationEntry entry) {
CancellationSignal ongoingBindCallback = mOngoingBindCallbacks.remove(entry);
if (ongoingBindCallback != null) {
- mLogger.currentOngoingBindingAborted(entry.getKey());
+ mLogger.currentOngoingBindingAborted(entry);
ongoingBindCallback.cancel();
}
}
@@ -116,7 +116,7 @@ public class HeadsUpViewBinder {
public void unbindHeadsUpView(NotificationEntry entry) {
abortBindCallback(entry);
mStage.getStageParams(entry).markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP);
- mLogger.entryContentViewMarkedFreeable(entry.getKey());
- mStage.requestRebind(entry, e -> mLogger.entryUnbound(e.getKey()));
+ mLogger.entryContentViewMarkedFreeable(entry);
+ mStage.requestRebind(entry, e -> mLogger.entryUnbound(e));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
index 50a6207efe0b..d1feaa05c653 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
@@ -3,44 +3,46 @@ package com.android.systemui.statusbar.notification.interruption
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class HeadsUpViewBinderLogger @Inject constructor(@NotificationHeadsUpLog val buffer: LogBuffer) {
- fun startBindingHun(key: String) {
+ fun startBindingHun(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"start binding heads up entry $str1 "
})
}
- fun currentOngoingBindingAborted(key: String) {
+ fun currentOngoingBindingAborted(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"aborted potential ongoing heads up entry binding $str1 "
})
}
- fun entryBoundSuccessfully(key: String) {
+ fun entryBoundSuccessfully(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"heads up entry bound successfully $str1 "
})
}
- fun entryUnbound(key: String) {
+ fun entryUnbound(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"heads up entry unbound successfully $str1 "
})
}
- fun entryContentViewMarkedFreeable(key: String) {
+ fun entryContentViewMarkedFreeable(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"start unbinding heads up entry $str1 "
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 1d18ca3dfade..016b388ff60a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -16,11 +16,12 @@
package com.android.systemui.statusbar.notification.interruption
-import android.service.notification.StatusBarNotification
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationInterruptLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class NotificationInterruptLogger @Inject constructor(
@@ -41,17 +42,17 @@ class NotificationInterruptLogger @Inject constructor(
})
}
- fun logNoBubbleNotAllowed(sbn: StatusBarNotification) {
+ fun logNoBubbleNotAllowed(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No bubble up: not allowed to bubble: $str1"
})
}
- fun logNoBubbleNoMetadata(sbn: StatusBarNotification) {
+ fun logNoBubbleNoMetadata(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No bubble up: notification: $str1 doesn't have valid metadata"
})
@@ -64,89 +65,89 @@ class NotificationInterruptLogger @Inject constructor(
})
}
- fun logNoHeadsUpPackageSnoozed(sbn: StatusBarNotification) {
+ fun logNoHeadsUpPackageSnoozed(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No alerting: snoozed package: $str1"
})
}
- fun logNoHeadsUpAlreadyBubbled(sbn: StatusBarNotification) {
+ fun logNoHeadsUpAlreadyBubbled(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No heads up: in unlocked shade where notification is shown as a bubble: $str1"
})
}
- fun logNoHeadsUpSuppressedByDnd(sbn: StatusBarNotification) {
+ fun logNoHeadsUpSuppressedByDnd(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No heads up: suppressed by DND: $str1"
})
}
- fun logNoHeadsUpNotImportant(sbn: StatusBarNotification) {
+ fun logNoHeadsUpNotImportant(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No heads up: unimportant notification: $str1"
})
}
- fun logNoHeadsUpNotInUse(sbn: StatusBarNotification) {
+ fun logNoHeadsUpNotInUse(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No heads up: not in use: $str1"
})
}
fun logNoHeadsUpSuppressedBy(
- sbn: StatusBarNotification,
+ entry: NotificationEntry,
suppressor: NotificationInterruptSuppressor
) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
str2 = suppressor.name
}, {
"No heads up: aborted by suppressor: $str2 sbnKey=$str1"
})
}
- fun logHeadsUp(sbn: StatusBarNotification) {
+ fun logHeadsUp(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"Heads up: $str1"
})
}
- fun logNoAlertingFilteredOut(sbn: StatusBarNotification) {
+ fun logNoAlertingFilteredOut(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No alerting: filtered notification: $str1"
})
}
- fun logNoAlertingGroupAlertBehavior(sbn: StatusBarNotification) {
+ fun logNoAlertingGroupAlertBehavior(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No alerting: suppressed due to group alert behavior: $str1"
})
}
fun logNoAlertingSuppressedBy(
- sbn: StatusBarNotification,
+ entry: NotificationEntry,
suppressor: NotificationInterruptSuppressor,
awake: Boolean
) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
str2 = suppressor.name
bool1 = awake
}, {
@@ -154,65 +155,65 @@ class NotificationInterruptLogger @Inject constructor(
})
}
- fun logNoAlertingRecentFullscreen(sbn: StatusBarNotification) {
+ fun logNoAlertingRecentFullscreen(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No alerting: recent fullscreen: $str1"
})
}
- fun logNoPulsingSettingDisabled(sbn: StatusBarNotification) {
+ fun logNoPulsingSettingDisabled(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No pulsing: disabled by setting: $str1"
})
}
- fun logNoPulsingBatteryDisabled(sbn: StatusBarNotification) {
+ fun logNoPulsingBatteryDisabled(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No pulsing: disabled by battery saver: $str1"
})
}
- fun logNoPulsingNoAlert(sbn: StatusBarNotification) {
+ fun logNoPulsingNoAlert(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No pulsing: notification shouldn't alert: $str1"
})
}
- fun logNoPulsingNoAmbientEffect(sbn: StatusBarNotification) {
+ fun logNoPulsingNoAmbientEffect(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No pulsing: ambient effect suppressed: $str1"
})
}
- fun logNoPulsingNotImportant(sbn: StatusBarNotification) {
+ fun logNoPulsingNotImportant(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"No pulsing: not important enough: $str1"
})
}
- fun logPulsing(sbn: StatusBarNotification) {
+ fun logPulsing(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ str1 = entry.logKey
}, {
"Pulsing: $str1"
})
}
- fun keyguardHideNotification(key: String) {
+ fun keyguardHideNotification(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = key
+ str1 = entry.logKey
}, {
"Keyguard Hide Notification: $str1"
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index a063dbd99626..8378b69bee9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -147,14 +147,14 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
}
if (!entry.canBubble()) {
- mLogger.logNoBubbleNotAllowed(sbn);
+ mLogger.logNoBubbleNotAllowed(entry);
return false;
}
if (entry.getBubbleMetadata() == null
|| (entry.getBubbleMetadata().getShortcutId() == null
&& entry.getBubbleMetadata().getIntent() == null)) {
- mLogger.logNoBubbleNoMetadata(sbn);
+ mLogger.logNoBubbleNoMetadata(entry);
return false;
}
@@ -203,23 +203,23 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
}
if (isSnoozedPackage(sbn)) {
- mLogger.logNoHeadsUpPackageSnoozed(sbn);
+ mLogger.logNoHeadsUpPackageSnoozed(entry);
return false;
}
boolean inShade = mStatusBarStateController.getState() == SHADE;
if (entry.isBubble() && inShade) {
- mLogger.logNoHeadsUpAlreadyBubbled(sbn);
+ mLogger.logNoHeadsUpAlreadyBubbled(entry);
return false;
}
if (entry.shouldSuppressPeek()) {
- mLogger.logNoHeadsUpSuppressedByDnd(sbn);
+ mLogger.logNoHeadsUpSuppressedByDnd(entry);
return false;
}
if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
- mLogger.logNoHeadsUpNotImportant(sbn);
+ mLogger.logNoHeadsUpNotImportant(entry);
return false;
}
@@ -232,17 +232,17 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
boolean inUse = mPowerManager.isScreenOn() && !isDreaming;
if (!inUse) {
- mLogger.logNoHeadsUpNotInUse(sbn);
+ mLogger.logNoHeadsUpNotInUse(entry);
return false;
}
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) {
- mLogger.logNoHeadsUpSuppressedBy(sbn, mSuppressors.get(i));
+ mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i));
return false;
}
}
- mLogger.logHeadsUp(sbn);
+ mLogger.logHeadsUp(entry);
return true;
}
@@ -254,38 +254,36 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
* @return true if the entry should ambient pulse, false otherwise
*/
private boolean shouldHeadsUpWhenDozing(NotificationEntry entry) {
- StatusBarNotification sbn = entry.getSbn();
-
if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) {
- mLogger.logNoPulsingSettingDisabled(sbn);
+ mLogger.logNoPulsingSettingDisabled(entry);
return false;
}
if (mBatteryController.isAodPowerSave()) {
- mLogger.logNoPulsingBatteryDisabled(sbn);
+ mLogger.logNoPulsingBatteryDisabled(entry);
return false;
}
if (!canAlertCommon(entry)) {
- mLogger.logNoPulsingNoAlert(sbn);
+ mLogger.logNoPulsingNoAlert(entry);
return false;
}
if (!canAlertHeadsUpCommon(entry)) {
- mLogger.logNoPulsingNoAlert(sbn);
+ mLogger.logNoPulsingNoAlert(entry);
return false;
}
if (entry.shouldSuppressAmbient()) {
- mLogger.logNoPulsingNoAmbientEffect(sbn);
+ mLogger.logNoPulsingNoAmbientEffect(entry);
return false;
}
if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
- mLogger.logNoPulsingNotImportant(sbn);
+ mLogger.logNoPulsingNotImportant(entry);
return false;
}
- mLogger.logPulsing(sbn);
+ mLogger.logPulsing(entry);
return true;
}
@@ -296,22 +294,20 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
* @return true if these checks pass, false if the notification should not alert
*/
private boolean canAlertCommon(NotificationEntry entry) {
- StatusBarNotification sbn = entry.getSbn();
-
if (!mFlags.isNewPipelineEnabled() && mNotificationFilter.shouldFilterOut(entry)) {
- mLogger.logNoAlertingFilteredOut(sbn);
+ mLogger.logNoAlertingFilteredOut(entry);
return false;
}
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressInterruptions(entry)) {
- mLogger.logNoAlertingSuppressedBy(sbn, mSuppressors.get(i), /* awake */ false);
+ mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ false);
return false;
}
}
if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) {
- mLogger.keyguardHideNotification(entry.getKey());
+ mLogger.keyguardHideNotification(entry);
return false;
}
@@ -329,12 +325,12 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
// Don't alert notifications that are suppressed due to group alert behavior
if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
- mLogger.logNoAlertingGroupAlertBehavior(sbn);
+ mLogger.logNoAlertingGroupAlertBehavior(entry);
return false;
}
if (entry.hasJustLaunchedFullScreenIntent()) {
- mLogger.logNoAlertingRecentFullscreen(sbn);
+ mLogger.logNoAlertingRecentFullscreen(entry);
return false;
}
@@ -352,7 +348,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressAwakeInterruptions(entry)) {
- mLogger.logNoAlertingSuppressedBy(sbn, mSuppressors.get(i), /* awake */ true);
+ mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ true);
return false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 599039d46556..a493a676e3d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import android.util.Log;
import android.view.View;
@@ -247,7 +248,7 @@ public class ExpandableNotificationRowController implements NotifViewController
@Override
@NonNull
public String getNodeLabel() {
- return mView.getEntry().getKey();
+ return logKey(mView.getEntry());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
index c66140822d92..99a24cb3e9ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
@@ -67,6 +67,7 @@ public class HybridConversationNotificationView extends HybridNotificationView {
mConversationIconView = requireViewById(com.android.internal.R.id.conversation_icon);
mConversationFacePile = requireViewById(com.android.internal.R.id.conversation_face_pile);
mConversationSenderName = requireViewById(R.id.conversation_notification_sender);
+ applyTextColor(mConversationSenderName, mSecondaryTextColor);
mFacePileSize = getResources()
.getDimensionPixelSize(R.dimen.conversation_single_line_face_pile_size);
mFacePileAvatarSize = getResources()
@@ -75,6 +76,9 @@ public class HybridConversationNotificationView extends HybridNotificationView {
.getDimensionPixelSize(R.dimen.conversation_single_line_avatar_size);
mFacePileProtectionWidth = getResources().getDimensionPixelSize(
R.dimen.conversation_single_line_face_pile_protection_width);
+ mTransformationHelper.setCustomTransformation(
+ new FadeOutAndDownWithTitleTransformation(mConversationSenderName),
+ mConversationSenderName.getId());
mTransformationHelper.addViewTransformingToSimilar(mConversationIconView);
mTransformationHelper.addTransformedView(mConversationSenderName);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
index 40a44ffd7fe3..77fd05186090 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
@@ -24,7 +24,6 @@ import android.content.Context;
import android.content.res.Resources;
import android.service.notification.StatusBarNotification;
import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -57,10 +56,8 @@ public class HybridGroupManager {
mOverflowNumberPadding = res.getDimensionPixelSize(R.dimen.group_overflow_number_padding);
}
- private HybridNotificationView inflateHybridViewWithStyle(int style,
- View contentView, ViewGroup parent) {
- LayoutInflater inflater = new ContextThemeWrapper(mContext, style)
- .getSystemService(LayoutInflater.class);
+ private HybridNotificationView inflateHybridView(View contentView, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
int layout = contentView instanceof ConversationLayout
? R.layout.hybrid_conversation_notification
: R.layout.hybrid_notification;
@@ -93,16 +90,8 @@ public class HybridGroupManager {
public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
View contentView, StatusBarNotification notification,
ViewGroup parent) {
- return bindFromNotificationWithStyle(reusableView, contentView, notification,
- R.style.HybridNotification, parent);
- }
-
- private HybridNotificationView bindFromNotificationWithStyle(
- HybridNotificationView reusableView, View contentView,
- StatusBarNotification notification,
- int style, ViewGroup parent) {
if (reusableView == null) {
- reusableView = inflateHybridViewWithStyle(style, contentView, parent);
+ reusableView = inflateHybridView(contentView, parent);
}
CharSequence titleText = resolveTitle(notification.getNotification());
CharSequence contentText = resolveText(notification.getNotification());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
index c0d85a6a16ef..fc9d9e8b736c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
@@ -16,13 +16,18 @@
package com.android.systemui.statusbar.notification.row;
+import static android.app.Notification.COLOR_INVALID;
+
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.TypedArray;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
+import androidx.annotation.ColorInt;
+
import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -40,6 +45,8 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
protected final ViewTransformationHelper mTransformationHelper = new ViewTransformationHelper();
protected TextView mTitleView;
protected TextView mTextView;
+ protected int mPrimaryTextColor = COLOR_INVALID;
+ protected int mSecondaryTextColor = COLOR_INVALID;
public HybridNotificationView(Context context) {
this(context, null);
@@ -69,42 +76,37 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ resolveThemeTextColors();
mTitleView = findViewById(R.id.notification_title);
mTextView = findViewById(R.id.notification_text);
+ applyTextColor(mTitleView, mPrimaryTextColor);
+ applyTextColor(mTextView, mSecondaryTextColor);
mTransformationHelper.setCustomTransformation(
- new ViewTransformationHelper.CustomTransformation() {
- @Override
- public boolean transformTo(TransformState ownState, TransformableView notification,
- float transformationAmount) {
- // We want to transform to the same y location as the title
- TransformState otherState = notification.getCurrentState(
- TRANSFORMING_VIEW_TITLE);
- CrossFadeHelper.fadeOut(mTextView, transformationAmount);
- if (otherState != null) {
- ownState.transformViewVerticalTo(otherState, transformationAmount);
- otherState.recycle();
- }
- return true;
- }
-
- @Override
- public boolean transformFrom(TransformState ownState,
- TransformableView notification, float transformationAmount) {
- // We want to transform from the same y location as the title
- TransformState otherState = notification.getCurrentState(
- TRANSFORMING_VIEW_TITLE);
- CrossFadeHelper.fadeIn(mTextView, transformationAmount, true /* remap */);
- if (otherState != null) {
- ownState.transformViewVerticalFrom(otherState, transformationAmount);
- otherState.recycle();
- }
- return true;
- }
- }, TRANSFORMING_VIEW_TEXT);
+ new FadeOutAndDownWithTitleTransformation(mTextView),
+ TRANSFORMING_VIEW_TEXT);
mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TITLE, mTitleView);
mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TEXT, mTextView);
}
+ protected void applyTextColor(TextView textView, @ColorInt int textColor) {
+ if (textColor != COLOR_INVALID) {
+ textView.setTextColor(textColor);
+ }
+ }
+
+ private void resolveThemeTextColors() {
+ try (TypedArray ta = mContext.getTheme().obtainStyledAttributes(
+ android.R.style.Theme_DeviceDefault_DayNight, new int[]{
+ android.R.attr.textColorPrimary,
+ android.R.attr.textColorSecondary
+ })) {
+ if (ta != null) {
+ mPrimaryTextColor = ta.getColor(0, mPrimaryTextColor);
+ mSecondaryTextColor = ta.getColor(1, mSecondaryTextColor);
+ }
+ }
+ }
+
public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
@Nullable View contentView) {
mTitleView.setText(title);
@@ -152,4 +154,40 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
@Override
public void setNotificationFaded(boolean faded) {}
+
+ protected static class FadeOutAndDownWithTitleTransformation extends
+ ViewTransformationHelper.CustomTransformation {
+
+ private final View mView;
+
+ public FadeOutAndDownWithTitleTransformation(View view) {
+ mView = view;
+ }
+
+ @Override
+ public boolean transformTo(TransformState ownState, TransformableView notification,
+ float transformationAmount) {
+ // We want to transform to the same y location as the title
+ TransformState otherState = notification.getCurrentState(TRANSFORMING_VIEW_TITLE);
+ CrossFadeHelper.fadeOut(mView, transformationAmount);
+ if (otherState != null) {
+ ownState.transformViewVerticalTo(otherState, transformationAmount);
+ otherState.recycle();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean transformFrom(TransformState ownState,
+ TransformableView notification, float transformationAmount) {
+ // We want to transform from the same y location as the title
+ TransformState otherState = notification.getCurrentState(TRANSFORMING_VIEW_TITLE);
+ CrossFadeHelper.fadeIn(mView, transformationAmount, true /* remap */);
+ if (otherState != null) {
+ ownState.transformViewVerticalFrom(otherState, transformationAmount);
+ otherState.recycle();
+ }
+ return true;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
index f693ebbb7830..ea564ddb9193 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
@@ -112,7 +112,8 @@ public final class NotifBindPipeline {
public void manageRow(
@NonNull NotificationEntry entry,
@NonNull ExpandableNotificationRow row) {
- mLogger.logManagedRow(entry.getKey());
+ mLogger.logManagedRow(entry);
+ mLogger.logManagedRow(entry);
final BindEntry bindEntry = getBindEntry(entry);
if (bindEntry == null) {
@@ -154,12 +155,12 @@ public final class NotifBindPipeline {
* the real work once rather than repeatedly start and cancel it.
*/
private void requestPipelineRun(NotificationEntry entry) {
- mLogger.logRequestPipelineRun(entry.getKey());
+ mLogger.logRequestPipelineRun(entry);
final BindEntry bindEntry = getBindEntry(entry);
if (bindEntry.row == null) {
// Row is not managed yet but may be soon. Stop for now.
- mLogger.logRequestPipelineRowNotSet(entry.getKey());
+ mLogger.logRequestPipelineRowNotSet(entry);
return;
}
@@ -177,7 +178,7 @@ public final class NotifBindPipeline {
* callbacks when the run finishes. If a run is already in progress, it is restarted.
*/
private void startPipeline(NotificationEntry entry) {
- mLogger.logStartPipeline(entry.getKey());
+ mLogger.logStartPipeline(entry);
if (mStage == null) {
throw new IllegalStateException("No stage was ever set on the pipeline");
@@ -193,7 +194,7 @@ public final class NotifBindPipeline {
final BindEntry bindEntry = getBindEntry(entry);
final Set<BindCallback> callbacks = bindEntry.callbacks;
- mLogger.logFinishedPipeline(entry.getKey(), callbacks.size());
+ mLogger.logFinishedPipeline(entry, callbacks.size());
bindEntry.invalidated = false;
// Move all callbacks to separate list as callbacks may themselves add/remove callbacks.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index ec406f0524ff..ab91926d466a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.row
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class NotifBindPipelineLogger @Inject constructor(
@@ -32,41 +34,41 @@ class NotifBindPipelineLogger @Inject constructor(
})
}
- fun logManagedRow(notifKey: String) {
+ fun logManagedRow(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = notifKey
+ str1 = entry.logKey
}, {
"Row set for notif: $str1"
})
}
- fun logRequestPipelineRun(notifKey: String) {
+ fun logRequestPipelineRun(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = notifKey
+ str1 = entry.logKey
}, {
"Request pipeline run for notif: $str1"
})
}
- fun logRequestPipelineRowNotSet(notifKey: String) {
+ fun logRequestPipelineRowNotSet(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = notifKey
+ str1 = entry.logKey
}, {
"Row is not set so pipeline will not run. notif = $str1"
})
}
- fun logStartPipeline(notifKey: String) {
+ fun logStartPipeline(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = notifKey
+ str1 = entry.logKey
}, {
"Start pipeline for notif: $str1"
})
}
- fun logFinishedPipeline(notifKey: String, numCallbacks: Int) {
+ fun logFinishedPipeline(entry: NotificationEntry, numCallbacks: Int) {
buffer.log(TAG, INFO, {
- str1 = notifKey
+ str1 = entry.logKey
int1 = numCallbacks
}, {
"Finished pipeline for notif $str1 with $int1 callbacks"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 134f24e7e646..27aa4b38e0ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -266,9 +266,14 @@ public class NotificationConversationInfo extends LinearLayout implements
snooze.setOnClickListener(mOnSnoozeClick);
*/
- if (mAppBubble == BUBBLE_PREFERENCE_ALL) {
- ((TextView) findViewById(R.id.default_summary)).setText(getResources().getString(
+ TextView defaultSummaryTextView = findViewById(R.id.default_summary);
+ if (mAppBubble == BUBBLE_PREFERENCE_ALL
+ && BubblesManager.areBubblesEnabled(mContext, mSbn.getUser())) {
+ defaultSummaryTextView.setText(getResources().getString(
R.string.notification_channel_summary_default_with_bubbles, mAppName));
+ } else {
+ defaultSummaryTextView.setText(getResources().getString(
+ R.string.notification_channel_summary_default));
}
findViewById(R.id.priority).setOnClickListener(mOnFavoriteClick);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index 3616f8faee1e..81cf14646465 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -57,7 +57,7 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> {
@NonNull StageCallback callback) {
RowContentBindParams params = getStageParams(entry);
- mLogger.logStageParams(entry.getKey(), params.toString());
+ mLogger.logStageParams(entry, params);
// Resolve content to bind/unbind.
@InflationFlag int inflationFlags = params.getContentViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
index 29cce3375c8a..f9923b2254d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -19,17 +19,19 @@ package com.android.systemui.statusbar.notification.row
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class RowContentBindStageLogger @Inject constructor(
@NotificationLog private val buffer: LogBuffer
) {
- fun logStageParams(notifKey: String, stageParams: String) {
+ fun logStageParams(entry: NotificationEntry, stageParams: RowContentBindParams) {
buffer.log(TAG, INFO, {
- str1 = notifKey
- str2 = stageParams
+ str1 = entry.logKey
+ str2 = stageParams.toString()
}, {
- "Invalidated notif $str1 with params: \n$str2"
+ "Invalidated notif $str1 with params: $str2"
})
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 213f00b8e03d..2fd02d9f1cd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -748,19 +748,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
- private void logHunSkippedForUnexpectedState(String key, boolean expected, boolean actual) {
+ private void logHunSkippedForUnexpectedState(ExpandableNotificationRow enr,
+ boolean expected, boolean actual) {
if (mLogger == null) return;
- mLogger.hunSkippedForUnexpectedState(key, expected, actual);
+ mLogger.hunSkippedForUnexpectedState(enr.getEntry(), expected, actual);
}
- private void logHunAnimationSkipped(String key, String reason) {
+ private void logHunAnimationSkipped(ExpandableNotificationRow enr, String reason) {
if (mLogger == null) return;
- mLogger.hunAnimationSkipped(key, reason);
+ mLogger.hunAnimationSkipped(enr.getEntry(), reason);
}
- private void logHunAnimationEventAdded(String key, int type) {
+ private void logHunAnimationEventAdded(ExpandableNotificationRow enr, int type) {
if (mLogger == null) return;
- mLogger.hunAnimationEventAdded(key, type);
+ mLogger.hunAnimationEventAdded(enr.getEntry(), type);
}
private void onDrawDebug(Canvas canvas) {
@@ -3174,7 +3175,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (isHeadsUp != row.isHeadsUp()) {
// For cases where we have a heads up showing and appearing again we shouldn't
// do the animations at all.
- logHunSkippedForUnexpectedState(key, isHeadsUp, row.isHeadsUp());
+ logHunSkippedForUnexpectedState(row, isHeadsUp, row.isHeadsUp());
continue;
}
int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
@@ -3192,7 +3193,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (row.isChildInGroup()) {
// We can otherwise get stuck in there if it was just isolated
row.setHeadsUpAnimatingAway(false);
- logHunAnimationSkipped(key, "row is child in group");
+ logHunAnimationSkipped(row, "row is child in group");
continue;
}
} else {
@@ -3200,7 +3201,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (viewState == null) {
// A view state was never generated for this view, so we don't need to animate
// this. This may happen with notification children.
- logHunAnimationSkipped(key, "row has no viewState");
+ logHunAnimationSkipped(row, "row has no viewState");
continue;
}
if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
@@ -3224,7 +3225,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
+ " onBottom=" + onBottom
+ " row=" + row.getEntry().getKey());
}
- logHunAnimationEventAdded(key, type);
+ logHunAnimationEventAdded(row, type);
}
mHeadsUpChangeAnimations.clear();
mAddedHeadsUpChildren.clear();
@@ -4360,8 +4361,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/**
* Update colors of "dismiss" and "empty shade" views.
- *
- * @param lightTheme True if light theme should be used.
*/
@ShadeViewRefactor(RefactorComponent.DECORATOR)
void updateDecorViews() {
@@ -4777,8 +4776,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (SPEW) {
Log.v(TAG, "generateHeadsUpAnimation: previous hun appear animation cancelled");
}
- logHunAnimationSkipped(row.getEntry().getKey(),
- "previous hun appear animation cancelled");
+ logHunAnimationSkipped(row, "previous hun appear animation cancelled");
return;
}
mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 04bf62104f66..5f79c0e3913a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -3,21 +3,27 @@ package com.android.systemui.statusbar.notification.stack
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.*
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER
import javax.inject.Inject
class NotificationStackScrollLogger @Inject constructor(
@NotificationHeadsUpLog private val buffer: LogBuffer
) {
- fun hunAnimationSkipped(key: String, reason: String) {
+ fun hunAnimationSkipped(entry: NotificationEntry, reason: String) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
str2 = reason
}, {
"heads up animation skipped: key: $str1 reason: $str2"
})
}
- fun hunAnimationEventAdded(key: String, type: Int) {
+ fun hunAnimationEventAdded(entry: NotificationEntry, type: Int) {
val reason: String
reason = if (type == ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
"HEADS_UP_DISAPPEAR"
@@ -33,16 +39,16 @@ class NotificationStackScrollLogger @Inject constructor(
type.toString()
}
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
str2 = reason
}, {
"heads up animation added: $str1 with type $str2"
})
}
- fun hunSkippedForUnexpectedState(key: String, expected: Boolean, actual: Boolean) {
+ fun hunSkippedForUnexpectedState(entry: NotificationEntry, expected: Boolean, actual: Boolean) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
bool1 = expected
bool2 = actual
}, {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index 77377af9ddfb..cb4a0884fea4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -3,6 +3,7 @@ package com.android.systemui.statusbar.notification.stack
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class StackStateLogger @Inject constructor(
@@ -10,7 +11,7 @@ class StackStateLogger @Inject constructor(
) {
fun logHUNViewDisappearing(key: String) {
buffer.log(TAG, LogLevel.INFO, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up view disappearing $str1 "
})
@@ -18,7 +19,7 @@ class StackStateLogger @Inject constructor(
fun logHUNViewAppearing(key: String) {
buffer.log(TAG, LogLevel.INFO, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up notification view appearing $str1 "
})
@@ -26,7 +27,7 @@ class StackStateLogger @Inject constructor(
fun logHUNViewDisappearingWithRemoveEvent(key: String) {
buffer.log(TAG, LogLevel.ERROR, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE"
})
@@ -34,7 +35,7 @@ class StackStateLogger @Inject constructor(
fun logHUNViewAppearingWithAddEvent(key: String) {
buffer.log(TAG, LogLevel.ERROR, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up view disappearing $str1 for ANIMATION_TYPE_ADD"
})
@@ -42,7 +43,7 @@ class StackStateLogger @Inject constructor(
fun disappearAnimationEnded(key: String) {
buffer.log(TAG, LogLevel.INFO, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up notification disappear animation ended $str1 "
})
@@ -50,7 +51,7 @@ class StackStateLogger @Inject constructor(
fun appearAnimationEnded(key: String) {
buffer.log(TAG, LogLevel.INFO, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up notification appear animation ended $str1 "
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index fd307df8d304..93b2e41e595b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -28,17 +28,13 @@ import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNL
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
@@ -46,13 +42,8 @@ import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.provider.MediaStore;
-import android.service.media.CameraPrewarmService;
import android.service.quickaccesswallet.GetWalletCardsError;
import android.service.quickaccesswallet.GetWalletCardsResponse;
import android.service.quickaccesswallet.QuickAccessWalletClient;
@@ -172,20 +163,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private KeyguardAffordanceHelper mAffordanceHelper;
private FalsingManager mFalsingManager;
private boolean mUserSetupComplete;
- private boolean mPrewarmBound;
- private Messenger mPrewarmMessenger;
- private final ServiceConnection mPrewarmConnection = new ServiceConnection() {
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mPrewarmMessenger = new Messenger(service);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mPrewarmMessenger = null;
- }
- };
private boolean mLeftIsVoiceAssist;
private Drawable mLeftAssistIcon;
@@ -602,46 +579,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
}
- public void bindCameraPrewarmService() {
- Intent intent = getCameraIntent();
- ActivityInfo targetInfo = mActivityIntentHelper.getTargetActivityInfo(intent,
- KeyguardUpdateMonitor.getCurrentUser(), true /* onlyDirectBootAware */);
- if (targetInfo != null && targetInfo.metaData != null) {
- String clazz = targetInfo.metaData.getString(
- MediaStore.META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE);
- if (clazz != null) {
- Intent serviceIntent = new Intent();
- serviceIntent.setClassName(targetInfo.packageName, clazz);
- serviceIntent.setAction(CameraPrewarmService.ACTION_PREWARM);
- try {
- if (getContext().bindServiceAsUser(serviceIntent, mPrewarmConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
- new UserHandle(UserHandle.USER_CURRENT))) {
- mPrewarmBound = true;
- }
- } catch (SecurityException e) {
- Log.w(TAG, "Unable to bind to prewarm service package=" + targetInfo.packageName
- + " class=" + clazz, e);
- }
- }
- }
- }
-
- public void unbindCameraPrewarmService(boolean launched) {
- if (mPrewarmBound) {
- if (mPrewarmMessenger != null && launched) {
- try {
- mPrewarmMessenger.send(Message.obtain(null /* handler */,
- CameraPrewarmService.MSG_CAMERA_FIRED));
- } catch (RemoteException e) {
- Log.w(TAG, "Error sending camera fired message", e);
- }
- }
- mContext.unbindService(mPrewarmConnection);
- mPrewarmBound = false;
- }
- }
-
public void launchCamera(String source) {
final Intent intent = getCameraIntent();
intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
@@ -651,8 +588,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
AsyncTask.execute(new Runnable() {
@Override
public void run() {
- int result = ActivityManager.START_CANCELED;
-
// Normally an activity will set it's requested rotation
// animation on its window. However when launching an activity
// causes the orientation to change this is too late. In these cases
@@ -666,7 +601,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
o.setRotationAnimationHint(
WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
try {
- result = ActivityTaskManager.getService().startActivityAsUser(
+ ActivityTaskManager.getService().startActivityAsUser(
null, getContext().getBasePackageName(),
getContext().getAttributionTag(), intent,
intent.resolveTypeIfNeeded(getContext().getContentResolver()),
@@ -675,25 +610,12 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
} catch (RemoteException e) {
Log.w(TAG, "Unable to start camera activity", e);
}
- final boolean launched = isSuccessfulLaunch(result);
- post(new Runnable() {
- @Override
- public void run() {
- unbindCameraPrewarmService(launched);
- }
- });
}
});
} else {
// We need to delay starting the activity because ResolverActivity finishes itself if
// launched behind lockscreen.
- mActivityStarter.startActivity(intent, false /* dismissShade */,
- new ActivityStarter.Callback() {
- @Override
- public void onActivityStarted(int resultCode) {
- unbindCameraPrewarmService(isSuccessfulLaunch(resultCode));
- }
- });
+ mActivityStarter.startActivity(intent, false /* dismissShade */);
}
}
@@ -705,12 +627,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
dozeTimeTick();
}
- private static boolean isSuccessfulLaunch(int result) {
- return result == ActivityManager.START_SUCCESS
- || result == ActivityManager.START_DELIVERED_TO_TOP
- || result == ActivityManager.START_TASK_TO_FRONT;
- }
-
public void launchLeftAffordance() {
if (mLeftIsVoiceAssist) {
launchVoiceAssist();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 6009eba24f5b..fbbb587872b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1670,7 +1670,6 @@ public class NotificationPanelViewController extends PanelViewController {
setQsExpansionEnabled();
}
- @Override
public void resetViews(boolean animate) {
mIsLaunchTransitionFinished = false;
mBlockTouches = false;
@@ -4592,13 +4591,6 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void onSwipingStarted(boolean rightIcon) {
mFalsingCollector.onAffordanceSwipingStarted(rightIcon);
- boolean
- camera =
- mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
- : rightIcon;
- if (camera) {
- mKeyguardBottomArea.bindCameraPrewarmService();
- }
mView.requestDisallowInterceptTouchEvent(true);
mOnlyAffordanceInThisMotion = true;
mQsTracking = false;
@@ -4607,7 +4599,6 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void onSwipingAborted() {
mFalsingCollector.onAffordanceSwipingAborted();
- mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS
index 18f0fb38999c..f5828f914eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS
@@ -1,3 +1,16 @@
per-file *Notification* = set noparent
per-file *Notification* = file:../notification/OWNERS
-per-file NotificationIcon* = ccassidy@google.com, evanlaird@google.com, pixel@google.com \ No newline at end of file
+
+per-file NotificationIcon* = ccassidy@google.com, evanlaird@google.com, pixel@google.com
+
+per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com
+per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com
+
+per-file NotificationShadeWindowControllerImpl.java = dupin@google.com, cinek@google.com, beverlyt@google.com, pixel@google.com, juliacr@google.com
+per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com
+per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com
+
+per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com
+
+per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com
+per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 9f0ecb9d4096..ed12b00cc644 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -1163,8 +1163,6 @@ public abstract class PanelViewController {
mTouchDisabled ? "T" : "f"));
}
- public abstract void resetViews(boolean animate);
-
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 36a045637a87..26bc3e3c0920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -93,6 +93,17 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
}
public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) {
+ // TODO(b/219008720): Remove those calls to Dependency.get by introducing a
+ // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
+ // the content and attach listeners.
+ this(context, theme, dismissOnDeviceLock, Dependency.get(SystemUIDialogManager.class),
+ Dependency.get(SysUiState.class), Dependency.get(BroadcastDispatcher.class),
+ Dependency.get(DialogLaunchAnimator.class));
+ }
+
+ public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
+ SystemUIDialogManager dialogManager, SysUiState sysUiState,
+ BroadcastDispatcher broadcastDispatcher, DialogLaunchAnimator dialogLaunchAnimator) {
super(context, theme);
mContext = context;
@@ -101,13 +112,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
attrs.setTitle(getClass().getSimpleName());
getWindow().setAttributes(attrs);
- mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this) : null;
-
- // TODO(b/219008720): Remove those calls to Dependency.get by introducing a
- // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
- // the content and attach listeners.
- mDialogManager = Dependency.get(SystemUIDialogManager.class);
- mSysUiState = Dependency.get(SysUiState.class);
+ mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this, broadcastDispatcher,
+ dialogLaunchAnimator) : null;
+ mDialogManager = dialogManager;
+ mSysUiState = sysUiState;
}
@Override
@@ -326,7 +334,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
* @param dismissAction An action to run when the dialog is dismissed.
*/
public static void registerDismissListener(Dialog dialog, @Nullable Runnable dismissAction) {
- DismissReceiver dismissReceiver = new DismissReceiver(dialog);
+ // TODO(b/219008720): Remove those calls to Dependency.get.
+ DismissReceiver dismissReceiver = new DismissReceiver(dialog,
+ Dependency.get(BroadcastDispatcher.class),
+ Dependency.get(DialogLaunchAnimator.class));
dialog.setOnDismissListener(d -> {
dismissReceiver.unregister();
if (dismissAction != null) dismissAction.run();
@@ -408,11 +419,11 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
private final BroadcastDispatcher mBroadcastDispatcher;
private final DialogLaunchAnimator mDialogLaunchAnimator;
- DismissReceiver(Dialog dialog) {
+ DismissReceiver(Dialog dialog, BroadcastDispatcher broadcastDispatcher,
+ DialogLaunchAnimator dialogLaunchAnimator) {
mDialog = dialog;
- // TODO(b/219008720): Remove those calls to Dependency.get.
- mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
- mDialogLaunchAnimator = Dependency.get(DialogLaunchAnimator.class);
+ mBroadcastDispatcher = broadcastDispatcher;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
}
void register() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt
index 1740bcbbefc8..16f28e7d1a21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt
@@ -2,10 +2,13 @@ package com.android.systemui.statusbar.phone.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.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -21,7 +24,8 @@ constructor(
configurationController: ConfigurationController,
dumpManager: DumpManager,
private val scrimController: ScrimController,
- @Main private val resources: Resources
+ @Main private val resources: Resources,
+ private val statusBarStateController: SysuiStatusBarStateController,
) {
private var inSplitShade = false
@@ -55,19 +59,23 @@ constructor(
}
private fun calculateScrimExpansionFraction(expansionEvent: PanelExpansionChangeEvent): Float {
- return if (inSplitShade) {
- expansionEvent.dragDownPxAmount / splitShadeScrimTransitionDistance
+ return if (inSplitShade && isScreenUnlocked()) {
+ constrain(expansionEvent.dragDownPxAmount / splitShadeScrimTransitionDistance, 0f, 1f)
} else {
expansionEvent.fraction
}
}
+ 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:
lastExpansionFraction: $lastExpansionFraction
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index a89c128dd584..753e94015751 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -119,6 +119,17 @@ public interface BatteryController extends DemoMode, Dumpable,
}
/**
+ * Returns {@code true} if the charging source is
+ * {@link android.os.BatteryManager#BATTERY_PLUGGED_DOCK}.
+ *
+ * <P>Note that charging from dock is not considered as wireless charging. In other words,
+ * {@link BatteryController#isWirelessCharging()} and this are mutually exclusive.
+ */
+ default boolean isChargingSourceDock() {
+ return false;
+ }
+
+ /**
* A listener that will be notified whenever a change in battery level or power save mode has
* occurred.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 917a5e0b9374..33ddf7eed006 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -76,7 +76,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
protected int mLevel;
protected boolean mPluggedIn;
- private boolean mPluggedInWireless;
+ private int mPluggedChargingSource;
protected boolean mCharging;
private boolean mStateUnknown = false;
private boolean mCharged;
@@ -195,10 +195,8 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
mLevel = (int)(100f
* intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
/ intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
- mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
- mPluggedInWireless = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)
- == BatteryManager.BATTERY_PLUGGED_WIRELESS;
-
+ mPluggedChargingSource = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ mPluggedIn = mPluggedChargingSource != 0;
final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
BatteryManager.BATTERY_STATUS_UNKNOWN);
mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
@@ -284,7 +282,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
@Override
public boolean isPluggedInWireless() {
- return mPluggedInWireless;
+ return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
}
@Override
@@ -441,4 +439,9 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
registerReceiver();
updatePowerSave();
}
+
+ @Override
+ public boolean isChargingSourceDock() {
+ return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_DOCK;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index 4cf1d2b3f91d..9946b4b9ecaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -17,15 +17,11 @@
package com.android.systemui.statusbar.policy;
import android.annotation.WorkerThread;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.text.TextUtils;
@@ -33,12 +29,19 @@ import android.util.Log;
import androidx.annotation.NonNull;
+import com.android.internal.annotations.GuardedBy;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
@@ -59,65 +62,88 @@ public class FlashlightControllerImpl implements FlashlightController {
"com.android.settings.flashlight.action.FLASHLIGHT_CHANGED";
private final CameraManager mCameraManager;
- private final Context mContext;
- /** Call {@link #ensureHandler()} before using */
- private Handler mHandler;
+ private final Executor mExecutor;
+ private final SecureSettings mSecureSettings;
+ private final DumpManager mDumpManager;
+ private final BroadcastSender mBroadcastSender;
- /** Lock on mListeners when accessing */
+ private final boolean mHasFlashlight;
+
+ @GuardedBy("mListeners")
private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
- /** Lock on {@code this} when accessing */
+ @GuardedBy("this")
private boolean mFlashlightEnabled;
-
- private String mCameraId;
+ @GuardedBy("this")
private boolean mTorchAvailable;
+ private final AtomicReference<String> mCameraId;
+ private final AtomicBoolean mInitted = new AtomicBoolean(false);
+
@Inject
- public FlashlightControllerImpl(Context context, DumpManager dumpManager) {
- mContext = context;
- mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+ public FlashlightControllerImpl(
+ DumpManager dumpManager,
+ CameraManager cameraManager,
+ @Background Executor bgExecutor,
+ SecureSettings secureSettings,
+ BroadcastSender broadcastSender,
+ PackageManager packageManager
+ ) {
+ mCameraManager = cameraManager;
+ mExecutor = bgExecutor;
+ mCameraId = new AtomicReference<>(null);
+ mSecureSettings = secureSettings;
+ mDumpManager = dumpManager;
+ mBroadcastSender = broadcastSender;
+
+ mHasFlashlight = packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
+ init();
+ }
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
- tryInitCamera();
+ private void init() {
+ if (!mInitted.getAndSet(true)) {
+ mDumpManager.registerDumpable(getClass().getSimpleName(), this);
+ mExecutor.execute(this::tryInitCamera);
+ }
}
+ @WorkerThread
private void tryInitCamera() {
+ if (!mHasFlashlight || mCameraId.get() != null) return;
try {
- mCameraId = getCameraId();
+ mCameraId.set(getCameraId());
} catch (Throwable e) {
Log.e(TAG, "Couldn't initialize.", e);
return;
}
- if (mCameraId != null) {
- ensureHandler();
- mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
+ if (mCameraId.get() != null) {
+ mCameraManager.registerTorchCallback(mExecutor, mTorchCallback);
}
}
public void setFlashlight(boolean enabled) {
- boolean pendingError = false;
- synchronized (this) {
- if (mCameraId == null) return;
- if (mFlashlightEnabled != enabled) {
- mFlashlightEnabled = enabled;
- try {
- mCameraManager.setTorchMode(mCameraId, enabled);
- } catch (CameraAccessException e) {
- Log.e(TAG, "Couldn't set torch mode", e);
- mFlashlightEnabled = false;
- pendingError = true;
+ if (!mHasFlashlight) return;
+ if (mCameraId.get() == null) {
+ mExecutor.execute(this::tryInitCamera);
+ }
+ mExecutor.execute(() -> {
+ if (mCameraId.get() == null) return;
+ synchronized (this) {
+ if (mFlashlightEnabled != enabled) {
+ try {
+ mCameraManager.setTorchMode(mCameraId.get(), enabled);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Couldn't set torch mode", e);
+ dispatchError();
+ }
}
}
- }
- dispatchModeChanged(mFlashlightEnabled);
- if (pendingError) {
- dispatchError();
- }
+ });
}
public boolean hasFlashlight() {
- return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
+ return mHasFlashlight;
}
public synchronized boolean isEnabled() {
@@ -131,13 +157,13 @@ public class FlashlightControllerImpl implements FlashlightController {
@Override
public void addCallback(@NonNull FlashlightListener l) {
synchronized (mListeners) {
- if (mCameraId == null) {
- tryInitCamera();
+ if (mCameraId.get() == null) {
+ mExecutor.execute(this::tryInitCamera);
}
cleanUpListenersLocked(l);
mListeners.add(new WeakReference<>(l));
- l.onFlashlightAvailabilityChanged(mTorchAvailable);
- l.onFlashlightChanged(mFlashlightEnabled);
+ l.onFlashlightAvailabilityChanged(isAvailable());
+ l.onFlashlightChanged(isEnabled());
}
}
@@ -148,14 +174,7 @@ public class FlashlightControllerImpl implements FlashlightController {
}
}
- private synchronized void ensureHandler() {
- if (mHandler == null) {
- HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
- mHandler = new Handler(thread.getLooper());
- }
- }
-
+ @WorkerThread
private String getCameraId() throws CameraAccessException {
String[] ids = mCameraManager.getCameraIdList();
for (String id : ids) {
@@ -221,10 +240,9 @@ public class FlashlightControllerImpl implements FlashlightController {
@Override
@WorkerThread
public void onTorchModeUnavailable(String cameraId) {
- if (TextUtils.equals(cameraId, mCameraId)) {
+ if (TextUtils.equals(cameraId, mCameraId.get())) {
setCameraAvailable(false);
- Settings.Secure.putInt(
- mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 0);
+ mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 0);
}
}
@@ -232,14 +250,12 @@ public class FlashlightControllerImpl implements FlashlightController {
@Override
@WorkerThread
public void onTorchModeChanged(String cameraId, boolean enabled) {
- if (TextUtils.equals(cameraId, mCameraId)) {
+ if (TextUtils.equals(cameraId, mCameraId.get())) {
setCameraAvailable(true);
setTorchMode(enabled);
- Settings.Secure.putInt(
- mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
- Settings.Secure.putInt(
- mContext.getContentResolver(), Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
- mContext.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
+ mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
+ mSecureSettings.putInt(Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
+ mBroadcastSender.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
}
}
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 bce5a159f79c..d3837d7347dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -142,7 +142,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
protected void setEntryPinned(
@NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) {
- mLogger.logSetEntryPinned(headsUpEntry.mEntry.getKey(), isPinned);
+ mLogger.logSetEntryPinned(headsUpEntry.mEntry, isPinned);
NotificationEntry entry = headsUpEntry.mEntry;
if (entry.isRowPinned() != isPinned) {
entry.setRowPinned(isPinned);
@@ -183,7 +183,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
entry.setHeadsUp(false);
setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
- mLogger.logNotificationActuallyRemoved(entry.getKey());
+ mLogger.logNotificationActuallyRemoved(entry);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 6a74ba957b4b..d7c81af53d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -20,6 +20,8 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.LogLevel.VERBOSE
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
/** Logger for [HeadsUpManager]. */
@@ -56,9 +58,9 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
- fun logShowNotification(key: String) {
+ fun logShowNotification(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"show notification $str1"
})
@@ -66,16 +68,16 @@ class HeadsUpManagerLogger @Inject constructor(
fun logRemoveNotification(key: String, releaseImmediately: Boolean) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = logKey(key)
bool1 = releaseImmediately
}, {
"remove notification $str1 releaseImmediately: $bool1"
})
}
- fun logNotificationActuallyRemoved(key: String) {
+ fun logNotificationActuallyRemoved(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"notification removed $str1 "
})
@@ -83,7 +85,7 @@ class HeadsUpManagerLogger @Inject constructor(
fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = logKey(key)
bool1 = alert
bool2 = hasEntry
}, {
@@ -91,12 +93,12 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
- fun logUpdateEntry(key: String, updatePostTime: Boolean) {
+ fun logUpdateEntry(entry: NotificationEntry, updatePostTime: Boolean) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
bool1 = updatePostTime
}, {
- "update entry $key updatePostTime: $bool1"
+ "update entry $str1 updatePostTime: $bool1"
})
}
@@ -108,9 +110,9 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
- fun logSetEntryPinned(key: String, isPinned: Boolean) {
+ fun logSetEntryPinned(entry: NotificationEntry, isPinned: Boolean) {
buffer.log(TAG, VERBOSE, {
- str1 = key
+ str1 = entry.logKey
bool1 = isPinned
}, {
"set entry pinned $str1 pinned: $bool1"
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index 4e9030f8045c..dac8a0bff28a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -92,7 +92,15 @@ public class Monitor {
}
private void updateConditionMetState(Condition condition) {
- mConditions.get(condition).stream().forEach(token -> mSubscriptions.get(token).update());
+ final ArraySet<Subscription.Token> subscriptions = mConditions.get(condition);
+
+ // It's possible the condition was removed between the time the callback occurred and
+ // update was executed on the main thread.
+ if (subscriptions == null) {
+ return;
+ }
+
+ subscriptions.stream().forEach(token -> mSubscriptions.get(token).update());
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 7de4586e1901..bc351427310d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -27,7 +27,6 @@ import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -68,8 +67,6 @@ import org.mockito.junit.MockitoRule;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper()
public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
- private static final int VIEW_WIDTH = 1600;
-
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
@@ -141,7 +138,6 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
when(mResources.getConfiguration()).thenReturn(mConfiguration);
when(mView.getContext()).thenReturn(mContext);
when(mView.getResources()).thenReturn(mResources);
- when(mView.getWidth()).thenReturn(VIEW_WIDTH);
when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
.thenReturn(mAdminSecondaryLockScreenController);
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
@@ -212,49 +208,26 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
mUserSwitcherController);
}
- private void touchDownLeftSide() {
+ private void touchDown() {
mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
MotionEvent.obtain(
/* downTime= */0,
/* eventTime= */0,
MotionEvent.ACTION_DOWN,
- /* x= */VIEW_WIDTH / 3f,
- /* y= */0,
- /* metaState= */0));
- }
-
- private void touchDownRightSide() {
- mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
- MotionEvent.obtain(
- /* downTime= */0,
- /* eventTime= */0,
- MotionEvent.ACTION_DOWN,
- /* x= */(VIEW_WIDTH / 3f) * 2,
+ /* x= */0,
/* y= */0,
/* metaState= */0));
}
@Test
- public void onInterceptTap_inhibitsFalsingInOneHandedMode() {
- when(mView.getMode()).thenReturn(MODE_ONE_HANDED);
- when(mView.isOneHandedModeLeftAligned()).thenReturn(true);
-
- touchDownLeftSide();
- verify(mFalsingCollector, never()).avoidGesture();
-
- // Now on the right.
- touchDownRightSide();
- verify(mFalsingCollector).avoidGesture();
-
- // Move and re-test
- reset(mFalsingCollector);
- when(mView.isOneHandedModeLeftAligned()).thenReturn(false);
+ public void onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
- // On the right...
- touchDownRightSide();
+ when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false);
+ touchDown();
verify(mFalsingCollector, never()).avoidGesture();
- touchDownLeftSide();
+ when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true);
+ touchDown();
verify(mFalsingCollector).avoidGesture();
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index d49f4d8172dc..f2ac0c7a7736 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -16,6 +16,11 @@
package com.android.keyguard;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE;
+import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
+import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
@@ -25,7 +30,9 @@ import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
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.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -34,14 +41,13 @@ import static org.mockito.Mockito.when;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Insets;
-import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Gravity;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
-import android.view.WindowInsetsController;
import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
@@ -70,6 +76,9 @@ import java.util.ArrayList;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper()
public class KeyguardSecurityContainerTest extends SysuiTestCase {
+
+ private static final int VIEW_WIDTH = 1600;
+
private int mScreenWidth;
private int mFakeMeasureSpec;
@@ -77,8 +86,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
public MockitoRule mRule = MockitoJUnit.rule();
@Mock
- private WindowInsetsController mWindowInsetsController;
- @Mock
private KeyguardSecurityViewFlipper mSecurityViewFlipper;
@Mock
private GlobalSettings mGlobalSettings;
@@ -102,7 +109,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
mSecurityViewFlipperLayoutParams = new FrameLayout.LayoutParams(
MATCH_PARENT, MATCH_PARENT);
- when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext());
mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
@@ -212,14 +218,12 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
mKeyguardSecurityContainer.updatePositionByTouchX(
mKeyguardSecurityContainer.getWidth() - 1f);
- verify(mGlobalSettings).putInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
- Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
- verify(mSecurityViewFlipper).setTranslationX(
+ verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_RIGHT);
+ assertSecurityTranslationX(
mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
mKeyguardSecurityContainer.updatePositionByTouchX(1f);
- verify(mGlobalSettings).putInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
- Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT);
+ verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_LEFT);
verify(mSecurityViewFlipper).setTranslationX(0.0f);
}
@@ -237,49 +241,43 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
}
@Test
- public void testUserSwitcherModeViewGravityLandscape() {
+ public void testUserSwitcherModeViewPositionLandscape() {
// GIVEN one user has been setup and in landscape
when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1));
- Configuration config = new Configuration();
- config.orientation = Configuration.ORIENTATION_LANDSCAPE;
- when(getContext().getResources().getConfiguration()).thenReturn(config);
+ Configuration landscapeConfig = configuration(ORIENTATION_LANDSCAPE);
+ when(getContext().getResources().getConfiguration()).thenReturn(landscapeConfig);
// WHEN UserSwitcherViewMode is initialized and config has changed
setupUserSwitcher();
- reset(mSecurityViewFlipper);
- when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
- mKeyguardSecurityContainer.onConfigurationChanged(config);
+ mKeyguardSecurityContainer.onConfigurationChanged(landscapeConfig);
// THEN views are oriented side by side
- verify(mSecurityViewFlipper).setLayoutParams(mLayoutCaptor.capture());
- assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(Gravity.RIGHT | Gravity.BOTTOM);
- ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
- R.id.keyguard_bouncer_user_switcher);
- assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
- .isEqualTo(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+ assertSecurityGravity(Gravity.LEFT | Gravity.BOTTOM);
+ assertUserSwitcherGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+ assertSecurityTranslationX(
+ mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
+ assertUserSwitcherTranslationX(0f);
+
}
@Test
public void testUserSwitcherModeViewGravityPortrait() {
// GIVEN one user has been setup and in landscape
when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1));
- Configuration config = new Configuration();
- config.orientation = Configuration.ORIENTATION_PORTRAIT;
- when(getContext().getResources().getConfiguration()).thenReturn(config);
+ Configuration portraitConfig = configuration(ORIENTATION_PORTRAIT);
+ when(getContext().getResources().getConfiguration()).thenReturn(portraitConfig);
// WHEN UserSwitcherViewMode is initialized and config has changed
setupUserSwitcher();
reset(mSecurityViewFlipper);
when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
- mKeyguardSecurityContainer.onConfigurationChanged(config);
+ mKeyguardSecurityContainer.onConfigurationChanged(portraitConfig);
// THEN views are both centered horizontally
- verify(mSecurityViewFlipper).setLayoutParams(mLayoutCaptor.capture());
- assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(Gravity.CENTER_HORIZONTAL);
- ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
- R.id.keyguard_bouncer_user_switcher);
- assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
- .isEqualTo(Gravity.CENTER_HORIZONTAL);
+ assertSecurityGravity(Gravity.CENTER_HORIZONTAL);
+ assertUserSwitcherGravity(Gravity.CENTER_HORIZONTAL);
+ assertSecurityTranslationX(0);
+ assertUserSwitcherTranslationX(0);
}
@Test
@@ -310,9 +308,102 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
assertThat(anchor.isClickable()).isTrue();
}
+ @Test
+ public void testTouchesAreRecognizedAsBeingOnTheOtherSideOfSecurity() {
+ setupUserSwitcher();
+ setViewWidth(VIEW_WIDTH);
+
+ // security is on the right side by default
+ assertThat(mKeyguardSecurityContainer.isTouchOnTheOtherSideOfSecurity(
+ touchEventLeftSide())).isTrue();
+ assertThat(mKeyguardSecurityContainer.isTouchOnTheOtherSideOfSecurity(
+ touchEventRightSide())).isFalse();
+
+ // move security to the left side
+ when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_LEFT);
+ mKeyguardSecurityContainer.onConfigurationChanged(new Configuration());
+
+ assertThat(mKeyguardSecurityContainer.isTouchOnTheOtherSideOfSecurity(
+ touchEventLeftSide())).isFalse();
+ assertThat(mKeyguardSecurityContainer.isTouchOnTheOtherSideOfSecurity(
+ touchEventRightSide())).isTrue();
+ }
+
+ @Test
+ public void testSecuritySwitchesSidesInLandscapeUserSwitcherMode() {
+ when(getContext().getResources().getConfiguration())
+ .thenReturn(configuration(ORIENTATION_LANDSCAPE));
+ setupUserSwitcher();
+
+ // switch sides
+ when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_LEFT);
+ mKeyguardSecurityContainer.onConfigurationChanged(new Configuration());
+
+ assertSecurityTranslationX(0);
+ assertUserSwitcherTranslationX(
+ mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
+ }
+
+ private Configuration configuration(@Configuration.Orientation int orientation) {
+ Configuration config = new Configuration();
+ config.orientation = orientation;
+ return config;
+ }
+
+ private void assertSecurityTranslationX(float translation) {
+ verify(mSecurityViewFlipper).setTranslationX(translation);
+ }
+
+ private void assertUserSwitcherTranslationX(float translation) {
+ ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
+ R.id.keyguard_bouncer_user_switcher);
+ assertThat(userSwitcher.getTranslationX()).isEqualTo(translation);
+ }
+
+ private void assertUserSwitcherGravity(@Gravity.GravityFlags int gravity) {
+ ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
+ R.id.keyguard_bouncer_user_switcher);
+ assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
+ .isEqualTo(gravity);
+ }
+
+ private void assertSecurityGravity(@Gravity.GravityFlags int gravity) {
+ verify(mSecurityViewFlipper, atLeastOnce()).setLayoutParams(mLayoutCaptor.capture());
+ assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(gravity);
+ }
+
+ private void setViewWidth(int width) {
+ mKeyguardSecurityContainer.setRight(width);
+ mKeyguardSecurityContainer.setLeft(0);
+ }
+
+ private MotionEvent touchEventLeftSide() {
+ return MotionEvent.obtain(
+ /* downTime= */0,
+ /* eventTime= */0,
+ MotionEvent.ACTION_DOWN,
+ /* x= */VIEW_WIDTH / 3f,
+ /* y= */0,
+ /* metaState= */0);
+ }
+
+ private MotionEvent touchEventRightSide() {
+ return MotionEvent.obtain(
+ /* downTime= */0,
+ /* eventTime= */0,
+ MotionEvent.ACTION_DOWN,
+ /* x= */(VIEW_WIDTH / 3f) * 2,
+ /* y= */0,
+ /* metaState= */0);
+ }
+
private void setupUserSwitcher() {
+ when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT);
mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER,
mGlobalSettings, mFalsingManager, mUserSwitcherController);
+ // reset mSecurityViewFlipper so setup doesn't influence test verifications
+ reset(mSecurityViewFlipper);
+ when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
}
private ArrayList<UserRecord> buildUserRecords(int count) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index a35efa995ddd..90609fa2772f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -210,7 +210,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
BOUNDS_POSITION_TOP,
mAuthController,
mStatusBarStateController,
- mKeyguardUpdateMonitor));
+ mKeyguardUpdateMonitor,
+ mExecutor));
mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings,
mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index d5df9fe0c2e8..c48cbb19b40a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -159,7 +159,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
@Test
fun doesNotStartIfAnimationIsCancelled() {
val runner = activityLaunchAnimator.createRunner(controller)
- runner.onAnimationCancelled()
+ runner.onAnimationCancelled(false /* isKeyguardOccluded */)
runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
waitForIdleSync()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 0fdd9054e4bc..b61bda8edd10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -468,6 +469,40 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
verify(mView).setUnpausedAlpha(255);
}
+ @Test
+ public void testUpdatePanelExpansion_pauseAuth() {
+ // GIVEN view is attached + on the keyguard
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureStatusBarExpansionListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+ reset(mView);
+
+ // WHEN panelViewExpansion changes to hide
+ when(mView.getUnpausedAlpha()).thenReturn(0);
+ updateStatusBarExpansion(0f, false);
+
+ // THEN pause auth is updated to PAUSE
+ verify(mView, atLeastOnce()).setPauseAuth(true);
+ }
+
+ @Test
+ public void testUpdatePanelExpansion_unpauseAuth() {
+ // GIVEN view is attached + on the keyguard + panel expansion is 0f
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureStatusBarExpansionListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+ reset(mView);
+
+ // WHEN panelViewExpansion changes to expanded
+ when(mView.getUnpausedAlpha()).thenReturn(255);
+ updateStatusBarExpansion(1f, true);
+
+ // THEN pause auth is updated to NOT pause
+ verify(mView, atLeastOnce()).setPauseAuth(false);
+ }
+
private void sendStatusBarStateChanged(int statusBarState) {
mStatusBarStateListener.onStateChanged(statusBarState);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index fb64c7b58aac..2adf2857a385 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -207,4 +207,15 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
assertThat(complications.contains(weatherComplication)).isFalse();
}
}
+
+ @Test
+ public void testComplicationWithNoTypeNotFiltered() {
+ final Complication complication = Mockito.mock(Complication.class);
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor);
+ stateController.addComplication(complication);
+ mExecutor.runAllReady();
+ assertThat(stateController.getComplications(true).contains(complication))
+ .isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
index dc1ae0e93757..964e6d79f0bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
@@ -72,19 +72,67 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
}
/**
- * Ensures {@link SmartSpaceComplication} is only registered when it is available.
+ * Ensures {@link SmartSpaceComplication} isn't registered right away on start.
*/
@Test
- public void testAvailability() {
+ public void testRegistrantStart_doesNotAddComplication() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
+ registrant.start();
+ verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication));
+ }
- final SmartSpaceComplication.Registrant registrant = new SmartSpaceComplication.Registrant(
+ private SmartSpaceComplication.Registrant getRegistrant() {
+ return new SmartSpaceComplication.Registrant(
mContext,
mDreamOverlayStateController,
mComplication,
mSmartspaceController);
+ }
+
+ @Test
+ public void testOverlayActive_addsTargetListener() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
registrant.start();
- verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication));
+ final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
+
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ dreamCallbackCaptor.getValue().onStateChanged();
+
+ // Test
+ final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
+ ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
+ verify(mSmartspaceController).addListener(listenerCaptor.capture());
+ }
+
+ @Test
+ public void testOverlayActive_targetsNonEmpty_addsComplication() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
+ registrant.start();
+
+ final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
+
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ dreamCallbackCaptor.getValue().onStateChanged();
+
+ final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
+ ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
+ verify(mSmartspaceController).addListener(listenerCaptor.capture());
+
+ // Test
+ final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
+ listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+ }
+
+ @Test
+ public void testOverlayActive_targetsEmpty_removesComplication() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
+ registrant.start();
final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
@@ -100,10 +148,41 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+
+ // Test
+ listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList());
+ verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
+ }
+
+ @Test
+ public void testOverlayInActive_removesTargetListener_removesComplication() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
+ registrant.start();
+
+ final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
+
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ dreamCallbackCaptor.getValue().onStateChanged();
+
+ final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
+ ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
+ verify(mSmartspaceController).addListener(listenerCaptor.capture());
+
+ final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
+ listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+
+ // Test
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(false);
+ dreamCallbackCaptor.getValue().onStateChanged();
+ verify(mSmartspaceController).removeListener(listenerCaptor.getValue());
+ verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
}
@Test
- public void testGetViewReusesSameView() {
+ public void testGetView_reusesSameView() {
final SmartSpaceComplication complication = new SmartSpaceComplication(getContext(),
mSmartspaceController);
final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index f00fbe6ac4d7..141a213a5b6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -480,4 +481,13 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
// hide dialog again
mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */);
}
+
+ @Test
+ public void testBugreportAction_whenDebugMode_shouldOfferBugreportButtonBeforeProvisioning() {
+ doReturn(1).when(mGlobalSettings).getInt(anyString(), anyInt());
+
+ GlobalActionsDialogLite.BugReportAction bugReportAction =
+ mGlobalActionsDialogLite.makeBugReportActionForTesting();
+ assertThat(bugReportAction.showBeforeProvisioning()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 11326e76b25e..59475cf0cb90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.media.dialog;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -51,6 +52,8 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
private static final String TEST_DEVICE_ID_1 = "test_device_id_1";
private static final String TEST_DEVICE_ID_2 = "test_device_id_2";
private static final String TEST_SESSION_NAME = "test_session_name";
+ private static final int TEST_MAX_VOLUME = 20;
+ private static final int TEST_CURRENT_VOLUME = 10;
// Mock
private MediaOutputController mMediaOutputController = mock(MediaOutputController.class);
@@ -64,12 +67,14 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
private MediaOutputAdapter mMediaOutputAdapter;
private MediaOutputAdapter.MediaDeviceViewHolder mViewHolder;
private List<MediaDevice> mMediaDevices = new ArrayList<>();
+ MediaOutputSeekbar mSpyMediaOutputSeekbar;
@Before
public void setUp() {
mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController, mMediaOutputDialog);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
+ mSpyMediaOutputSeekbar = spy(mViewHolder.mSeekBar);
when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
@@ -169,6 +174,16 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
}
@Test
+ public void onBindViewHolder_initSeekbar_setsVolume() {
+ when(mMediaDevice1.getMaxVolume()).thenReturn(TEST_MAX_VOLUME);
+ when(mMediaDevice1.getCurrentVolume()).thenReturn(TEST_CURRENT_VOLUME);
+ mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mSeekBar.getVolume()).isEqualTo(TEST_CURRENT_VOLUME);
+ }
+
+ @Test
public void onBindViewHolder_bindNonActiveConnectedDevice_verifyView() {
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index e3b5059131fa..9eaa20c2afed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -32,6 +32,7 @@ import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Bundle;
+import android.os.PowerExemptionManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -86,6 +87,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
NearbyMediaDevicesManager.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private final AudioManager mAudioManager = mock(AudioManager.class);
+ private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
@@ -110,7 +112,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotificationEntryManager, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index feed3347f3e2..2bf5f0fcbfcb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -19,6 +19,9 @@ package com.android.systemui.media.dialog;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -34,10 +37,12 @@ import android.graphics.drawable.Icon;
import android.media.AudioManager;
import android.media.MediaDescription;
import android.media.MediaMetadata;
+import android.media.MediaRoute2Info;
import android.media.NearbyDevice;
import android.media.RoutingSessionInfo;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
+import android.os.PowerExemptionManager;
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
@@ -97,6 +102,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
private RoutingSessionInfo mRemoteSessionInfo = mock(RoutingSessionInfo.class);
private ActivityStarter mStarter = mock(ActivityStarter.class);
private AudioManager mAudioManager = mock(AudioManager.class);
+ private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
@@ -125,7 +131,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -177,7 +183,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mSpyContext, null,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
mMediaOutputController.start(mCb);
@@ -206,7 +212,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mSpyContext, null,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
mMediaOutputController.start(mCb);
@@ -511,7 +517,7 @@ public class MediaOutputControllerTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mSpyContext, null,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
}
@@ -591,4 +597,20 @@ public class MediaOutputControllerTest extends SysuiTestCase {
assertThat(mMediaOutputController.isVolumeControlEnabled(mMediaDevice1)).isTrue();
}
+
+ @Test
+ public void setTemporaryAllowListExceptionIfNeeded_fromRemoteToBluetooth_addsAllowList() {
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
+ when(mMediaDevice1.getDeviceType()).thenReturn(
+ MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
+ when(mMediaDevice1.getFeatures()).thenReturn(
+ ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_AUDIO_PLAYBACK));
+ when(mMediaDevice2.getDeviceType()).thenReturn(
+ MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
+
+ mMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mMediaDevice2);
+
+ verify(mPowerExemptionManager).addToTemporaryAllowList(anyString(), anyInt(), anyString(),
+ anyLong());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index b16c928adcd4..c45db05bacee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -29,6 +29,7 @@ import android.media.MediaRoute2Info;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
+import android.os.PowerExemptionManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -84,6 +85,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
NearbyMediaDevicesManager.class);
private final AudioManager mAudioManager = mock(AudioManager.class);
+ private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputDialog mMediaOutputDialog;
@@ -103,7 +105,7 @@ public class MediaOutputDialogTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotificationEntryManager, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
mMediaOutputController, mUiEventLogger);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
index 379bb4fd6336..4534ae6448ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.when;
import android.media.AudioManager;
import android.media.session.MediaSessionManager;
+import android.os.PowerExemptionManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
@@ -70,6 +71,7 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase {
private NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
NearbyMediaDevicesManager.class);
private final AudioManager mAudioManager = mock(AudioManager.class);
+ private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private MediaOutputGroupDialog mMediaOutputGroupDialog;
private MediaOutputController mMediaOutputController;
@@ -80,7 +82,7 @@ public class MediaOutputGroupDialogTest extends SysuiTestCase {
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotificationEntryManager, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false, mBroadcastSender,
mMediaOutputController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index b9a69bb8641a..2eb478303cb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -25,6 +25,7 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -65,6 +66,8 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
@Mock
private lateinit var logger: MediaTttLogger
@Mock
+ private lateinit var accessibilityManager: AccessibilityManager
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var viewUtil: ViewUtil
@@ -88,11 +91,21 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
)).thenReturn(applicationInfo)
context.setMockPackageManager(packageManager)
+ whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any()))
+ .thenReturn(TIMEOUT_MS.toInt())
+
fakeClock = FakeSystemClock()
fakeExecutor = FakeExecutor(fakeClock)
controllerCommon = TestControllerCommon(
- context, logger, windowManager, viewUtil, fakeExecutor, tapGestureDetector, powerManager
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ fakeExecutor,
+ accessibilityManager,
+ tapGestureDetector,
+ powerManager
)
}
@@ -344,6 +357,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
windowManager: WindowManager,
viewUtil: ViewUtil,
@Main mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
tapGestureDetector: TapGestureDetector,
powerManager: PowerManager
) : MediaTttChipControllerCommon<ChipInfo>(
@@ -352,23 +366,22 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
windowManager,
viewUtil,
mainExecutor,
+ accessibilityManager,
tapGestureDetector,
powerManager,
R.layout.media_ttt_chip
) {
- override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {
-
- }
-
- override fun getIconSize(isAppIcon: Boolean): Int? = ICON_SIZE
+ override val windowLayoutParams = commonWindowLayoutParams
+ override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {}
+ override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE
}
inner class ChipInfo : ChipInfoCommon {
- override fun getTimeoutMs() = TIMEOUT_MS
+ override fun getTimeoutMs() = 1L
}
}
private const val PACKAGE_NAME = "com.android.systemui"
private const val APP_NAME = "Fake App Name"
private const val TIMEOUT_MS = 10000L
-private const val ICON_SIZE = 47 \ No newline at end of file
+private const val ICON_SIZE = 47
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 9edc4f4c71c3..bbc564193080 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -28,6 +28,7 @@ import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
@@ -65,6 +66,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
@Mock
private lateinit var logger: MediaTttLogger
@Mock
+ private lateinit var accessibilityManager: AccessibilityManager
+ @Mock
private lateinit var powerManager: PowerManager
@Mock
private lateinit var windowManager: WindowManager
@@ -99,6 +102,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
windowManager,
viewUtil,
FakeExecutor(FakeSystemClock()),
+ accessibilityManager,
TapGestureDetector(context),
powerManager,
Handler.getMain(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index a8c72ddfd5d7..7ca0cd34ab26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -27,6 +27,7 @@ import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import android.widget.TextView
import androidx.test.filters.SmallTest
@@ -67,6 +68,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
@Mock
private lateinit var logger: MediaTttLogger
@Mock
+ private lateinit var accessibilityManager: AccessibilityManager
+ @Mock
private lateinit var powerManager: PowerManager
@Mock
private lateinit var windowManager: WindowManager
@@ -95,9 +98,12 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
fakeClock = FakeSystemClock()
fakeExecutor = FakeExecutor(fakeClock)
+
uiEventLoggerFake = UiEventLoggerFake()
senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
+ whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
+
controllerSender = MediaTttChipControllerSender(
commandQueue,
context,
@@ -105,6 +111,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
windowManager,
viewUtil,
fakeExecutor,
+ accessibilityManager,
TapGestureDetector(context),
powerManager,
senderUiEventLogger
@@ -592,7 +599,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
fakeClock.advanceTime(1000L)
controllerSender.removeChip("fakeRemovalReason")
- fakeClock.advanceTime(state.state.timeout + 1)
+ fakeClock.advanceTime(TIMEOUT + 1L)
verify(windowManager).removeView(any())
}
@@ -615,7 +622,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
fakeClock.advanceTime(1000L)
controllerSender.removeChip("fakeRemovalReason")
- fakeClock.advanceTime(state.state.timeout + 1)
+ fakeClock.advanceTime(TIMEOUT + 1L)
verify(windowManager).removeView(any())
}
@@ -674,6 +681,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
private const val APP_NAME = "Fake app name"
private const val OTHER_DEVICE_NAME = "My Tablet"
private const val PACKAGE_NAME = "com.android.systemui"
+private const val TIMEOUT = 10000
private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
.addFeature("feature")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
index 489c8c86028e..bf237abba8fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt
@@ -57,6 +57,7 @@ class QSContainerImplTest : SysuiTestCase() {
@Test
fun testContainerBottomPadding() {
+ val originalPadding = qsPanelContainer.paddingBottom
qsContainer.updateResources(
qsPanelController,
quickStatusBarHeaderController
@@ -66,7 +67,7 @@ class QSContainerImplTest : SysuiTestCase() {
anyInt(),
anyInt(),
anyInt(),
- eq(mContext.resources.getDimensionPixelSize(R.dimen.footer_actions_height))
+ eq(originalPadding)
)
}
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
index 60cfd7249919..b98be75a51c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt
@@ -150,6 +150,14 @@ class QSPanelTest : SysuiTestCase() {
assertThat(footer.isVisibleToUser).isTrue()
}
+ @Test
+ fun testBottomPadding() {
+ val padding = 10
+ context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_bottom, padding)
+ qsPanel.updatePadding()
+ assertThat(qsPanel.paddingBottom).isEqualTo(padding)
+ }
+
private infix fun View.isLeftOf(other: View): Boolean {
val rect = Rect()
getBoundsOnScreen(rect)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index 2f0f0a0a1b8f..37f96c8d7023 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -36,17 +36,17 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Spy
import org.mockito.Mockito
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
-import java.util.concurrent.Executor
+import org.mockito.Spy
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -87,6 +87,34 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
private lateinit var controller: DreamSmartspaceController
+ /**
+ * A class which implements SmartspaceView and extends View. This is mocked to provide the right
+ * object inheritance and interface implementation used in DreamSmartspaceController
+ */
+ private class TestView(context: Context?) : View(context), SmartspaceView {
+ override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {}
+
+ override fun setPrimaryTextColor(color: Int) {}
+
+ override fun setIsDreaming(isDreaming: Boolean) {}
+
+ override fun setDozeAmount(amount: Float) {}
+
+ override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {}
+
+ override fun setFalsingManager(falsingManager: FalsingManager?) {}
+
+ override fun setDnd(image: Drawable?, description: String?) {}
+
+ override fun setNextAlarm(image: Drawable?, description: String?) {}
+
+ override fun setMediaTarget(target: SmartspaceTarget?) {}
+
+ override fun getSelectedPage(): Int { return 0; }
+
+ override fun getCurrentCardTopPadding(): Int { return 0; }
+ }
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -130,34 +158,6 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
}
/**
- * A class which implements SmartspaceView and extends View. This is mocked to provide the right
- * object inheritance and interface implementation used in DreamSmartspaceController
- */
- private class TestView(context: Context?) : View(context), SmartspaceView {
- override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {}
-
- override fun setPrimaryTextColor(color: Int) {}
-
- override fun setIsDreaming(isDreaming: Boolean) {}
-
- override fun setDozeAmount(amount: Float) {}
-
- override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {}
-
- override fun setFalsingManager(falsingManager: FalsingManager?) {}
-
- override fun setDnd(image: Drawable?, description: String?) {}
-
- override fun setNextAlarm(image: Drawable?, description: String?) {}
-
- override fun setMediaTarget(target: SmartspaceTarget?) {}
-
- override fun getSelectedPage(): Int { return 0; }
-
- override fun getCurrentCardTopPadding(): Int { return 0; }
- }
-
- /**
* Ensures session begins when a view is attached.
*/
@Test
@@ -180,16 +180,4 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
verify(session).close()
}
-
- /**
- * Ensures setIsDreaming(true) is called when the view is built.
- */
- @Test
- fun testSetIsDreamingTrueOnViewCreate() {
- `when`(precondition.conditionsMet()).thenReturn(true)
-
- controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
-
- verify(smartspaceView).setIsDreaming(true)
- }
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
index b4cae38d8b6e..d0cf792b698d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
@@ -74,9 +74,9 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
// Verify ripple added to window manager.
captor.value.onBatteryLevelChanged(
- 0 /* unusedBatteryLevel */,
- true /* plugged in */,
- false /* charging */)
+ /* unusedBatteryLevel= */ 0,
+ /* plugged in= */ true,
+ /* charging= */ false)
val attachListenerCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
verify(rippleView).addOnAttachStateChangeListener(attachListenerCaptor.capture())
@@ -144,4 +144,22 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
// Verify that ripple is triggered.
verify(rippleView).addOnAttachStateChangeListener(ArgumentMatchers.any())
}
+
+ @Test
+ fun testRipple_whenDocked_doesNotPlayRipple() {
+ `when`(batteryController.isChargingSourceDock).thenReturn(true)
+ val captor = ArgumentCaptor
+ .forClass(BatteryController.BatteryStateChangeCallback::class.java)
+ verify(batteryController).addCallback(captor.capture())
+
+ captor.value.onBatteryLevelChanged(
+ /* unusedBatteryLevel= */ 0,
+ /* plugged in= */ true,
+ /* charging= */ false)
+
+ val attachListenerCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
+ verify(rippleView, never()).addOnAttachStateChangeListener(attachListenerCaptor.capture())
+ verify(windowManager, never()).addView(eq(rippleView), any<WindowManager.LayoutParams>())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index 7d06abf5cd67..3fc0c8176f2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -63,7 +63,7 @@ public class DynamicPrivacyControllerTest extends SysuiTestCase {
mock(StatusBarKeyguardViewManager.class));
mDynamicPrivacyController.addListener(mListener);
// Disable dynamic privacy by default
- allowPrivateNotificationsInPublic(true);
+ allowNotificationsInPublic(false);
}
@Test
@@ -108,24 +108,21 @@ public class DynamicPrivacyControllerTest extends SysuiTestCase {
@Test
public void dynamicPrivacyOnlyWhenHidingPrivate() {
- // Verify that when only hiding notifications, this isn't enabled
- allowPrivateNotificationsInPublic(true);
- when(mLockScreenUserManager.shouldHideNotifications(any())).thenReturn(
- false);
- assertFalse("Dynamic privacy shouldn't be enabled when only hiding notifications",
+ // Verify that when hiding notifications, this isn't enabled
+ allowNotificationsInPublic(false);
+ assertFalse("Dynamic privacy shouldn't be enabled when hiding notifications",
mDynamicPrivacyController.isDynamicPrivacyEnabled());
- allowPrivateNotificationsInPublic(false);
- assertTrue("Should be enabled when hiding notification contents",
+ allowNotificationsInPublic(true);
+ assertTrue("Should be enabled whenever notifications are visible",
mDynamicPrivacyController.isDynamicPrivacyEnabled());
}
private void enableDynamicPrivacy() {
- allowPrivateNotificationsInPublic(false);
+ allowNotificationsInPublic(true);
}
- private void allowPrivateNotificationsInPublic(boolean allow) {
- when(mLockScreenUserManager.userAllowsPrivateNotificationsInPublic(anyInt())).thenReturn(
- allow);
+ private void allowNotificationsInPublic(boolean allow) {
+ when(mLockScreenUserManager.userAllowsNotificationsInPublic(anyInt())).thenReturn(allow);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java
index 4507366e3073..ee7d55864931 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java
@@ -85,6 +85,11 @@ public class NoManSimulator {
mRankings.put(key, ranking);
}
+ /** This is for testing error cases: b/216384850 */
+ public Ranking removeRankingWithoutEvent(String key) {
+ return mRankings.remove(key);
+ }
+
private RankingMap buildRankingMap() {
return new RankingMap(mRankings.values().toArray(new Ranking[0]));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 958d54230f1c..af43826091a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -1492,6 +1492,80 @@ public class NotifCollectionTest extends SysuiTestCase {
}
@Test
+ public void testMissingRankingWhenRemovalFeatureIsDisabled() {
+ // GIVEN a pipeline with one two notifications
+ when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(false);
+ String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key;
+ String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key;
+ NotificationEntry entry1 = mCollectionListener.getEntry(key1);
+ NotificationEntry entry2 = mCollectionListener.getEntry(key2);
+ clearInvocations(mCollectionListener);
+
+ // GIVEN the message for removing key1 gets does not reach NotifCollection
+ Ranking ranking1 = mNoMan.removeRankingWithoutEvent(key1);
+ // WHEN the message for removing key2 arrives
+ mNoMan.retractNotif(entry2.getSbn(), REASON_APP_CANCEL);
+
+ // THEN only entry2 gets removed
+ verify(mCollectionListener).onEntryRemoved(eq(entry2), eq(REASON_APP_CANCEL));
+ verify(mCollectionListener).onEntryCleanUp(eq(entry2));
+ verify(mCollectionListener).onRankingApplied();
+ verifyNoMoreInteractions(mCollectionListener);
+ verify(mLogger).logMissingRankings(eq(List.of(entry1)), eq(1), any());
+ verify(mLogger, never()).logRecoveredRankings(any());
+ clearInvocations(mCollectionListener, mLogger);
+
+ // WHEN a ranking update includes key1 again
+ mNoMan.setRanking(key1, ranking1);
+ mNoMan.issueRankingUpdate();
+
+ // VERIFY that we do nothing but log the 'recovery'
+ verify(mCollectionListener).onRankingUpdate(any());
+ verify(mCollectionListener).onRankingApplied();
+ verifyNoMoreInteractions(mCollectionListener);
+ verify(mLogger, never()).logMissingRankings(any(), anyInt(), any());
+ verify(mLogger).logRecoveredRankings(eq(List.of(key1)));
+ }
+
+ @Test
+ public void testMissingRankingWhenRemovalFeatureIsEnabled() {
+ // GIVEN a pipeline with one two notifications
+ when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(true);
+ String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key;
+ String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key;
+ NotificationEntry entry1 = mCollectionListener.getEntry(key1);
+ NotificationEntry entry2 = mCollectionListener.getEntry(key2);
+ clearInvocations(mCollectionListener);
+
+ // GIVEN the message for removing key1 gets does not reach NotifCollection
+ Ranking ranking1 = mNoMan.removeRankingWithoutEvent(key1);
+ // WHEN the message for removing key2 arrives
+ mNoMan.retractNotif(entry2.getSbn(), REASON_APP_CANCEL);
+
+ // THEN both entry1 and entry2 get removed
+ verify(mCollectionListener).onEntryRemoved(eq(entry2), eq(REASON_APP_CANCEL));
+ verify(mCollectionListener).onEntryRemoved(eq(entry1), eq(REASON_UNKNOWN));
+ verify(mCollectionListener).onEntryCleanUp(eq(entry2));
+ verify(mCollectionListener).onEntryCleanUp(eq(entry1));
+ verify(mCollectionListener).onRankingApplied();
+ verifyNoMoreInteractions(mCollectionListener);
+ verify(mLogger).logMissingRankings(eq(List.of(entry1)), eq(1), any());
+ verify(mLogger, never()).logRecoveredRankings(any());
+ clearInvocations(mCollectionListener, mLogger);
+
+ // WHEN a ranking update includes key1 again
+ mNoMan.setRanking(key1, ranking1);
+ mNoMan.issueRankingUpdate();
+
+ // VERIFY that we do nothing but log the 'recovery'
+ verify(mCollectionListener).onRankingUpdate(any());
+ verify(mCollectionListener).onRankingApplied();
+ verifyNoMoreInteractions(mCollectionListener);
+ verify(mLogger, never()).logMissingRankings(any(), anyInt(), any());
+ verify(mLogger).logRecoveredRankings(eq(List.of(key1)));
+ }
+
+ @Test
public void testRegisterFutureDismissal() throws RemoteException {
// GIVEN a pipeline with one notification
NotifEvent notifEvent = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
@@ -1610,9 +1684,9 @@ public class NotifCollectionTest extends SysuiTestCase {
return new CollectionEvent(rawEvent, requireNonNull(mEntryCaptor.getValue()));
}
- private void verifyBuiltList(Collection<NotificationEntry> list) {
- verify(mBuildListener).onBuildList(mBuildListCaptor.capture());
- assertEquals(new ArraySet<>(list), new ArraySet<>(mBuildListCaptor.getValue()));
+ private void verifyBuiltList(Collection<NotificationEntry> expectedList) {
+ verify(mBuildListener).onBuildList(mBuildListCaptor.capture(), any());
+ assertThat(mBuildListCaptor.getValue()).containsExactly(expectedList.toArray());
}
private static class RecordingCollectionListener implements NotifCollectionListener {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 769143ddbc0d..d4add7547656 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -108,6 +108,7 @@ public class NotificationEntryTest extends SysuiTestCase {
@Test
public void testBlockableEntryWhenCritical() {
doReturn(true).when(mChannel).isBlockable();
+ mEntry.setRanking(mEntry.getRanking());
assertTrue(mEntry.isBlockable());
}
@@ -117,6 +118,7 @@ public class NotificationEntryTest extends SysuiTestCase {
public void testBlockableEntryWhenCriticalAndChannelNotBlockable() {
doReturn(true).when(mChannel).isBlockable();
doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
+ mEntry.setRanking(mEntry.getRanking());
assertTrue(mEntry.isBlockable());
}
@@ -125,6 +127,7 @@ public class NotificationEntryTest extends SysuiTestCase {
public void testNonBlockableEntryWhenCriticalAndChannelNotBlockable() {
doReturn(false).when(mChannel).isBlockable();
doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
+ mEntry.setRanking(mEntry.getRanking());
assertFalse(mEntry.isBlockable());
}
@@ -164,6 +167,9 @@ public class NotificationEntryTest extends SysuiTestCase {
doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
doReturn(false).when(mChannel).isBlockable();
+ mEntry.setRanking(mEntry.getRanking());
+
+ assertFalse(mEntry.isBlockable());
assertTrue(mEntry.isExemptFromDndVisualSuppression());
assertFalse(mEntry.shouldSuppressAmbient());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 4e7e79f2cb26..555adfdfdc31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -53,6 +53,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.NotificationInteractionTracker;
+import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
@@ -569,7 +570,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
assertTrue(entry.hasFinishedInitialization());
// WHEN the pipeline is kicked off
- mReadyForBuildListener.onBuildList(singletonList(entry));
+ mReadyForBuildListener.onBuildList(singletonList(entry), "test");
mPipelineChoreographer.runIfScheduled();
// THEN the entry's initialization time is reset
@@ -1797,6 +1798,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
@Test
public void testStableMultipleSectionOrdering() {
+ // WHEN the list is originally built with reordering disabled
mListBuilder.setSectioners(asList(
new PackageSectioner(PACKAGE_1), new PackageSectioner(PACKAGE_2)));
mStabilityManager.setAllowEntryReordering(false);
@@ -1807,12 +1809,94 @@ public class ShadeListBuilderTest extends SysuiTestCase {
addNotif(3, PACKAGE_1).setRank(3);
dispatchBuild();
+ // VERIFY the order and that entry reordering has not been suppressed
verifyBuiltList(
notif(0),
notif(1),
notif(3),
notif(2)
);
+ verify(mStabilityManager, never()).onEntryReorderSuppressed();
+
+ // WHEN the ranks change
+ setNewRank(notif(0).entry, 4);
+ dispatchBuild();
+
+ // VERIFY the order does not change that entry reordering has been suppressed
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ notif(3),
+ notif(2)
+ );
+ verify(mStabilityManager).onEntryReorderSuppressed();
+
+ // WHEN reordering is now allowed again
+ mStabilityManager.setAllowEntryReordering(true);
+ dispatchBuild();
+
+ // VERIFY that list order changes
+ verifyBuiltList(
+ notif(1),
+ notif(3),
+ notif(0),
+ notif(2)
+ );
+ }
+
+ @Test
+ public void testStableChildOrdering() {
+ // WHEN the list is originally built with reordering disabled
+ mStabilityManager.setAllowEntryReordering(false);
+ addGroupSummary(0, PACKAGE_1, GROUP_1).setRank(0);
+ addGroupChild(1, PACKAGE_1, GROUP_1).setRank(1);
+ addGroupChild(2, PACKAGE_1, GROUP_1).setRank(2);
+ addGroupChild(3, PACKAGE_1, GROUP_1).setRank(3);
+ dispatchBuild();
+
+ // VERIFY the order and that entry reordering has not been suppressed
+ verifyBuiltList(
+ group(
+ summary(0),
+ child(1),
+ child(2),
+ child(3)
+ )
+ );
+ verify(mStabilityManager, never()).onEntryReorderSuppressed();
+
+ // WHEN the ranks change
+ setNewRank(notif(2).entry, 5);
+ dispatchBuild();
+
+ // VERIFY the order does not change that entry reordering has been suppressed
+ verifyBuiltList(
+ group(
+ summary(0),
+ child(1),
+ child(2),
+ child(3)
+ )
+ );
+ verify(mStabilityManager).onEntryReorderSuppressed();
+
+ // WHEN reordering is now allowed again
+ mStabilityManager.setAllowEntryReordering(true);
+ dispatchBuild();
+
+ // VERIFY that list order changes
+ verifyBuiltList(
+ group(
+ summary(0),
+ child(1),
+ child(3),
+ child(2)
+ )
+ );
+ }
+
+ private static void setNewRank(NotificationEntry entry, int rank) {
+ entry.setRanking(new RankingBuilder(entry.getRanking()).setRank(rank).build());
}
@Test
@@ -2008,7 +2092,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
mPendingSet.clear();
}
- mReadyForBuildListener.onBuildList(mEntrySet);
+ mReadyForBuildListener.onBuildList(mEntrySet, "test");
mPipelineChoreographer.runIfScheduled();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
deleted file mode 100644
index d082d74078db..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.os.Handler;
-import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * TODO(b/224771204) Create test cases
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@Ignore
-public class KeyguardCoordinatorTest extends SysuiTestCase {
- private static final int NOTIF_USER_ID = 0;
- private static final int CURR_USER_ID = 1;
-
- @Mock private Handler mMainHandler;
- @Mock private KeyguardStateController mKeyguardStateController;
- @Mock private BroadcastDispatcher mBroadcastDispatcher;
- @Mock private StatusBarStateController mStatusBarStateController;
- @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock private HighPriorityProvider mHighPriorityProvider;
- @Mock private SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
- @Mock private NotifPipeline mNotifPipeline;
- @Mock private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
-
- private NotificationEntry mEntry;
- private NotifFilter mKeyguardFilter;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- KeyguardCoordinator keyguardCoordinator = new KeyguardCoordinator(
- mStatusBarStateController,
- mKeyguardUpdateMonitor, mHighPriorityProvider, mSectionHeaderVisibilityProvider,
- mKeyguardNotificationVisibilityProvider, mock(SharedCoordinatorLogger.class));
-
- mEntry = new NotificationEntryBuilder()
- .setUser(new UserHandle(NOTIF_USER_ID))
- .build();
-
- ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- keyguardCoordinator.attach(mNotifPipeline);
- verify(mNotifPipeline, times(1)).addFinalizeFilter(filterCaptor.capture());
- mKeyguardFilter = filterCaptor.getValue();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
new file mode 100644
index 000000000000..8c506a6d16ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class KeyguardCoordinatorTest : SysuiTestCase() {
+ private val notifPipeline: NotifPipeline = mock()
+ private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
+ private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
+ private val sharedCoordinatorLogger: SharedCoordinatorLogger = mock()
+ private val statusBarStateController: StatusBarStateController = mock()
+
+ private lateinit var onStateChangeListener: Consumer<String>
+ private lateinit var keyguardFilter: NotifFilter
+
+ @Before
+ fun setup() {
+ val keyguardCoordinator = KeyguardCoordinator(
+ keyguardNotifVisibilityProvider,
+ sectionHeaderVisibilityProvider,
+ sharedCoordinatorLogger,
+ statusBarStateController
+ )
+ keyguardCoordinator.attach(notifPipeline)
+ onStateChangeListener = withArgCaptor {
+ verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+ }
+ keyguardFilter = withArgCaptor {
+ verify(notifPipeline).addFinalizeFilter(capture())
+ }
+ }
+
+ @Test
+ fun testSetSectionHeadersVisibleInShade() {
+ clearInvocations(sectionHeaderVisibilityProvider)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+ onStateChangeListener.accept("state change")
+ verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(true)
+ }
+
+ @Test
+ fun testSetSectionHeadersNotVisibleOnKeyguard() {
+ clearInvocations(sectionHeaderVisibilityProvider)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ onStateChangeListener.accept("state change")
+ verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index 72d8ff3f35f5..a6d3719de5cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.when;
import static java.util.Objects.requireNonNull;
+import android.os.Handler;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -42,7 +43,6 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.notification.SectionClassifier;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.ListEntry;
@@ -57,8 +57,10 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -97,8 +99,10 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Mock private IStatusBarService mService;
@Mock private BindEventManagerImpl mBindEventManagerImpl;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+ @Mock private Handler mHandler;
+ @Mock private SecureSettings mSecureSettings;
@Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
- private final SectionClassifier mSectionClassifier = new SectionClassifier();
+ private final SectionStyleProvider mSectionStyleProvider = new SectionStyleProvider();
private NotifUiAdjustmentProvider mAdjustmentProvider;
@@ -110,8 +114,11 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mAdjustmentProvider =
- new NotifUiAdjustmentProvider(mLockscreenUserManager, mSectionClassifier);
+ mAdjustmentProvider = new NotifUiAdjustmentProvider(
+ mHandler,
+ mSecureSettings,
+ mLockscreenUserManager,
+ mSectionStyleProvider);
mEntry = getNotificationEntryBuilder().setParent(ROOT_ENTRY).build();
mInflationError = new Exception(TEST_MESSAGE);
mErrorManager = new NotifInflationErrorManager();
@@ -495,7 +502,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
private static final int TEST_MAX_GROUP_DELAY = 100;
private void setSectionIsLowPriority(boolean minimized) {
- mSectionClassifier.setMinimizedSections(minimized
+ mSectionStyleProvider.setMinimizedSections(minimized
? Collections.singleton(mNotifSection.getSectioner())
: Collections.emptyList());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index 15c1cb7b123a..50b3fc7e1c42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -42,7 +42,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
-import com.android.systemui.statusbar.notification.SectionClassifier;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -50,6 +49,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
@@ -70,7 +70,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private HighPriorityProvider mHighPriorityProvider;
- @Mock private SectionClassifier mSectionClassifier;
+ @Mock private SectionStyleProvider mSectionStyleProvider;
@Mock private NotifPipeline mNotifPipeline;
@Mock private NodeController mAlertingHeaderController;
@Mock private NodeController mSilentNodeController;
@@ -94,7 +94,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
mRankingCoordinator = new RankingCoordinator(
mStatusBarStateController,
mHighPriorityProvider,
- mSectionClassifier,
+ mSectionStyleProvider,
mAlertingHeaderController,
mSilentHeaderController,
mSilentNodeController);
@@ -102,7 +102,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
mEntry.setRanking(getRankingForUnfilteredNotif().build());
mRankingCoordinator.attach(mNotifPipeline);
- verify(mSectionClassifier).setMinimizedSections(any());
+ verify(mSectionStyleProvider).setMinimizedSections(any());
verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture());
mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0);
mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
index 447ba1510e13..3f3de009fb04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
@@ -21,13 +21,13 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.AssistantFeedbackController
import com.android.systemui.statusbar.notification.FeedbackIcon
-import com.android.systemui.statusbar.notification.SectionClassifier
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.collection.render.NotifRowController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -37,8 +37,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations.initMocks
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations.initMocks
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -53,7 +53,7 @@ class RowAppearanceCoordinatorTest : SysuiTestCase() {
@Mock private lateinit var pipeline: NotifPipeline
@Mock private lateinit var assistantFeedbackController: AssistantFeedbackController
- @Mock private lateinit var sectionClassifier: SectionClassifier
+ @Mock private lateinit var sectionStyleProvider: SectionStyleProvider
@Mock private lateinit var section1: NotifSection
@Mock private lateinit var section2: NotifSection
@@ -66,7 +66,7 @@ class RowAppearanceCoordinatorTest : SysuiTestCase() {
coordinator = RowAppearanceCoordinator(
mContext,
assistantFeedbackController,
- sectionClassifier
+ sectionStyleProvider
)
coordinator.attach(pipeline)
beforeRenderListListener = withArgCaptor {
@@ -82,8 +82,8 @@ class RowAppearanceCoordinatorTest : SysuiTestCase() {
@Test
fun testSetSystemExpandedOnlyOnFirst() {
- whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(false)
- whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(false)
+ whenever(sectionStyleProvider.isMinimizedSection(eq(section1))).thenReturn(false)
+ whenever(sectionStyleProvider.isMinimizedSection(eq(section1))).thenReturn(false)
beforeRenderListListener.onBeforeRenderList(listOf(entry1, entry2))
afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
verify(controller1).setSystemExpanded(eq(true))
@@ -93,8 +93,8 @@ class RowAppearanceCoordinatorTest : SysuiTestCase() {
@Test
fun testSetSystemExpandedNeverIfMinimized() {
- whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(true)
- whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(true)
+ whenever(sectionStyleProvider.isMinimizedSection(eq(section1))).thenReturn(true)
+ whenever(sectionStyleProvider.isMinimizedSection(eq(section1))).thenReturn(true)
beforeRenderListListener.onBeforeRenderList(listOf(entry1, entry2))
afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
verify(controller1).setSystemExpanded(eq(false))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index dd15caebf9e6..246943e3088e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -1,29 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.systemui.statusbar.notification.collection.inflation
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.notification.SectionClassifier
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class NotifUiAdjustmentProviderTest : SysuiTestCase() {
private val lockscreenUserManager: NotificationLockscreenUserManager = mock()
- private val sectionClassifier: SectionClassifier = mock()
+ private val sectionStyleProvider: SectionStyleProvider = mock()
+ private val handler: Handler = mock()
+ private val secureSettings: SecureSettings = mock()
+ private val uri = FakeSettings().getUriFor(SHOW_NOTIFICATION_SNOOZE)
+ private val dirtyListener: Runnable = mock()
+
+ private val section = NotifSection(mock(), 0)
+ private val entry = NotificationEntryBuilder()
+ .setSection(section)
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .build()
+
+ private lateinit var contentObserver: ContentObserver
private val adjustmentProvider = NotifUiAdjustmentProvider(
+ handler,
+ secureSettings,
lockscreenUserManager,
- sectionClassifier,
+ sectionStyleProvider,
)
+ @Before
+ fun setup() {
+ verifyNoMoreInteractions(secureSettings)
+ adjustmentProvider.addDirtyListener(dirtyListener)
+ verify(secureSettings).getInt(eq(SHOW_NOTIFICATION_SNOOZE), any())
+ contentObserver = withArgCaptor {
+ verify(secureSettings).registerContentObserverForUser(
+ eq(SHOW_NOTIFICATION_SNOOZE), capture(), any()
+ )
+ }
+ verifyNoMoreInteractions(secureSettings, dirtyListener)
+ }
+
@Test
fun notifLockscreenStateChangeWillNotifDirty() {
val dirtyListener = mock<Runnable>()
@@ -33,6 +91,35 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
verify(lockscreenUserManager).addNotificationStateChangedListener(capture())
}
notifLocksreenStateChangeListener.onNotificationStateChanged()
- verify(dirtyListener).run();
+ verify(dirtyListener).run()
+ }
+
+ @Test
+ fun additionalAddDoesNotRegisterAgain() {
+ clearInvocations(secureSettings)
+ adjustmentProvider.addDirtyListener(mock())
+ verifyNoMoreInteractions(secureSettings)
+ }
+
+ @Test
+ fun onChangeWillQueryThenNotifyDirty() {
+ contentObserver.onChange(false, listOf(uri), 0, 0)
+ with(inOrder(secureSettings, dirtyListener)) {
+ verify(secureSettings).getInt(eq(SHOW_NOTIFICATION_SNOOZE), any())
+ verify(dirtyListener).run()
+ }
+ }
+
+ @Test
+ fun changingSnoozeChangesProvidedAdjustment() {
+ whenever(secureSettings.getInt(eq(SHOW_NOTIFICATION_SNOOZE), any())).thenReturn(0)
+ val original = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(original.isSnoozeEnabled).isFalse()
+
+ whenever(secureSettings.getInt(eq(SHOW_NOTIFICATION_SNOOZE), any())).thenReturn(1)
+ contentObserver.onChange(false, listOf(uri), 0, 0)
+ val withSnoozing = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(withSnoozing.isSnoozeEnabled).isTrue()
+ assertThat(withSnoozing).isNotEqualTo(original)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt
new file mode 100644
index 000000000000..6c07174bce65
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.notifcollection
+
+import android.service.notification.NotificationListenerService.RankingMap
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotifCollectionLoggerTest : SysuiTestCase() {
+ private val logger: NotifCollectionLogger = mock()
+ private val entry1: NotificationEntry = NotificationEntryBuilder().setId(1).build()
+ private val entry2: NotificationEntry = NotificationEntryBuilder().setId(2).build()
+
+ private fun mapOfEntries(vararg entries: NotificationEntry): Map<String, NotificationEntry> =
+ entries.associateBy { it.key }
+
+ private fun rankingMapOf(vararg entries: NotificationEntry): RankingMap =
+ RankingMap(entries.map { it.ranking }.toTypedArray())
+
+ @Test
+ fun testMaybeLogInconsistentRankings_logsNewlyInconsistentRanking() {
+ val rankingMap = rankingMapOf(entry1)
+ maybeLogInconsistentRankings(
+ logger = logger,
+ oldKeysWithoutRankings = emptySet(),
+ newEntriesWithoutRankings = mapOfEntries(entry2),
+ rankingMap = rankingMap
+ )
+ verify(logger).logMissingRankings(
+ newlyInconsistentEntries = eq(listOf(entry2)),
+ totalInconsistent = eq(1),
+ rankingMap = eq(rankingMap),
+ )
+ verifyNoMoreInteractions(logger)
+ }
+
+ @Test
+ fun testMaybeLogInconsistentRankings_doesNotLogAlreadyInconsistentRanking() {
+ maybeLogInconsistentRankings(
+ logger = logger,
+ oldKeysWithoutRankings = setOf(entry2.key),
+ newEntriesWithoutRankings = mapOfEntries(entry2),
+ rankingMap = rankingMapOf(entry1)
+ )
+ verifyNoMoreInteractions(logger)
+ }
+
+ @Test
+ fun testMaybeLogInconsistentRankings_logsWhenRankingIsAdded() {
+ maybeLogInconsistentRankings(
+ logger = logger,
+ oldKeysWithoutRankings = setOf(entry2.key),
+ newEntriesWithoutRankings = mapOfEntries(),
+ rankingMap = rankingMapOf(entry1, entry2)
+ )
+ verify(logger).logRecoveredRankings(
+ newlyConsistentKeys = eq(listOf(entry2.key)),
+ )
+ verifyNoMoreInteractions(logger)
+ }
+
+ @Test
+ fun testMaybeLogInconsistentRankings_doesNotLogsWhenEntryIsRemoved() {
+ maybeLogInconsistentRankings(
+ logger = logger,
+ oldKeysWithoutRankings = setOf(entry2.key),
+ newEntriesWithoutRankings = mapOfEntries(),
+ rankingMap = rankingMapOf(entry1)
+ )
+ verifyNoMoreInteractions(logger)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
index 0e1865861cae..ac254abe60b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.collection.render
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
-import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -28,6 +27,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
import com.android.systemui.statusbar.notification.collection.getAttachState
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
@@ -413,4 +413,4 @@ private fun buildSection(
return nodeController
}
}, index)
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
index 0d5a5fe086a3..3f641df376ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
@@ -72,32 +72,32 @@ public class HeadsUpViewBinderTest extends SysuiTestCase {
});
mViewBinder.bindHeadsUpView(mEntry, null);
- verify(mLogger).startBindingHun(eq("key"));
+ verify(mLogger).startBindingHun(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
callback.get().onBindFinished(mEntry);
- verify(mLogger).entryBoundSuccessfully(eq("key"));
+ verify(mLogger).entryBoundSuccessfully(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
mViewBinder.bindHeadsUpView(mEntry, null);
- verify(mLogger).startBindingHun(eq("key"));
+ verify(mLogger).startBindingHun(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
callback.get().onBindFinished(mEntry);
- verify(mLogger).entryBoundSuccessfully(eq("key"));
+ verify(mLogger).entryBoundSuccessfully(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
mViewBinder.unbindHeadsUpView(mEntry);
- verify(mLogger).entryContentViewMarkedFreeable(eq("key"));
+ verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
callback.get().onBindFinished(mEntry);
- verify(mLogger).entryUnbound(eq("key"));
+ verify(mLogger).entryUnbound(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
}
@@ -111,12 +111,12 @@ public class HeadsUpViewBinderTest extends SysuiTestCase {
});
mViewBinder.bindHeadsUpView(mEntry, null);
- verify(mLogger).startBindingHun(eq("key"));
+ verify(mLogger).startBindingHun(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
mViewBinder.abortBindCallback(mEntry);
- verify(mLogger).currentOngoingBindingAborted(eq("key"));
+ verify(mLogger).currentOngoingBindingAborted(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
@@ -135,18 +135,18 @@ public class HeadsUpViewBinderTest extends SysuiTestCase {
});
mViewBinder.bindHeadsUpView(mEntry, null);
- verify(mLogger).startBindingHun(eq("key"));
+ verify(mLogger).startBindingHun(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
mViewBinder.unbindHeadsUpView(mEntry);
- verify(mLogger).currentOngoingBindingAborted(eq("key"));
- verify(mLogger).entryContentViewMarkedFreeable(eq("key"));
+ verify(mLogger).currentOngoingBindingAborted(eq(mEntry));
+ verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
callback.get().onBindFinished(mEntry);
- verify(mLogger).entryUnbound(eq("key"));
+ verify(mLogger).entryUnbound(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt
index b24b348ac316..cafe113e7872 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -5,6 +5,8 @@ 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.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.systemui.statusbar.policy.FakeConfigurationController
@@ -13,6 +15,7 @@ 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)
@@ -21,6 +24,7 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
@Mock private lateinit var scrimController: ScrimController
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
private val configurationController = FakeConfigurationController()
private lateinit var controller: ScrimShadeTransitionController
@@ -31,9 +35,14 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
context.ensureTestableResources()
controller =
ScrimShadeTransitionController(
- configurationController, dumpManager, scrimController, context.resources
+ configurationController,
+ dumpManager,
+ scrimController,
+ context.resources,
+ statusBarStateController
)
}
+
@Test
fun onPanelExpansionChanged_inSingleShade_setsFractionEqualToEventFraction() {
setSplitShadeEnabled(false)
@@ -44,7 +53,9 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
}
@Test
- fun onPanelExpansionChanged_inSplitShade_setsFractionBasedOnDragDownAmount() {
+ 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)
@@ -55,6 +66,54 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
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_onKeyguard_setsFractionEqualToEventFraction() {
+ 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()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index fec2123b304a..fda80a2f9ed8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Intent;
+import android.os.BatteryManager;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerSaveState;
@@ -196,4 +197,26 @@ public class BatteryControllerTest extends SysuiTestCase {
TestableLooper.get(this).processAllMessages();
// Should not throw an exception
}
+
+ @Test
+ public void batteryStateChanged_withChargingSourceDock_isChargingSourceDockTrue() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
+ intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_DOCK);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertTrue(mBatteryController.isChargingSourceDock());
+ }
+
+ @Test
+ public void batteryStateChanged_withChargingSourceNotDock_isChargingSourceDockFalse() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING);
+ intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertFalse(mBatteryController.isChargingSourceDock());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
new file mode 100644
index 000000000000..db0029af4ee2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.pm.PackageManager
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraManager
+import android.hardware.camera2.impl.CameraMetadataNative
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FlashlightControllerImplTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var dumpManager: DumpManager
+
+ @Mock
+ private lateinit var cameraManager: CameraManager
+
+ @Mock
+ private lateinit var broadcastSender: BroadcastSender
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
+ private lateinit var fakeSettings: FakeSettings
+ private lateinit var fakeSystemClock: FakeSystemClock
+ private lateinit var backgroundExecutor: FakeExecutor
+ private lateinit var controller: FlashlightControllerImpl
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ fakeSystemClock = FakeSystemClock()
+ backgroundExecutor = FakeExecutor(fakeSystemClock)
+ fakeSettings = FakeSettings()
+
+ `when`(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH))
+ .thenReturn(true)
+
+ controller = FlashlightControllerImpl(
+ dumpManager,
+ cameraManager,
+ backgroundExecutor,
+ fakeSettings,
+ broadcastSender,
+ packageManager
+ )
+ }
+
+ @Test
+ fun testNoCameraManagerInteractionDirectlyOnConstructor() {
+ verifyZeroInteractions(cameraManager)
+ }
+
+ @Test
+ fun testCameraManagerInitAfterConstructionOnExecutor() {
+ injectCamera()
+ backgroundExecutor.runAllReady()
+
+ verify(cameraManager).registerTorchCallback(eq(backgroundExecutor), any())
+ }
+
+ @Test
+ fun testNoCallbackIfNoFlashCamera() {
+ injectCamera(flash = false)
+ backgroundExecutor.runAllReady()
+
+ verify(cameraManager, never()).registerTorchCallback(any<Executor>(), any())
+ }
+
+ @Test
+ fun testNoCallbackIfNoBackCamera() {
+ injectCamera(facing = CameraCharacteristics.LENS_FACING_FRONT)
+ backgroundExecutor.runAllReady()
+
+ verify(cameraManager, never()).registerTorchCallback(any<Executor>(), any())
+ }
+
+ @Test
+ fun testSetFlashlightInBackgroundExecutor() {
+ val id = injectCamera()
+ backgroundExecutor.runAllReady()
+
+ clearInvocations(cameraManager)
+ val enable = !controller.isEnabled
+ controller.setFlashlight(enable)
+ verifyNoMoreInteractions(cameraManager)
+
+ backgroundExecutor.runAllReady()
+ verify(cameraManager).setTorchMode(id, enable)
+ }
+
+ private fun injectCamera(
+ flash: Boolean = true,
+ facing: Int = CameraCharacteristics.LENS_FACING_BACK
+ ): String {
+ val cameraID = "ID"
+ val camera = CameraCharacteristics(CameraMetadataNative().apply {
+ set(CameraCharacteristics.FLASH_INFO_AVAILABLE, flash)
+ set(CameraCharacteristics.LENS_FACING, facing)
+ })
+ `when`(cameraManager.cameraIdList).thenReturn(arrayOf(cameraID))
+ `when`(cameraManager.getCameraCharacteristics(cameraID)).thenReturn(camera)
+ return cameraID
+ }
+}
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 424a40058997..b8e25ab43691 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
@@ -106,7 +106,7 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest {
public void testHunRemovedLogging() {
mAlertEntry.mEntry = mEntry;
mHeadsUpManager.onAlertEntryRemoved(mAlertEntry);
- verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry.getKey()));
+ verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
index 1e35b0f0e68a..125b3627b342 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
@@ -159,6 +159,24 @@ public class ConditionMonitorTest extends SysuiTestCase {
Mockito.clearInvocations(callback);
}
+ // Ensure that updating a callback that is removed doesn't result in an exception due to the
+ // absence of the condition.
+ @Test
+ public void testUpdateRemovedCallback() {
+ final Monitor.Callback callback1 =
+ mock(Monitor.Callback.class);
+ final Monitor.Subscription.Token subscription1 =
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback1).build());
+ ArgumentCaptor<Condition.Callback> monitorCallback =
+ ArgumentCaptor.forClass(Condition.Callback.class);
+ mExecutor.runAllReady();
+ verify(mCondition1).addCallback(monitorCallback.capture());
+ // This will execute first before the handler for onConditionChanged.
+ mConditionMonitor.removeSubscription(subscription1);
+ monitorCallback.getValue().onConditionChanged(mCondition1);
+ mExecutor.runAllReady();
+ }
+
@Test
public void addCallback_addFirstCallback_addCallbackToAllConditions() {
final Monitor.Callback callback1 =
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
index 229799a8457d..d5991d3930a8 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -73,6 +73,9 @@ class AssociationStoreImpl implements AssociationStore {
private final Set<OnChangeListener> mListeners = new LinkedHashSet<>();
void addAssociation(@NonNull AssociationInfo association) {
+ // Validity check first.
+ checkNotRevoked(association);
+
final int id = association.getId();
if (DEBUG) {
@@ -99,6 +102,9 @@ class AssociationStoreImpl implements AssociationStore {
}
void updateAssociation(@NonNull AssociationInfo updated) {
+ // Validity check first.
+ checkNotRevoked(updated);
+
final int id = updated.getId();
if (DEBUG) {
@@ -292,6 +298,9 @@ class AssociationStoreImpl implements AssociationStore {
}
void setAssociations(Collection<AssociationInfo> allAssociations) {
+ // Validity check first.
+ allAssociations.forEach(AssociationStoreImpl::checkNotRevoked);
+
if (DEBUG) {
Log.i(TAG, "setAssociations() n=" + allAssociations.size());
final StringJoiner stringJoiner = new StringJoiner(", ");
@@ -324,4 +333,11 @@ class AssociationStoreImpl implements AssociationStore {
mAddressMap.clear();
mCachedPerUser.clear();
}
+
+ private static void checkNotRevoked(@NonNull AssociationInfo association) {
+ if (association.isRevoked()) {
+ throw new IllegalArgumentException(
+ "Revoked (removed) associations MUST NOT appear in the AssociationStore");
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 3f7cba6a4d09..abc49372053e 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -18,6 +18,7 @@
package com.android.server.companion;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -48,6 +49,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.NotificationManager;
@@ -91,6 +94,7 @@ import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.infra.PerUser;
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
@@ -100,10 +104,12 @@ import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -127,6 +133,9 @@ public class CompanionDeviceManagerService extends SystemService {
private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
+ private final ActivityManager mActivityManager;
+ private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
+
private PersistentDataStore mPersistentStore;
private final PersistUserStateHandler mUserPersistenceHandler;
@@ -135,6 +144,7 @@ public class CompanionDeviceManagerService extends SystemService {
private CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private CompanionApplicationController mCompanionAppController;
+ private final ActivityTaskManagerInternal mAtmInternal;
private final ActivityManagerInternal mAmInternal;
private final IAppOpsService mAppOpsManager;
private final PowerWhitelistManager mPowerWhitelistManager;
@@ -150,21 +160,53 @@ public class CompanionDeviceManagerService extends SystemService {
@GuardedBy("mPreviouslyUsedIds")
private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>();
+ /**
+ * A structure that consists of a set of revoked associations that pending for role holder
+ * removal per each user.
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #addToPendingRoleHolderRemoval(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
+ */
+ @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
+ private final PerUserAssociationSet mRevokedAssociationsPendingRoleHolderRemoval =
+ new PerUserAssociationSet();
+ /**
+ * Contains uid-s of packages pending to be removed from the role holder list (after
+ * revocation of an association), which will happen one the package is no longer visible to the
+ * user.
+ * For quicker uid -> (userId, packageName) look-up this is not a {@code Set<Integer>} but
+ * a {@code Map<Integer, String>} which maps uid-s to packageName-s (userId-s can be derived
+ * from uid-s using {@link UserHandle#getUserId(int)}).
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #addToPendingRoleHolderRemoval(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ */
+ @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
+ private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>();
+
private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
new RemoteCallbackList<>();
public CompanionDeviceManagerService(Context context) {
super(context);
+ mActivityManager = context.getSystemService(ActivityManager.class);
mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
mAppOpsManager = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
+ mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mUserManager = context.getSystemService(UserManager.class);
mUserPersistenceHandler = new PersistUserStateHandler();
mAssociationStore = new AssociationStoreImpl();
+
+ mOnPackageVisibilityChangeListener =
+ new OnPackageVisibilityChangeListener(mActivityManager);
}
@Override
@@ -201,7 +243,33 @@ public class CompanionDeviceManagerService extends SystemService {
mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds);
}
- mAssociationStore.setAssociations(allAssociations);
+ final Set<AssociationInfo> activeAssociations =
+ new ArraySet<>(/* capacity */ allAssociations.size());
+ // A set contains the userIds that need to persist state after remove the app
+ // from the list of role holders.
+ final Set<Integer> usersToPersistStateFor = new ArraySet<>();
+
+ for (AssociationInfo association : allAssociations) {
+ if (!association.isRevoked()) {
+ activeAssociations.add(association);
+ } else if (maybeRemoveRoleHolderForAssociation(association)) {
+ // Nothing more to do here, but we'll need to persist all the associations to the
+ // disk afterwards.
+ usersToPersistStateFor.add(association.getUserId());
+ } else {
+ addToPendingRoleHolderRemoval(association);
+ }
+ }
+
+ mAssociationStore.setAssociations(activeAssociations);
+
+ // IMPORTANT: only do this AFTER mAssociationStore.setAssociations(), because
+ // persistStateForUser() queries AssociationStore.
+ // (If persistStateForUser() is invoked before mAssociationStore.setAssociations() it
+ // would effectively just clear-out all the persisted associations).
+ for (int userId : usersToPersistStateFor) {
+ persistStateForUser(userId);
+ }
}
@Override
@@ -351,10 +419,18 @@ public class CompanionDeviceManagerService extends SystemService {
}
private void persistStateForUser(@UserIdInt int userId) {
- final List<AssociationInfo> updatedAssociations =
- mAssociationStore.getAssociationsForUser(userId);
+ // We want to store both active associations and the revoked (removed) association that we
+ // are keeping around for the final clean-up (delayed role holder removal).
+ final List<AssociationInfo> allAssociations;
+ // Start with the active associations - these we can get from the AssociationStore.
+ allAssociations = new ArrayList<>(
+ mAssociationStore.getAssociationsForUser(userId));
+ // ... and add the revoked (removed) association, that are yet to be permanently removed.
+ allAssociations.addAll(getPendingRoleHolderRemovalAssociationsForUser(userId));
+
final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
- mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser);
+
+ mPersistentStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
}
private void notifyListeners(
@@ -422,13 +498,17 @@ public class CompanionDeviceManagerService extends SystemService {
removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
}
- for (AssociationInfo ai : mAssociationStore.getAssociations()) {
- if (!ai.isSelfManaged()) continue;
- final boolean isInactive = currentTime - ai.getLastTimeConnectedMs() >= removalWindow;
- if (isInactive) {
- Slog.i(TAG, "Removing inactive self-managed association: " + ai.getId());
- disassociateInternal(ai.getId());
- }
+ for (AssociationInfo association : mAssociationStore.getAssociations()) {
+ if (!association.isSelfManaged()) continue;
+
+ final boolean isInactive =
+ currentTime - association.getLastTimeConnectedMs() >= removalWindow;
+ if (!isInactive) continue;
+
+ final int id = association.getId();
+
+ Slog.i(TAG, "Removing inactive self-managed association id=" + id);
+ disassociateInternal(id);
}
}
@@ -668,7 +748,7 @@ public class CompanionDeviceManagerService extends SystemService {
enforceCallerIsSystemOr(userId, packageName);
AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
- userId, packageName, deviceAddress);
+ userId, packageName, deviceAddress);
if (association == null) {
throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
@@ -728,7 +808,7 @@ public class CompanionDeviceManagerService extends SystemService {
enforceUsesCompanionDeviceFeature(getContext(), userId, callingPackage);
checkState(!ArrayUtils.isEmpty(
- mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
+ mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
"App must have an association before calling this API");
}
@@ -788,8 +868,8 @@ public class CompanionDeviceManagerService extends SystemService {
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
- macAddress, displayName, deviceProfile, selfManaged, false, timestamp,
- Long.MAX_VALUE);
+ macAddress, displayName, deviceProfile, selfManaged,
+ /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE);
Slog.i(TAG, "New CDM association created=" + association);
mAssociationStore.addAssociation(association);
@@ -801,6 +881,11 @@ public class CompanionDeviceManagerService extends SystemService {
updateSpecialAccessPermissionForAssociatedPackage(association);
logCreateAssociation(deviceProfile);
+
+ // Don't need to update the mRevokedAssociationsPendingRoleHolderRemoval since
+ // maybeRemoveRoleHolderForAssociation in PackageInactivityListener will handle the case
+ // that there are other devices with the same profile, so the role holder won't be removed.
+
return association;
}
@@ -881,36 +966,184 @@ public class CompanionDeviceManagerService extends SystemService {
final String packageName = association.getPackageName();
final String deviceProfile = association.getDeviceProfile();
+ if (!maybeRemoveRoleHolderForAssociation(association)) {
+ // Need to remove the app from list of the role holders, but will have to do it later
+ // (the app is in foreground at the moment).
+ addToPendingRoleHolderRemoval(association);
+ }
+
+ // Need to check if device still present now because CompanionDevicePresenceMonitor will
+ // remove current connected device after mAssociationStore.removeAssociation
final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId);
// Removing the association.
mAssociationStore.removeAssociation(associationId);
+ // Do not need to persistUserState since CompanionDeviceManagerService will get callback
+ // from #onAssociationChanged, and it will handle the persistUserState which including
+ // active and revoked association.
logRemoveAssociation(deviceProfile);
- final List<AssociationInfo> otherAssociations =
- mAssociationStore.getAssociationsForPackage(userId, packageName);
-
- // Check if the package is associated with other devices with the same profile.
- // If not: take away the role.
- if (deviceProfile != null) {
- final boolean shouldKeepTheRole = any(otherAssociations,
- it -> deviceProfile.equals(it.getDeviceProfile()));
- if (!shouldKeepTheRole) {
- Binder.withCleanCallingIdentity(() ->
- removeRoleHolderForAssociation(getContext(), association));
- }
- }
-
if (!wasPresent || !association.isNotifyOnDeviceNearby()) return;
// The device was connected and the app was notified: check if we need to unbind the app
// now.
- final boolean shouldStayBound = any(otherAssociations,
+ final boolean shouldStayBound = any(
+ mAssociationStore.getAssociationsForPackage(userId, packageName),
it -> it.isNotifyOnDeviceNearby()
&& mDevicePresenceMonitor.isDevicePresent(it.getId()));
if (shouldStayBound) return;
mCompanionAppController.unbindCompanionApplication(userId, packageName);
}
+ /**
+ * First, checks if the companion application should be removed from the list role holders when
+ * upon association's removal, i.e.: association's profile (matches the role) is not null,
+ * the application does not have other associations with the same profile, etc.
+ *
+ * <p>
+ * Then, if establishes that the application indeed has to be removed from the list of the role
+ * holders, checks if it could be done right now -
+ * {@link android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, UserHandle, java.util.concurrent.Executor, java.util.function.Consumer) RoleManager#removeRoleHolderAsUser()}
+ * will kill the application's process, which leads poor user experience if the application was
+ * in foreground when this happened, to avoid this CDMS delays invoking
+ * {@code RoleManager.removeRoleHolderAsUser()} until the app is no longer in foreground.
+ *
+ * @return {@code true} if the application does NOT need be removed from the list of the role
+ * holders OR if the application was successfully removed from the list of role holders.
+ * I.e.: from the role-management perspective the association is done with.
+ * {@code false} if the application needs to be removed from the list of role the role
+ * holders, BUT it CDMS would prefer to do it later.
+ * I.e.: application is in the foreground at the moment, but invoking
+ * {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process,
+ * which would lead to the poor UX, hence need to try later.
+ */
+
+ private boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
+ if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association);
+
+ final String deviceProfile = association.getDeviceProfile();
+ if (deviceProfile == null) {
+ // No role was granted to for this association, there is nothing else we need to here.
+ return true;
+ }
+
+ // Check if the applications is associated with another devices with the profile. If so,
+ // it should remain the role holder.
+ final int id = association.getId();
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ final boolean roleStillInUse = any(
+ mAssociationStore.getAssociationsForPackage(userId, packageName),
+ it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId());
+ if (roleStillInUse) {
+ // Application should remain a role holder, there is nothing else we need to here.
+ return true;
+ }
+
+ final int packageProcessImportance = getPackageProcessImportance(userId, packageName);
+ if (packageProcessImportance <= IMPORTANCE_VISIBLE) {
+ // Need to remove the app from the list of role holders, but the process is visible to
+ // the user at the moment, so we'll need to it later: log and return false.
+ Slog.i(TAG, "Cannot remove role holder for the removed association id=" + id
+ + " now - process is visible.");
+ return false;
+ }
+
+ removeRoleHolderForAssociation(getContext(), association);
+ return true;
+ }
+
+ private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
+ return Binder.withCleanCallingIdentity(() -> {
+ final int uid =
+ mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+ return mActivityManager.getUidImportance(uid);
+ });
+ }
+
+ /**
+ * Set revoked flag for active association and add the revoked association and the uid into
+ * the caches.
+ *
+ * @see #mRevokedAssociationsPendingRoleHolderRemoval
+ * @see #mUidsPendingRoleHolderRemoval
+ * @see OnPackageVisibilityChangeListener
+ */
+ private void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ // First: set revoked flag.
+ association = AssociationInfo.builder(association)
+ .setRevoked(true)
+ .build();
+
+ final String packageName = association.getPackageName();
+ final int userId = association.getUserId();
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+
+ // Second: add to the set.
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ mRevokedAssociationsPendingRoleHolderRemoval.forUser(association.getUserId())
+ .add(association);
+ if (!mUidsPendingRoleHolderRemoval.containsKey(uid)) {
+ mUidsPendingRoleHolderRemoval.put(uid, packageName);
+
+ if (mUidsPendingRoleHolderRemoval.size() == 1) {
+ // Just added first uid: start the listener
+ mOnPackageVisibilityChangeListener.startListening();
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove the revoked association form the cache and also remove the uid form the map if
+ * there are other associations with the same package still pending for role holder removal.
+ *
+ * @see #mRevokedAssociationsPendingRoleHolderRemoval
+ * @see #mUidsPendingRoleHolderRemoval
+ * @see OnPackageVisibilityChangeListener
+ */
+ private void removeFromPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ final String packageName = association.getPackageName();
+ final int userId = association.getUserId();
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)
+ .remove(association);
+
+ final boolean shouldKeepUidForRemoval = any(
+ getPendingRoleHolderRemovalAssociationsForUser(userId),
+ ai -> packageName.equals(ai.getPackageName()));
+ // Do not remove the uid form the map since other associations with
+ // the same packageName still pending for role holder removal.
+ if (!shouldKeepUidForRemoval) {
+ mUidsPendingRoleHolderRemoval.remove(uid);
+ }
+
+ if (mUidsPendingRoleHolderRemoval.isEmpty()) {
+ // The set is empty now - can "turn off" the listener.
+ mOnPackageVisibilityChangeListener.stopListening();
+ }
+ }
+ }
+
+ /**
+ * @return a copy of the revoked associations set (safeguarding against
+ * {@code ConcurrentModificationException}-s).
+ */
+ private @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
+ @UserIdInt int userId) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ // Return a copy.
+ return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
+ }
+ }
+
+ private String getPackageNameByUid(int uid) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ return mUidsPendingRoleHolderRemoval.get(uid);
+ }
+ }
+
private void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
final PackageInfo packageInfo =
getPackageInfo(getContext(), association.getUserId(), association.getPackageName());
@@ -969,6 +1202,9 @@ public class CompanionDeviceManagerService extends SystemService {
companionAppUids.add(uid);
}
}
+ if (mAtmInternal != null) {
+ mAtmInternal.setCompanionAppUids(userId, companionAppUids);
+ }
if (mAmInternal != null) {
// Make a copy of the set and send it to ActivityManager.
mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
@@ -1128,4 +1364,80 @@ public class CompanionDeviceManagerService extends SystemService {
persistStateForUser(userId);
}
}
+
+ /**
+ * An OnUidImportanceListener class which watches the importance of the packages.
+ * In this class, we ONLY interested in the importance of the running process is greater than
+ * {@link RunningAppProcessInfo.IMPORTANCE_VISIBLE} for the uids have been added into the
+ * {@link mUidsPendingRoleHolderRemoval}. Lastly remove the role holder for the revoked
+ * associations for the same packages.
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
+ */
+ private class OnPackageVisibilityChangeListener implements
+ ActivityManager.OnUidImportanceListener {
+ final @NonNull ActivityManager mAm;
+
+ OnPackageVisibilityChangeListener(@NonNull ActivityManager am) {
+ this.mAm = am;
+ }
+
+ void startListening() {
+ Binder.withCleanCallingIdentity(
+ () -> mAm.addOnUidImportanceListener(
+ /* listener */ OnPackageVisibilityChangeListener.this,
+ RunningAppProcessInfo.IMPORTANCE_VISIBLE));
+ }
+
+ void stopListening() {
+ Binder.withCleanCallingIdentity(
+ () -> mAm.removeOnUidImportanceListener(
+ /* listener */ OnPackageVisibilityChangeListener.this));
+ }
+
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ if (importance <= RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
+ // The lower the importance value the more "important" the process is.
+ // We are only interested when the process ceases to be visible.
+ return;
+ }
+
+ final String packageName = getPackageNameByUid(uid);
+ if (packageName == null) {
+ // Not interested in this uid.
+ return;
+ }
+
+ final int userId = UserHandle.getUserId(uid);
+
+ boolean needToPersistStateForUser = false;
+
+ for (AssociationInfo association :
+ getPendingRoleHolderRemovalAssociationsForUser(userId)) {
+ if (!packageName.equals(association.getPackageName())) continue;
+
+ if (!maybeRemoveRoleHolderForAssociation(association)) {
+ // Did not remove the role holder, will have to try again later.
+ continue;
+ }
+
+ removeFromPendingRoleHolderRemoval(association);
+ needToPersistStateForUser = true;
+ }
+
+ if (needToPersistStateForUser) {
+ mUserPersistenceHandler.postPersistUserState(userId);
+ }
+ }
+ }
+
+ private static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
+ @Override
+ protected @NonNull Set<AssociationInfo> create(int userId) {
+ return new ArraySet<>();
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index 36393894f727..4b56c1b28036 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -103,7 +103,7 @@ import java.util.concurrent.ConcurrentMap;
* Since Android T the data is stored to "companion_device_manager.xml" file in
* {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}.
*
- * See {@link #getBaseStorageFileForUser(int) getBaseStorageFileForUser()}
+ * See {@link DataStoreUtils#getBaseStorageFileForUser(int, String)}
*
* <p>
* Since Android T the data is stored using the v1 schema.
@@ -120,7 +120,7 @@ import java.util.concurrent.ConcurrentMap;
* <li> {@link #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) readPreviouslyUsedIdsV1()}
* </ul>
*
- * The following snippet is a sample of a file that is using v0 schema.
+ * The following snippet is a sample of a file that is using v1 schema.
* <pre>{@code
* <state persistence-version="1">
* <associations>
@@ -130,6 +130,8 @@ import java.util.concurrent.ConcurrentMap;
* mac_address="AA:BB:CC:DD:EE:00"
* self_managed="false"
* notify_device_nearby="false"
+ * revoked="false"
+ * last_time_connected="1634641160229"
* time_approved="1634389553216"/>
*
* <association
@@ -139,6 +141,8 @@ import java.util.concurrent.ConcurrentMap;
* display_name="Jhon's Chromebook"
* self_managed="true"
* notify_device_nearby="false"
+ * revoked="false"
+ * last_time_connected="1634641160229"
* time_approved="1634641160229"/>
* </associations>
*
@@ -178,6 +182,7 @@ final class PersistentDataStore {
private static final String XML_ATTR_PROFILE = "profile";
private static final String XML_ATTR_SELF_MANAGED = "self_managed";
private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
+ private static final String XML_ATTR_REVOKED = "revoked";
private static final String XML_ATTR_TIME_APPROVED = "time_approved";
private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
@@ -415,7 +420,8 @@ final class PersistentDataStore {
out.add(new AssociationInfo(associationId, userId, appPackage,
MacAddress.fromString(deviceAddress), null, profile,
- /* managedByCompanionApp */false, notify, timeApproved, Long.MAX_VALUE));
+ /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved,
+ Long.MAX_VALUE));
}
private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -444,13 +450,14 @@ final class PersistentDataStore {
final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME);
final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED);
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
+ final boolean revoked = readBooleanAttribute(parser, XML_ATTR_REVOKED, false);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
final long lastTimeConnected = readLongAttribute(
parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
- appPackage, macAddress, displayName, profile, selfManaged, notify, timeApproved,
- lastTimeConnected);
+ appPackage, macAddress, displayName, profile, selfManaged, notify, revoked,
+ timeApproved, lastTimeConnected);
if (associationInfo != null) {
out.add(associationInfo);
}
@@ -503,6 +510,8 @@ final class PersistentDataStore {
writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
writeBooleanAttribute(
serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby());
+ writeBooleanAttribute(
+ serializer, XML_ATTR_REVOKED, a.isRevoked());
writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs());
writeLongAttribute(
serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs());
@@ -544,11 +553,12 @@ final class PersistentDataStore {
private static AssociationInfo createAssociationInfoNoThrow(int associationId,
@UserIdInt int userId, @NonNull String appPackage, @Nullable MacAddress macAddress,
@Nullable CharSequence displayName, @Nullable String profile, boolean selfManaged,
- boolean notify, long timeApproved, long lastTimeConnected) {
+ boolean notify, boolean revoked, long timeApproved, long lastTimeConnected) {
AssociationInfo associationInfo = null;
try {
associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress,
- displayName, profile, selfManaged, notify, timeApproved, lastTimeConnected);
+ displayName, profile, selfManaged, notify, revoked, timeApproved,
+ lastTimeConnected);
} catch (Exception e) {
if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
}
diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java
index 35488a80b78b..0fff3f488562 100644
--- a/services/companion/java/com/android/server/companion/RolesUtils.java
+++ b/services/companion/java/com/android/server/companion/RolesUtils.java
@@ -85,6 +85,8 @@ final class RolesUtils {
final int userId = associationInfo.getUserId();
final UserHandle userHandle = UserHandle.of(userId);
+ Slog.i(TAG, "Removing CDM role holder, role=" + deviceProfile
+ + ", package=u" + userId + "\\" + packageName);
roleManager.removeRoleHolderAsUser(deviceProfile, packageName,
MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
success -> {
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 297d28dadde3..56990eda3e78 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -19,7 +19,7 @@ package com.android.server.adb;
import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
import android.annotation.NonNull;
-import android.annotation.TestApi;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -102,11 +102,26 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi
+ * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keys
* that are authorized to connect to the ADB service itself.
+ *
+ * <p>The AdbDebuggingManager controls two files:
+ * <ol>
+ * <li>adb_keys
+ * <li>adb_temp_keys.xml
+ * </ol>
+ *
+ * <p>The ADB Daemon (adbd) reads <em>only</em> the adb_keys file for authorization. Public keys
+ * from registered hosts are stored in adb_keys, one entry per line.
+ *
+ * <p>AdbDebuggingManager also keeps adb_temp_keys.xml, which is used for two things
+ * <ol>
+ * <li>Removing unused keys from the adb_keys file
+ * <li>Managing authorized WiFi access points for ADB over WiFi
+ * </ol>
*/
public class AdbDebuggingManager {
- private static final String TAG = "AdbDebuggingManager";
+ private static final String TAG = AdbDebuggingManager.class.getSimpleName();
private static final boolean DEBUG = false;
private static final boolean MDNS_DEBUG = false;
@@ -118,18 +133,20 @@ public class AdbDebuggingManager {
// as a subsequent connection occurs within the allowed duration.
private static final String ADB_TEMP_KEYS_FILE = "adb_temp_keys.xml";
private static final int BUFFER_SIZE = 65536;
+ private static final Ticker SYSTEM_TICKER = () -> System.currentTimeMillis();
private final Context mContext;
private final ContentResolver mContentResolver;
- private final Handler mHandler;
- private AdbDebuggingThread mThread;
+ @VisibleForTesting final AdbDebuggingHandler mHandler;
+ @Nullable private AdbDebuggingThread mThread;
private boolean mAdbUsbEnabled = false;
private boolean mAdbWifiEnabled = false;
private String mFingerprints;
// A key can be used more than once (e.g. USB, wifi), so need to keep a refcount
- private final Map<String, Integer> mConnectedKeys;
- private String mConfirmComponent;
- private final File mTestUserKeyFile;
+ private final Map<String, Integer> mConnectedKeys = new HashMap<>();
+ private final String mConfirmComponent;
+ @Nullable private final File mUserKeyFile;
+ @Nullable private final File mTempKeysFile;
private static final String WIFI_PERSISTENT_CONFIG_PROPERTY =
"persist.adb.tls_server.enable";
@@ -138,37 +155,44 @@ public class AdbDebuggingManager {
private static final int PAIRING_CODE_LENGTH = 6;
private PairingThread mPairingThread = null;
// A list of keys connected via wifi
- private final Set<String> mWifiConnectedKeys;
+ private final Set<String> mWifiConnectedKeys = new HashSet<>();
// The current info of the adbwifi connection.
- private AdbConnectionInfo mAdbConnectionInfo;
+ private AdbConnectionInfo mAdbConnectionInfo = new AdbConnectionInfo();
// Polls for a tls port property when adb wifi is enabled
private AdbConnectionPortPoller mConnectionPortPoller;
private final PortListenerImpl mPortListener = new PortListenerImpl();
+ private final Ticker mTicker;
public AdbDebuggingManager(Context context) {
- mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
- mContext = context;
- mContentResolver = mContext.getContentResolver();
- mTestUserKeyFile = null;
- mConnectedKeys = new HashMap<String, Integer>();
- mWifiConnectedKeys = new HashSet<String>();
- mAdbConnectionInfo = new AdbConnectionInfo();
+ this(
+ context,
+ /* confirmComponent= */ null,
+ getAdbFile(ADB_KEYS_FILE),
+ getAdbFile(ADB_TEMP_KEYS_FILE),
+ /* adbDebuggingThread= */ null,
+ SYSTEM_TICKER);
}
/**
* Constructor that accepts the component to be invoked to confirm if the user wants to allow
* an adb connection from the key.
*/
- @TestApi
- protected AdbDebuggingManager(Context context, String confirmComponent, File testUserKeyFile) {
- mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
+ @VisibleForTesting
+ AdbDebuggingManager(
+ Context context,
+ String confirmComponent,
+ File testUserKeyFile,
+ File tempKeysFile,
+ AdbDebuggingThread adbDebuggingThread,
+ Ticker ticker) {
mContext = context;
mContentResolver = mContext.getContentResolver();
mConfirmComponent = confirmComponent;
- mTestUserKeyFile = testUserKeyFile;
- mConnectedKeys = new HashMap<String, Integer>();
- mWifiConnectedKeys = new HashSet<String>();
- mAdbConnectionInfo = new AdbConnectionInfo();
+ mUserKeyFile = testUserKeyFile;
+ mTempKeysFile = tempKeysFile;
+ mThread = adbDebuggingThread;
+ mTicker = ticker;
+ mHandler = new AdbDebuggingHandler(FgThread.get().getLooper(), mThread);
}
static void sendBroadcastWithDebugPermission(@NonNull Context context, @NonNull Intent intent,
@@ -189,8 +213,7 @@ public class AdbDebuggingManager {
// consisting of only letters, digits, and hyphens, must begin and end
// with a letter or digit, must not contain consecutive hyphens, and
// must contain at least one letter.
- @VisibleForTesting
- static final String SERVICE_PROTOCOL = "adb-tls-pairing";
+ @VisibleForTesting static final String SERVICE_PROTOCOL = "adb-tls-pairing";
private final String mServiceType = String.format("_%s._tcp.", SERVICE_PROTOCOL);
private int mPort;
@@ -352,16 +375,24 @@ public class AdbDebuggingManager {
}
}
- class AdbDebuggingThread extends Thread {
+ @VisibleForTesting
+ static class AdbDebuggingThread extends Thread {
private boolean mStopped;
private LocalSocket mSocket;
private OutputStream mOutputStream;
private InputStream mInputStream;
+ private Handler mHandler;
+ @VisibleForTesting
AdbDebuggingThread() {
super(TAG);
}
+ @VisibleForTesting
+ void setHandler(Handler handler) {
+ mHandler = handler;
+ }
+
@Override
public void run() {
if (DEBUG) Slog.d(TAG, "Entering thread");
@@ -536,7 +567,7 @@ public class AdbDebuggingManager {
}
}
- class AdbConnectionInfo {
+ private static class AdbConnectionInfo {
private String mBssid;
private String mSsid;
private int mPort;
@@ -743,11 +774,14 @@ public class AdbDebuggingManager {
// Notification when adbd socket is disconnected.
static final int MSG_ADBD_SOCKET_DISCONNECTED = 27;
+ // === Messages from other parts of the system
+ private static final int MESSAGE_KEY_FILES_UPDATED = 28;
+
// === Messages we can send to adbd ===========
static final String MSG_DISCONNECT_DEVICE = "DD";
static final String MSG_DISABLE_ADBDWIFI = "DA";
- private AdbKeyStore mAdbKeyStore;
+ @Nullable @VisibleForTesting AdbKeyStore mAdbKeyStore;
// Usb, Wi-Fi transports can be enabled together or separately, so don't break the framework
// connection unless all transport types are disconnected.
@@ -762,19 +796,19 @@ public class AdbDebuggingManager {
}
};
- AdbDebuggingHandler(Looper looper) {
+ /** Constructor that accepts the AdbDebuggingThread to which responses should be sent. */
+ @VisibleForTesting
+ AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread) {
super(looper);
+ mThread = thread;
}
- /**
- * Constructor that accepts the AdbDebuggingThread to which responses should be sent
- * and the AdbKeyStore to be used to store the temporary grants.
- */
- @TestApi
- AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread, AdbKeyStore adbKeyStore) {
- super(looper);
- mThread = thread;
- mAdbKeyStore = adbKeyStore;
+ /** Initialize the AdbKeyStore so tests can grab mAdbKeyStore immediately. */
+ @VisibleForTesting
+ void initKeyStore() {
+ if (mAdbKeyStore == null) {
+ mAdbKeyStore = new AdbKeyStore();
+ }
}
// Show when at least one device is connected.
@@ -805,6 +839,7 @@ public class AdbDebuggingManager {
registerForAuthTimeChanges();
mThread = new AdbDebuggingThread();
+ mThread.setHandler(mHandler);
mThread.start();
mAdbKeyStore.updateKeyStore();
@@ -825,8 +860,7 @@ public class AdbDebuggingManager {
if (!mConnectedKeys.isEmpty()) {
for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) {
- mAdbKeyStore.setLastConnectionTime(entry.getKey(),
- System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(entry.getKey(), mTicker.currentTimeMillis());
}
sendPersistKeyStoreMessage();
mConnectedKeys.clear();
@@ -836,9 +870,7 @@ public class AdbDebuggingManager {
}
public void handleMessage(Message msg) {
- if (mAdbKeyStore == null) {
- mAdbKeyStore = new AdbKeyStore();
- }
+ initKeyStore();
switch (msg.what) {
case MESSAGE_ADB_ENABLED:
@@ -873,7 +905,7 @@ public class AdbDebuggingManager {
if (!mConnectedKeys.containsKey(key)) {
mConnectedKeys.put(key, 1);
}
- mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(key, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
}
@@ -920,9 +952,7 @@ public class AdbDebuggingManager {
mConnectedKeys.clear();
// If the key store has not yet been instantiated then do so now; this avoids
// the unnecessary creation of the key store when adb is not enabled.
- if (mAdbKeyStore == null) {
- mAdbKeyStore = new AdbKeyStore();
- }
+ initKeyStore();
mWifiConnectedKeys.clear();
mAdbKeyStore.deleteKeyStore();
cancelJobToUpdateAdbKeyStore();
@@ -937,7 +967,8 @@ public class AdbDebuggingManager {
alwaysAllow = true;
int refcount = mConnectedKeys.get(key) - 1;
if (refcount == 0) {
- mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(
+ key, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
mConnectedKeys.remove(key);
@@ -963,7 +994,7 @@ public class AdbDebuggingManager {
if (!mConnectedKeys.isEmpty()) {
for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) {
mAdbKeyStore.setLastConnectionTime(entry.getKey(),
- System.currentTimeMillis());
+ mTicker.currentTimeMillis());
}
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
@@ -984,7 +1015,7 @@ public class AdbDebuggingManager {
} else {
mConnectedKeys.put(key, mConnectedKeys.get(key) + 1);
}
- mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(key, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
logAdbConnectionChanged(key, AdbProtoEnums.AUTOMATICALLY_ALLOWED, true);
@@ -1206,6 +1237,10 @@ public class AdbDebuggingManager {
}
break;
}
+ case MESSAGE_KEY_FILES_UPDATED: {
+ mAdbKeyStore.reloadKeyMap();
+ break;
+ }
}
}
@@ -1377,8 +1412,7 @@ public class AdbDebuggingManager {
AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent,
UserHandle.ALL);
// Add the key into the keystore
- mAdbKeyStore.setLastConnectionTime(publicKey,
- System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(publicKey, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
}
@@ -1449,19 +1483,13 @@ public class AdbDebuggingManager {
extras.add(new AbstractMap.SimpleEntry<String, String>("ssid", ssid));
extras.add(new AbstractMap.SimpleEntry<String, String>("bssid", bssid));
int currentUserId = ActivityManager.getCurrentUser();
- UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
- String componentString;
- if (userInfo.isAdmin()) {
- componentString = Resources.getSystem().getString(
- com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent);
- } else {
- componentString = Resources.getSystem().getString(
- com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent);
- }
+ String componentString =
+ Resources.getSystem().getString(
+ R.string.config_customAdbWifiNetworkConfirmationComponent);
ComponentName componentName = ComponentName.unflattenFromString(componentString);
+ UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
if (startConfirmationActivity(componentName, userInfo.getUserHandle(), extras)
- || startConfirmationService(componentName, userInfo.getUserHandle(),
- extras)) {
+ || startConfirmationService(componentName, userInfo.getUserHandle(), extras)) {
return;
}
Slog.e(TAG, "Unable to start customAdbWifiNetworkConfirmation[SecondaryUser]Component "
@@ -1543,7 +1571,7 @@ public class AdbDebuggingManager {
/**
* Returns a new File with the specified name in the adb directory.
*/
- private File getAdbFile(String fileName) {
+ private static File getAdbFile(String fileName) {
File dataDir = Environment.getDataDirectory();
File adbDir = new File(dataDir, ADB_DIRECTORY);
@@ -1556,66 +1584,38 @@ public class AdbDebuggingManager {
}
File getAdbTempKeysFile() {
- return getAdbFile(ADB_TEMP_KEYS_FILE);
+ return mTempKeysFile;
}
File getUserKeyFile() {
- return mTestUserKeyFile == null ? getAdbFile(ADB_KEYS_FILE) : mTestUserKeyFile;
+ return mUserKeyFile;
}
- private void writeKey(String key) {
- try {
- File keyFile = getUserKeyFile();
-
- if (keyFile == null) {
- return;
- }
-
- FileOutputStream fo = new FileOutputStream(keyFile, true);
- fo.write(key.getBytes());
- fo.write('\n');
- fo.close();
-
- FileUtils.setPermissions(keyFile.toString(),
- FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
- } catch (IOException ex) {
- Slog.e(TAG, "Error writing key:" + ex);
+ private void writeKeys(Iterable<String> keys) {
+ if (mUserKeyFile == null) {
+ return;
}
- }
- private void writeKeys(Iterable<String> keys) {
- AtomicFile atomicKeyFile = null;
+ AtomicFile atomicKeyFile = new AtomicFile(mUserKeyFile);
+ // Note: Do not use a try-with-resources with the FileOutputStream, because AtomicFile
+ // requires that it's cleaned up with AtomicFile.failWrite();
FileOutputStream fo = null;
try {
- File keyFile = getUserKeyFile();
-
- if (keyFile == null) {
- return;
- }
-
- atomicKeyFile = new AtomicFile(keyFile);
fo = atomicKeyFile.startWrite();
for (String key : keys) {
fo.write(key.getBytes());
fo.write('\n');
}
atomicKeyFile.finishWrite(fo);
-
- FileUtils.setPermissions(keyFile.toString(),
- FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
} catch (IOException ex) {
Slog.e(TAG, "Error writing keys: " + ex);
- if (atomicKeyFile != null) {
- atomicKeyFile.failWrite(fo);
- }
+ atomicKeyFile.failWrite(fo);
+ return;
}
- }
- private void deleteKeyFile() {
- File keyFile = getUserKeyFile();
- if (keyFile != null) {
- keyFile.delete();
- }
+ FileUtils.setPermissions(
+ mUserKeyFile.toString(),
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
}
/**
@@ -1745,6 +1745,13 @@ public class AdbDebuggingManager {
}
/**
+ * Notify that they key files were updated so the AdbKeyManager reloads the keys.
+ */
+ public void notifyKeyFilesUpdated() {
+ mHandler.sendEmptyMessage(AdbDebuggingHandler.MESSAGE_KEY_FILES_UPDATED);
+ }
+
+ /**
* Sends a message to the handler to persist the keystore.
*/
private void sendPersistKeyStoreMessage() {
@@ -1778,7 +1785,7 @@ public class AdbDebuggingManager {
try {
dump.write("keystore", AdbDebuggingManagerProto.KEYSTORE,
- FileUtils.readTextFile(getAdbTempKeysFile(), 0, null));
+ FileUtils.readTextFile(mTempKeysFile, 0, null));
} catch (IOException e) {
Slog.i(TAG, "Cannot read keystore: ", e);
}
@@ -1792,12 +1799,12 @@ public class AdbDebuggingManager {
* ADB_ALLOWED_CONNECTION_TIME setting.
*/
class AdbKeyStore {
- private Map<String, Long> mKeyMap;
- private Set<String> mSystemKeys;
- private File mKeyFile;
private AtomicFile mAtomicKeyFile;
- private List<String> mTrustedNetworks;
+ private final Set<String> mSystemKeys;
+ private final Map<String, Long> mKeyMap = new HashMap<>();
+ private final List<String> mTrustedNetworks = new ArrayList<>();
+
private static final int KEYSTORE_VERSION = 1;
private static final int MAX_SUPPORTED_KEYSTORE_VERSION = 1;
private static final String XML_KEYSTORE_START_TAG = "keyStore";
@@ -1819,26 +1826,22 @@ public class AdbDebuggingManager {
public static final long NO_PREVIOUS_CONNECTION = 0;
/**
- * Constructor that uses the default location for the persistent adb keystore.
+ * Create an AdbKeyStore instance.
+ *
+ * <p>Upon creation, we parse {@link #mTempKeysFile} to determine authorized WiFi APs and
+ * retrieve the map of stored ADB keys and their last connected times. After that, we read
+ * the {@link #mUserKeyFile}, and any keys that exist in that file that do not exist in the
+ * map are added to the map (for backwards compatibility).
*/
AdbKeyStore() {
- init();
- }
-
- /**
- * Constructor that uses the specified file as the location for the persistent adb keystore.
- */
- AdbKeyStore(File keyFile) {
- mKeyFile = keyFile;
- init();
- }
-
- private void init() {
initKeyFile();
- mKeyMap = getKeyMap();
- mTrustedNetworks = getTrustedNetworks();
+ readTempKeysFile();
mSystemKeys = getSystemKeysFromFile(SYSTEM_KEY_FILE);
- addUserKeysToKeyStore();
+ addExistingUserKeysToKeyStore();
+ }
+
+ public void reloadKeyMap() {
+ readTempKeysFile();
}
public void addTrustedNetwork(String bssid) {
@@ -1877,7 +1880,6 @@ public class AdbDebuggingManager {
public void removeKey(String key) {
if (mKeyMap.containsKey(key)) {
mKeyMap.remove(key);
- writeKeys(mKeyMap.keySet());
sendPersistKeyStoreMessage();
}
}
@@ -1886,12 +1888,9 @@ public class AdbDebuggingManager {
* Initializes the key file that will be used to persist the adb grants.
*/
private void initKeyFile() {
- if (mKeyFile == null) {
- mKeyFile = getAdbTempKeysFile();
- }
- // getAdbTempKeysFile can return null if the adb file cannot be obtained
- if (mKeyFile != null) {
- mAtomicKeyFile = new AtomicFile(mKeyFile);
+ // mTempKeysFile can be null if the adb file cannot be obtained
+ if (mTempKeysFile != null) {
+ mAtomicKeyFile = new AtomicFile(mTempKeysFile);
}
}
@@ -1932,201 +1931,108 @@ public class AdbDebuggingManager {
}
/**
- * Returns the key map with the keys and last connection times from the key file.
+ * Update the key map and the trusted networks list with values parsed from the temp keys
+ * file.
*/
- private Map<String, Long> getKeyMap() {
- Map<String, Long> keyMap = new HashMap<String, Long>();
- // if the AtomicFile could not be instantiated before attempt again; if it still fails
- // return an empty key map.
+ private void readTempKeysFile() {
+ mKeyMap.clear();
+ mTrustedNetworks.clear();
if (mAtomicKeyFile == null) {
initKeyFile();
if (mAtomicKeyFile == null) {
- Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
- return keyMap;
+ Slog.e(
+ TAG,
+ "Unable to obtain the key file, " + mTempKeysFile + ", for reading");
+ return;
}
}
if (!mAtomicKeyFile.exists()) {
- return keyMap;
+ return;
}
try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
- TypedXmlPullParser parser = Xml.resolvePullParser(keyStream);
- // Check for supported keystore version.
- XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
- if (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) {
- Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag="
- + tagName);
- return keyMap;
- }
+ TypedXmlPullParser parser;
+ try {
+ parser = Xml.resolvePullParser(keyStream);
+ XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
+
int keystoreVersion = parser.getAttributeInt(null, XML_ATTRIBUTE_VERSION);
if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) {
Slog.e(TAG, "Keystore version=" + keystoreVersion
+ " not supported (max_supported="
+ MAX_SUPPORTED_KEYSTORE_VERSION + ")");
- return keyMap;
- }
- }
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null) {
- break;
- } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
- long connectionTime;
- try {
- connectionTime = parser.getAttributeLong(null,
- XML_ATTRIBUTE_LAST_CONNECTION);
- } catch (XmlPullParserException e) {
- Slog.e(TAG,
- "Caught a NumberFormatException parsing the last connection time: "
- + e);
- XmlUtils.skipCurrentTag(parser);
- continue;
+ return;
}
- keyMap.put(key, connectionTime);
+ } catch (XmlPullParserException e) {
+ // This could be because the XML document doesn't start with
+ // XML_KEYSTORE_START_TAG. Try again, instead just starting the document with
+ // the adbKey tag (the old format).
+ parser = Xml.resolvePullParser(keyStream);
}
+ readKeyStoreContents(parser);
} catch (IOException e) {
Slog.e(TAG, "Caught an IOException parsing the XML key file: ", e);
} catch (XmlPullParserException e) {
- Slog.w(TAG, "Caught XmlPullParserException parsing the XML key file: ", e);
- // The file could be written in a format prior to introducing keystore tag.
- return getKeyMapBeforeKeystoreVersion();
+ Slog.e(TAG, "Caught XmlPullParserException parsing the XML key file: ", e);
+ }
+ }
+
+ private void readKeyStoreContents(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ // This parser is very forgiving. For backwards-compatibility, we simply iterate through
+ // all the tags in the file, skipping over anything that's not an <adbKey> tag or a
+ // <wifiAP> tag. Invalid tags (such as ones that don't have a valid "lastConnection"
+ // attribute) are simply ignored.
+ while ((parser.next()) != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (XML_TAG_ADB_KEY.equals(tagName)) {
+ addAdbKeyToKeyMap(parser);
+ } else if (XML_TAG_WIFI_ACCESS_POINT.equals(tagName)) {
+ addTrustedNetworkToTrustedNetworks(parser);
+ } else {
+ Slog.w(TAG, "Ignoring tag '" + tagName + "'. Not recognized.");
+ }
+ XmlUtils.skipCurrentTag(parser);
}
- return keyMap;
}
-
- /**
- * Returns the key map with the keys and last connection times from the key file.
- * This implementation was prior to adding the XML_KEYSTORE_START_TAG.
- */
- private Map<String, Long> getKeyMapBeforeKeystoreVersion() {
- Map<String, Long> keyMap = new HashMap<String, Long>();
- // if the AtomicFile could not be instantiated before attempt again; if it still fails
- // return an empty key map.
- if (mAtomicKeyFile == null) {
- initKeyFile();
- if (mAtomicKeyFile == null) {
- Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
- return keyMap;
- }
- }
- if (!mAtomicKeyFile.exists()) {
- return keyMap;
- }
- try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
- TypedXmlPullParser parser = Xml.resolvePullParser(keyStream);
- XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY);
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null) {
- break;
- } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
- long connectionTime;
- try {
- connectionTime = parser.getAttributeLong(null,
- XML_ATTRIBUTE_LAST_CONNECTION);
- } catch (XmlPullParserException e) {
- Slog.e(TAG,
- "Caught a NumberFormatException parsing the last connection time: "
- + e);
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- keyMap.put(key, connectionTime);
- }
- } catch (IOException | XmlPullParserException e) {
- Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
+ private void addAdbKeyToKeyMap(TypedXmlPullParser parser) {
+ String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
+ try {
+ long connectionTime =
+ parser.getAttributeLong(null, XML_ATTRIBUTE_LAST_CONNECTION);
+ mKeyMap.put(key, connectionTime);
+ } catch (XmlPullParserException e) {
+ Slog.e(TAG, "Error reading adbKey attributes", e);
}
- return keyMap;
}
- /**
- * Returns the map of trusted networks from the keystore file.
- *
- * This was implemented in keystore version 1.
- */
- private List<String> getTrustedNetworks() {
- List<String> trustedNetworks = new ArrayList<String>();
- // if the AtomicFile could not be instantiated before attempt again; if it still fails
- // return an empty key map.
- if (mAtomicKeyFile == null) {
- initKeyFile();
- if (mAtomicKeyFile == null) {
- Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
- return trustedNetworks;
- }
- }
- if (!mAtomicKeyFile.exists()) {
- return trustedNetworks;
- }
- try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
- TypedXmlPullParser parser = Xml.resolvePullParser(keyStream);
- // Check for supported keystore version.
- XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
- if (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) {
- Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag="
- + tagName);
- return trustedNetworks;
- }
- int keystoreVersion = parser.getAttributeInt(null, XML_ATTRIBUTE_VERSION);
- if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) {
- Slog.e(TAG, "Keystore version=" + keystoreVersion
- + " not supported (max_supported="
- + MAX_SUPPORTED_KEYSTORE_VERSION);
- return trustedNetworks;
- }
- }
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null) {
- break;
- } else if (!tagName.equals(XML_TAG_WIFI_ACCESS_POINT)) {
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID);
- trustedNetworks.add(bssid);
- }
- } catch (IOException | XmlPullParserException | NumberFormatException e) {
- Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
- }
- return trustedNetworks;
+ private void addTrustedNetworkToTrustedNetworks(TypedXmlPullParser parser) {
+ String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID);
+ mTrustedNetworks.add(bssid);
}
/**
* Updates the keystore with keys that were previously set to be always allowed before the
* connection time of keys was tracked.
*/
- private void addUserKeysToKeyStore() {
- File userKeyFile = getUserKeyFile();
+ private void addExistingUserKeysToKeyStore() {
+ if (mUserKeyFile == null || !mUserKeyFile.exists()) {
+ return;
+ }
boolean mapUpdated = false;
- if (userKeyFile != null && userKeyFile.exists()) {
- try (BufferedReader in = new BufferedReader(new FileReader(userKeyFile))) {
- long time = System.currentTimeMillis();
- String key;
- while ((key = in.readLine()) != null) {
- // if the keystore does not contain the key from the user key file then add
- // it to the Map with the current system time to prevent it from expiring
- // immediately if the user is actively using this key.
- if (!mKeyMap.containsKey(key)) {
- mKeyMap.put(key, time);
- mapUpdated = true;
- }
+ try (BufferedReader in = new BufferedReader(new FileReader(mUserKeyFile))) {
+ String key;
+ while ((key = in.readLine()) != null) {
+ // if the keystore does not contain the key from the user key file then add
+ // it to the Map with the current system time to prevent it from expiring
+ // immediately if the user is actively using this key.
+ if (!mKeyMap.containsKey(key)) {
+ mKeyMap.put(key, mTicker.currentTimeMillis());
+ mapUpdated = true;
}
- } catch (IOException e) {
- Slog.e(TAG, "Caught an exception reading " + userKeyFile + ": " + e);
}
+ } catch (IOException e) {
+ Slog.e(TAG, "Caught an exception reading " + mUserKeyFile + ": " + e);
}
if (mapUpdated) {
sendPersistKeyStoreMessage();
@@ -2147,7 +2053,9 @@ public class AdbDebuggingManager {
if (mAtomicKeyFile == null) {
initKeyFile();
if (mAtomicKeyFile == null) {
- Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for writing");
+ Slog.e(
+ TAG,
+ "Unable to obtain the key file, " + mTempKeysFile + ", for writing");
return;
}
}
@@ -2178,17 +2086,21 @@ public class AdbDebuggingManager {
Slog.e(TAG, "Caught an exception writing the key map: ", e);
mAtomicKeyFile.failWrite(keyStream);
}
+ writeKeys(mKeyMap.keySet());
}
private boolean filterOutOldKeys() {
- boolean keysDeleted = false;
long allowedTime = getAllowedConnectionTime();
- long systemTime = System.currentTimeMillis();
+ if (allowedTime == 0) {
+ return false;
+ }
+ boolean keysDeleted = false;
+ long systemTime = mTicker.currentTimeMillis();
Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator();
while (keyMapIterator.hasNext()) {
Map.Entry<String, Long> keyEntry = keyMapIterator.next();
long connectionTime = keyEntry.getValue();
- if (allowedTime != 0 && systemTime > (connectionTime + allowedTime)) {
+ if (systemTime > (connectionTime + allowedTime)) {
keyMapIterator.remove();
keysDeleted = true;
}
@@ -2212,7 +2124,7 @@ public class AdbDebuggingManager {
if (allowedTime == 0) {
return minExpiration;
}
- long systemTime = System.currentTimeMillis();
+ long systemTime = mTicker.currentTimeMillis();
Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator();
while (keyMapIterator.hasNext()) {
Map.Entry<String, Long> keyEntry = keyMapIterator.next();
@@ -2233,7 +2145,9 @@ public class AdbDebuggingManager {
public void deleteKeyStore() {
mKeyMap.clear();
mTrustedNetworks.clear();
- deleteKeyFile();
+ if (mUserKeyFile != null) {
+ mUserKeyFile.delete();
+ }
if (mAtomicKeyFile == null) {
return;
}
@@ -2260,7 +2174,8 @@ public class AdbDebuggingManager {
* is set to true the time will be set even if it is older than the previously written
* connection time.
*/
- public void setLastConnectionTime(String key, long connectionTime, boolean force) {
+ @VisibleForTesting
+ void setLastConnectionTime(String key, long connectionTime, boolean force) {
// Do not set the connection time to a value that is earlier than what was previously
// stored as the last connection time unless force is set.
if (mKeyMap.containsKey(key) && mKeyMap.get(key) >= connectionTime && !force) {
@@ -2271,11 +2186,6 @@ public class AdbDebuggingManager {
if (mSystemKeys.contains(key)) {
return;
}
- // if this is the first time the key is being added then write it to the key file as
- // well.
- if (!mKeyMap.containsKey(key)) {
- writeKey(key);
- }
mKeyMap.put(key, connectionTime);
}
@@ -2307,12 +2217,8 @@ public class AdbDebuggingManager {
long allowedConnectionTime = getAllowedConnectionTime();
// if the allowed connection time is 0 then revert to the previous behavior of always
// allowing previously granted adb grants.
- if (allowedConnectionTime == 0 || (System.currentTimeMillis() < (lastConnectionTime
- + allowedConnectionTime))) {
- return true;
- } else {
- return false;
- }
+ return allowedConnectionTime == 0
+ || (mTicker.currentTimeMillis() < (lastConnectionTime + allowedConnectionTime));
}
/**
@@ -2324,4 +2230,15 @@ public class AdbDebuggingManager {
return mTrustedNetworks.contains(bssid);
}
}
+
+ /**
+ * A Guava-like interface for getting the current system time.
+ *
+ * This allows us to swap a fake ticker in for testing to reduce "Thread.sleep()" calls and test
+ * for exact expected times instead of random ones.
+ */
+ @VisibleForTesting
+ interface Ticker {
+ long currentTimeMillis();
+ }
}
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index 5d0c732d5f48..55d8dba69626 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -152,6 +152,14 @@ public class AdbService extends IAdbManager.Stub {
}
@Override
+ public void notifyKeyFilesUpdated() {
+ if (mDebuggingManager == null) {
+ return;
+ }
+ mDebuggingManager.notifyKeyFilesUpdated();
+ }
+
+ @Override
public void startAdbdForTransport(byte transportType) {
FgThread.getHandler().sendMessage(obtainMessage(
AdbService::setAdbdEnabledForTransport, AdbService.this, true, transportType));
diff --git a/services/core/java/com/android/server/am/TraceErrorLogger.java b/services/core/java/com/android/server/am/TraceErrorLogger.java
index 29a9b5c501c4..ec0587f721d4 100644
--- a/services/core/java/com/android/server/am/TraceErrorLogger.java
+++ b/services/core/java/com/android/server/am/TraceErrorLogger.java
@@ -16,7 +16,6 @@
package com.android.server.am;
-import android.os.Build;
import android.os.Trace;
import java.util.UUID;
@@ -31,7 +30,7 @@ public class TraceErrorLogger {
private static final int PLACEHOLDER_VALUE = 1;
public boolean isAddErrorIdEnabled() {
- return Build.IS_DEBUGGABLE;
+ return true;
}
/**
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 43d77ab32371..0040ea9215b3 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -365,6 +365,8 @@ public class AudioService extends IAudioService.Stub
private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45;
private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46;
private static final int MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR = 47;
+ private static final int MSG_ROTATION_UPDATE = 48;
+ private static final int MSG_FOLD_UPDATE = 49;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -1251,7 +1253,9 @@ public class AudioService extends IAudioService.Stub
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
if (mMonitorRotation) {
- RotationHelper.init(mContext, mAudioHandler);
+ RotationHelper.init(mContext, mAudioHandler,
+ rotationParam -> onRotationUpdate(rotationParam),
+ foldParam -> onFoldUpdate(foldParam));
}
intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
@@ -1398,6 +1402,20 @@ public class AudioService extends IAudioService.Stub
}
//-----------------------------------------------------------------
+ // rotation/fold updates coming from RotationHelper
+ void onRotationUpdate(String rotationParameter) {
+ // use REPLACE as only the last rotation matters
+ sendMsg(mAudioHandler, MSG_ROTATION_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0,
+ /*obj*/ rotationParameter, /*delay*/ 0);
+ }
+
+ void onFoldUpdate(String foldParameter) {
+ // use REPLACE as only the last fold state matters
+ sendMsg(mAudioHandler, MSG_FOLD_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0,
+ /*obj*/ foldParameter, /*delay*/ 0);
+ }
+
+ //-----------------------------------------------------------------
// monitoring requests for volume range initialization
@Override // AudioSystemAdapter.OnVolRangeInitRequestListener
public void onVolumeRangeInitRequestFromNative() {
@@ -8327,6 +8345,16 @@ public class AudioService extends IAudioService.Stub
case MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR:
dispatchDeviceVolumeBehavior((AudioDeviceAttributes) msg.obj, msg.arg1);
break;
+
+ case MSG_ROTATION_UPDATE:
+ // rotation parameter format: "rotation=x" where x is one of 0, 90, 180, 270
+ mAudioSystem.setParameters((String) msg.obj);
+ break;
+
+ case MSG_FOLD_UPDATE:
+ // fold parameter format: "device_folded=x" where x is one of on, off
+ mAudioSystem.setParameters((String) msg.obj);
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
index eb8387fe85e5..5cdf58bdd62f 100644
--- a/services/core/java/com/android/server/audio/RotationHelper.java
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -21,13 +21,14 @@ import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
-import android.media.AudioSystem;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
+import java.util.function.Consumer;
+
/**
* Class to handle device rotation events for AudioService, and forward device rotation
* and folded state to the audio HALs through AudioSystem.
@@ -53,6 +54,10 @@ class RotationHelper {
private static AudioDisplayListener sDisplayListener;
private static FoldStateListener sFoldStateListener;
+ /** callback to send rotation updates to AudioSystem */
+ private static Consumer<String> sRotationUpdateCb;
+ /** callback to send folded state updates to AudioSystem */
+ private static Consumer<String> sFoldUpdateCb;
private static final Object sRotationLock = new Object();
private static final Object sFoldStateLock = new Object();
@@ -67,13 +72,16 @@ class RotationHelper {
* - sDisplayListener != null
* - sContext != null
*/
- static void init(Context context, Handler handler) {
+ static void init(Context context, Handler handler,
+ Consumer<String> rotationUpdateCb, Consumer<String> foldUpdateCb) {
if (context == null) {
throw new IllegalArgumentException("Invalid null context");
}
sContext = context;
sHandler = handler;
sDisplayListener = new AudioDisplayListener();
+ sRotationUpdateCb = rotationUpdateCb;
+ sFoldUpdateCb = foldUpdateCb;
enable();
}
@@ -115,21 +123,26 @@ class RotationHelper {
if (DEBUG_ROTATION) {
Log.i(TAG, "publishing device rotation =" + rotation + " (x90deg)");
}
+ String rotationParam;
switch (rotation) {
case Surface.ROTATION_0:
- AudioSystem.setParameters("rotation=0");
+ rotationParam = "rotation=0";
break;
case Surface.ROTATION_90:
- AudioSystem.setParameters("rotation=90");
+ rotationParam = "rotation=90";
break;
case Surface.ROTATION_180:
- AudioSystem.setParameters("rotation=180");
+ rotationParam = "rotation=180";
break;
case Surface.ROTATION_270:
- AudioSystem.setParameters("rotation=270");
+ rotationParam = "rotation=270";
break;
default:
Log.e(TAG, "Unknown device rotation");
+ rotationParam = null;
+ }
+ if (rotationParam != null) {
+ sRotationUpdateCb.accept(rotationParam);
}
}
@@ -140,11 +153,13 @@ class RotationHelper {
synchronized (sFoldStateLock) {
if (sDeviceFold != newFolded) {
sDeviceFold = newFolded;
+ String foldParam;
if (newFolded) {
- AudioSystem.setParameters("device_folded=on");
+ foldParam = "device_folded=on";
} else {
- AudioSystem.setParameters("device_folded=off");
+ foldParam = "device_folded=off";
}
+ sFoldUpdateCb.accept(foldParam);
}
}
}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 5b26672c7de2..dd44af1b68ee 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -280,18 +280,13 @@ public class SpatializerHelper {
}
// for both transaural / binaural, we are not forcing enablement as the init() method
// could have been called another time after boot in case of audioserver restart
- if (mTransauralSupported) {
- // not force-enabling as this device might already be in the device list
- addCompatibleAudioDevice(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
- false /*forceEnable*/);
- }
- if (mBinauralSupported) {
- // not force-enabling as this device might already be in the device list
- addCompatibleAudioDevice(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
- false /*forceEnable*/);
- }
+ addCompatibleAudioDevice(
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+ false /*forceEnable*/);
+ // not force-enabling as this device might already be in the device list
+ addCompatibleAudioDevice(
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
+ false /*forceEnable*/);
} catch (RemoteException e) {
resetCapabilities();
} finally {
@@ -497,10 +492,9 @@ public class SpatializerHelper {
synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
// build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices
ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>();
- for (SADeviceState dev : mSADevices) {
- if (dev.mEnabled) {
- compatList.add(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
- dev.mDeviceType, dev.mDeviceAddress == null ? "" : dev.mDeviceAddress));
+ for (SADeviceState deviceState : mSADevices) {
+ if (deviceState.mEnabled) {
+ compatList.add(deviceState.getAudioDeviceAttributes());
}
}
return compatList;
@@ -521,15 +515,15 @@ public class SpatializerHelper {
*/
private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada,
boolean forceEnable) {
+ if (!isDeviceCompatibleWithSpatializationModes(ada)) {
+ return;
+ }
loglogi("addCompatibleAudioDevice: dev=" + ada);
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
boolean isInList = false;
SADeviceState deviceUpdated = null; // non-null on update.
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
isInList = true;
if (forceEnable) {
deviceState.mEnabled = true;
@@ -539,11 +533,10 @@ public class SpatializerHelper {
}
}
if (!isInList) {
- final SADeviceState dev = new SADeviceState(deviceType,
- wireless ? ada.getAddress() : "");
- dev.mEnabled = true;
- mSADevices.add(dev);
- deviceUpdated = dev;
+ final SADeviceState deviceState = new SADeviceState(ada.getType(), ada.getAddress());
+ deviceState.mEnabled = true;
+ mSADevices.add(deviceState);
+ deviceUpdated = deviceState;
}
if (deviceUpdated != null) {
onRoutingUpdated();
@@ -574,13 +567,10 @@ public class SpatializerHelper {
synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
loglogi("removeCompatibleAudioDevice: dev=" + ada);
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
SADeviceState deviceUpdated = null; // non-null on update.
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
deviceState.mEnabled = false;
deviceUpdated = deviceState;
break;
@@ -602,10 +592,9 @@ public class SpatializerHelper {
// if not a wireless device, this value will be overwritten to map the type
// to TYPE_BUILTIN_SPEAKER or TYPE_WIRED_HEADPHONES
@AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
// if not a wireless device: find if media device is in the speaker, wired headphones
- if (!wireless) {
+ if (!isWireless(deviceType)) {
// is the device type capable of doing SA?
if (!mSACapableDeviceTypes.contains(deviceType)) {
Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada);
@@ -640,9 +629,7 @@ public class SpatializerHelper {
boolean enabled = false;
boolean available = false;
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
available = true;
enabled = deviceState.mEnabled;
break;
@@ -652,11 +639,12 @@ public class SpatializerHelper {
}
private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) {
+ if (!isDeviceCompatibleWithSpatializationModes(ada)) {
+ return;
+ }
boolean knownDevice = false;
for (SADeviceState deviceState : mSADevices) {
- // wireless device so always check address
- if (ada.getType() == deviceState.mDeviceType
- && ada.getAddress().equals(deviceState.mDeviceAddress)) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
knownDevice = true;
break;
}
@@ -704,13 +692,8 @@ public class SpatializerHelper {
if (ada.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
return false;
}
-
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
return true;
}
}
@@ -719,12 +702,19 @@ public class SpatializerHelper {
private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes,
@NonNull AudioFormat format, @NonNull AudioDeviceAttributes[] devices) {
- final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(devices[0].getType(),
+ if (isDeviceCompatibleWithSpatializationModes(devices[0])) {
+ return AudioSystem.canBeSpatialized(attributes, format, devices);
+ }
+ return false;
+ }
+
+ private boolean isDeviceCompatibleWithSpatializationModes(@NonNull AudioDeviceAttributes ada) {
+ final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
/*default when type not found*/ SpatializationMode.SPATIALIZER_BINAURAL);
if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
|| (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL
&& mTransauralSupported)) {
- return AudioSystem.canBeSpatialized(attributes, format, devices);
+ return true;
}
return false;
}
@@ -1089,13 +1079,8 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled
+ " for " + ada);
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
if (!deviceState.mHasHeadTracker) {
Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled
+ " device:" + ada + " on a device without headtracker");
@@ -1109,7 +1094,7 @@ public class SpatializerHelper {
}
}
// check current routing to see if it affects the headtracking mode
- if (ROUTING_DEVICES[0].getType() == deviceType
+ if (ROUTING_DEVICES[0].getType() == ada.getType()
&& ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
: Spatializer.HEAD_TRACKING_MODE_DISABLED);
@@ -1121,13 +1106,8 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada);
return false;
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
return deviceState.mHasHeadTracker;
}
}
@@ -1144,13 +1124,8 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada);
return false;
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
if (!deviceState.mHasHeadTracker) {
deviceState.mHasHeadTracker = true;
mAudioService.persistSpatialAudioDeviceSettings();
@@ -1168,13 +1143,8 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada);
return false;
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
if (!deviceState.mHasHeadTracker) {
return false;
}
@@ -1531,7 +1501,7 @@ public class SpatializerHelper {
SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @NonNull String address) {
mDeviceType = deviceType;
- mDeviceAddress = Objects.requireNonNull(address);
+ mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
}
@Override
@@ -1599,6 +1569,18 @@ public class SpatializerHelper {
return null;
}
}
+
+ public AudioDeviceAttributes getAudioDeviceAttributes() {
+ return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress);
+ }
+
+ public boolean matchesAudioDeviceAttributes(AudioDeviceAttributes ada) {
+ final int deviceType = ada.getType();
+ final boolean wireless = isWireless(deviceType);
+ return (deviceType == mDeviceType)
+ && (!wireless || ada.getAddress().equals(mDeviceAddress));
+ }
}
/*package*/ synchronized String getSADeviceSettings() {
@@ -1619,7 +1601,9 @@ public class SpatializerHelper {
// small list, not worth overhead of Arrays.stream(devSettings)
for (String setting : devSettings) {
SADeviceState devState = SADeviceState.fromPersistedString(setting);
- if (devState != null) {
+ if (devState != null
+ && isDeviceCompatibleWithSpatializationModes(
+ devState.getAudioDeviceAttributes())) {
mSADevices.add(devState);
logDeviceState(devState, "setSADeviceSettings");
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index cc49f07dd0e5..41ca13f5d5f5 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -538,13 +538,12 @@ public final class AuthSession implements IBinder.DeathRecipient {
void onDialogAnimatedIn() {
if (mState != STATE_AUTH_STARTED) {
- Slog.w(TAG, "onDialogAnimatedIn, unexpected state: " + mState);
+ Slog.e(TAG, "onDialogAnimatedIn, unexpected state: " + mState);
+ return;
}
mState = STATE_AUTH_STARTED_UI_SHOWING;
-
startAllPreparedFingerprintSensors();
- mState = STATE_AUTH_STARTED_UI_SHOWING;
}
void onTryAgainPressed() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index 968146a166ed..ef2931ff5850 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -20,14 +20,18 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.biometrics.BiometricConstants;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.function.BooleanSupplier;
/**
* Contains all the necessary information for a HAL operation.
@@ -84,6 +88,8 @@ public class BiometricSchedulerOperation {
private final BaseClientMonitor mClientMonitor;
@Nullable
private final ClientMonitorCallback mClientCallback;
+ @NonNull
+ private final BooleanSupplier mIsDebuggable;
@Nullable
private ClientMonitorCallback mOnStartCallback;
@OperationState
@@ -99,14 +105,33 @@ public class BiometricSchedulerOperation {
this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
}
+ @VisibleForTesting
+ BiometricSchedulerOperation(
+ @NonNull BaseClientMonitor clientMonitor,
+ @Nullable ClientMonitorCallback callback,
+ @NonNull BooleanSupplier isDebuggable
+ ) {
+ this(clientMonitor, callback, STATE_WAITING_IN_QUEUE, isDebuggable);
+ }
+
protected BiometricSchedulerOperation(
@NonNull BaseClientMonitor clientMonitor,
@Nullable ClientMonitorCallback callback,
@OperationState int state
) {
+ this(clientMonitor, callback, state, Build::isDebuggable);
+ }
+
+ private BiometricSchedulerOperation(
+ @NonNull BaseClientMonitor clientMonitor,
+ @Nullable ClientMonitorCallback callback,
+ @OperationState int state,
+ @NonNull BooleanSupplier isDebuggable
+ ) {
mClientMonitor = clientMonitor;
mClientCallback = callback;
mState = state;
+ mIsDebuggable = isDebuggable;
mCancelWatchdog = () -> {
if (!isFinished()) {
Slog.e(TAG, "[Watchdog Triggered]: " + this);
@@ -144,13 +169,19 @@ public class BiometricSchedulerOperation {
* @return if this operation started
*/
public boolean start(@NonNull ClientMonitorCallback callback) {
- checkInState("start",
+ if (errorWhenNoneOf("start",
STATE_WAITING_IN_QUEUE,
STATE_WAITING_FOR_COOKIE,
- STATE_WAITING_IN_QUEUE_CANCELING);
+ STATE_WAITING_IN_QUEUE_CANCELING)) {
+ return false;
+ }
if (mClientMonitor.getCookie() != 0) {
- throw new IllegalStateException("operation requires cookie");
+ String err = "operation requires cookie";
+ if (mIsDebuggable.getAsBoolean()) {
+ throw new IllegalStateException(err);
+ }
+ Slog.e(TAG, err);
}
return doStart(callback);
@@ -164,16 +195,18 @@ public class BiometricSchedulerOperation {
* @return if this operation started
*/
public boolean startWithCookie(@NonNull ClientMonitorCallback callback, int cookie) {
- checkInState("start",
- STATE_WAITING_IN_QUEUE,
- STATE_WAITING_FOR_COOKIE,
- STATE_WAITING_IN_QUEUE_CANCELING);
-
if (mClientMonitor.getCookie() != cookie) {
Slog.e(TAG, "Mismatched cookie for operation: " + this + ", received: " + cookie);
return false;
}
+ if (errorWhenNoneOf("start",
+ STATE_WAITING_IN_QUEUE,
+ STATE_WAITING_FOR_COOKIE,
+ STATE_WAITING_IN_QUEUE_CANCELING)) {
+ return false;
+ }
+
return doStart(callback);
}
@@ -217,10 +250,12 @@ public class BiometricSchedulerOperation {
* immediately abort the operation and notify the client that it has finished unsuccessfully.
*/
public void abort() {
- checkInState("cannot abort a non-pending operation",
+ if (errorWhenNoneOf("abort",
STATE_WAITING_IN_QUEUE,
STATE_WAITING_FOR_COOKIE,
- STATE_WAITING_IN_QUEUE_CANCELING);
+ STATE_WAITING_IN_QUEUE_CANCELING)) {
+ return;
+ }
if (isHalOperation()) {
((HalClientMonitor<?>) mClientMonitor).unableToStart();
@@ -247,7 +282,9 @@ public class BiometricSchedulerOperation {
* the callback used from {@link #start(ClientMonitorCallback)} is used)
*/
public void cancel(@NonNull Handler handler, @NonNull ClientMonitorCallback callback) {
- checkNotInState("cancel", STATE_FINISHED);
+ if (errorWhenOneOf("cancel", STATE_FINISHED)) {
+ return;
+ }
final int currentState = mState;
if (!isInterruptable()) {
@@ -402,21 +439,28 @@ public class BiometricSchedulerOperation {
return mClientMonitor;
}
- private void checkNotInState(String message, @OperationState int... states) {
- for (int state : states) {
- if (mState == state) {
- throw new IllegalStateException(message + ": illegal state= " + state);
+ private boolean errorWhenOneOf(String op, @OperationState int... states) {
+ final boolean isError = ArrayUtils.contains(states, mState);
+ if (isError) {
+ String err = op + ": mState must not be " + mState;
+ if (mIsDebuggable.getAsBoolean()) {
+ throw new IllegalStateException(err);
}
+ Slog.e(TAG, err);
}
+ return isError;
}
- private void checkInState(String message, @OperationState int... states) {
- for (int state : states) {
- if (mState == state) {
- return;
+ private boolean errorWhenNoneOf(String op, @OperationState int... states) {
+ final boolean isError = !ArrayUtils.contains(states, mState);
+ if (isError) {
+ String err = op + ": mState=" + mState + " must be one of " + Arrays.toString(states);
+ if (mIsDebuggable.getAsBoolean()) {
+ throw new IllegalStateException(err);
}
+ Slog.e(TAG, err);
}
- throw new IllegalStateException(message + ": illegal state= " + mState);
+ return isError;
}
@Override
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 3551ff91f542..f526960d0ef3 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -86,6 +86,8 @@ import android.net.ipsec.ike.ChildSessionConfiguration;
import android.net.ipsec.ike.ChildSessionParams;
import android.net.ipsec.ike.IkeSession;
import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionConnectionInfo;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.IkeTunnelConnectionParams;
import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
@@ -168,9 +170,10 @@ import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -190,11 +193,19 @@ public class Vpn {
private static final long VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS = 60 * 1000;
// Length of time (in milliseconds) that an app registered for VpnManager events is placed on
- // the device idle allowlist each time the a VpnManager event is fired.
+ // the device idle allowlist each time the VpnManager event is fired.
private static final long VPN_MANAGER_EVENT_ALLOWLIST_DURATION_MS = 30 * 1000;
private static final String LOCKDOWN_ALLOWLIST_SETTING_NAME =
Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST;
+
+ /**
+ * The retries for consecutive failures.
+ *
+ * <p>If retries have exceeded the length of this array, the last entry in the array will be
+ * used as a repeating interval.
+ */
+ private static final long[] IKEV2_VPN_RETRY_DELAYS_SEC = {1L, 2L, 5L, 30L, 60L, 300L, 900L};
/**
* Largest profile size allowable for Platform VPNs.
*
@@ -473,6 +484,20 @@ public class Vpn {
"Cannot set tunnel's fd as blocking=" + blocking, e);
}
}
+
+ /**
+ * Retrieves the next retry delay
+ *
+ * <p>If retries have exceeded the IKEV2_VPN_RETRY_DELAYS_SEC, the last entry in
+ * the array will be used as a repeating interval.
+ */
+ public long getNextRetryDelaySeconds(int retryCount) {
+ if (retryCount >= IKEV2_VPN_RETRY_DELAYS_SEC.length) {
+ return IKEV2_VPN_RETRY_DELAYS_SEC[IKEV2_VPN_RETRY_DELAYS_SEC.length - 1];
+ } else {
+ return IKEV2_VPN_RETRY_DELAYS_SEC[retryCount];
+ }
+ }
}
public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd,
@@ -1646,7 +1671,9 @@ public class Vpn {
for (String app : packageNames) {
int uid = getAppUid(app, userId);
if (uid != -1) uids.add(uid);
- if (Process.isApplicationUid(uid)) {
+ // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
+ // ConnectivityServiceTest.
+ if (Process.isApplicationUid(uid) && SdkLevel.isAtLeastT()) {
uids.add(Process.toSdkSandboxUid(uid));
}
}
@@ -2603,13 +2630,23 @@ public class Vpn {
void onDefaultNetworkLinkPropertiesChanged(@NonNull LinkProperties lp);
- void onChildOpened(
- @NonNull Network network, @NonNull ChildSessionConfiguration childConfig);
+ void onDefaultNetworkLost(@NonNull Network network);
+
+ void onIkeOpened(int token, @NonNull IkeSessionConfiguration ikeConfiguration);
+
+ void onIkeConnectionInfoChanged(
+ int token, @NonNull IkeSessionConnectionInfo ikeConnectionInfo);
- void onChildTransformCreated(
- @NonNull Network network, @NonNull IpSecTransform transform, int direction);
+ void onChildOpened(int token, @NonNull ChildSessionConfiguration childConfig);
- void onSessionLost(@NonNull Network network, @Nullable Exception exception);
+ void onChildTransformCreated(int token, @NonNull IpSecTransform transform, int direction);
+
+ void onChildMigrated(
+ int token,
+ @NonNull IpSecTransform inTransform,
+ @NonNull IpSecTransform outTransform);
+
+ void onSessionLost(int token, @Nullable Exception exception);
}
/**
@@ -2640,6 +2677,10 @@ public class Vpn {
class IkeV2VpnRunner extends VpnRunner implements IkeV2VpnRunnerCallback {
@NonNull private static final String TAG = "IkeV2VpnRunner";
+ // 5 seconds grace period before tearing down the IKE Session in case new default network
+ // will come up
+ private static final long NETWORK_LOST_TIMEOUT_MS = 5000L;
+
@NonNull private final IpSecManager mIpSecManager;
@NonNull private final Ikev2VpnProfile mProfile;
@NonNull private final ConnectivityManager.NetworkCallback mNetworkCallback;
@@ -2651,24 +2692,60 @@ public class Vpn {
* of the mutable Ikev2VpnRunner fields. The Ikev2VpnRunner is built mostly lock-free by
* virtue of everything being serialized on this executor.
*/
- @NonNull private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+ @NonNull
+ private final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(1);
+
+ @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostTimeout;
+ @Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionTimeout;
/** Signal to ensure shutdown is honored even if a new Network is connected. */
private boolean mIsRunning = true;
+ /**
+ * The token used by the primary/current/active IKE session.
+ *
+ * <p>This token MUST be updated when the VPN switches to use a new IKE session.
+ */
+ private int mCurrentToken = -1;
+
@Nullable private IpSecTunnelInterface mTunnelIface;
- @Nullable private IkeSession mSession;
@Nullable private Network mActiveNetwork;
@Nullable private NetworkCapabilities mUnderlyingNetworkCapabilities;
@Nullable private LinkProperties mUnderlyingLinkProperties;
private final String mSessionKey;
+ @Nullable private IkeSession mSession;
+ @Nullable private IkeSessionConnectionInfo mIkeConnectionInfo;
+
+ // mMobikeEnabled can only be updated after IKE AUTH is finished.
+ private boolean mMobikeEnabled = false;
+
+ /**
+ * The number of attempts since the last successful connection.
+ *
+ * <p>This variable controls the retry delay, and is reset when a new IKE session is
+ * opened or when there is a new default network.
+ */
+ private int mRetryCount = 0;
+
IkeV2VpnRunner(@NonNull Ikev2VpnProfile profile) {
super(TAG);
mProfile = profile;
mIpSecManager = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
mNetworkCallback = new VpnIkev2Utils.Ikev2VpnNetworkCallback(TAG, this, mExecutor);
mSessionKey = UUID.randomUUID().toString();
+
+ // Set the policy so that cancelled tasks will be removed from the work queue
+ mExecutor.setRemoveOnCancelPolicy(true);
+
+ // Set the policy so that all delayed tasks will not be executed
+ mExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+
+ // To avoid hitting RejectedExecutionException upon shutdown of the mExecutor */
+ mExecutor.setRejectedExecutionHandler(
+ (r, executor) -> {
+ Log.d(TAG, "Runnable " + r + " rejected by the mExecutor");
+ });
}
@Override
@@ -2707,22 +2784,64 @@ public class Vpn {
return Objects.equals(mActiveNetwork, network) && mIsRunning;
}
+ private boolean isActiveToken(int token) {
+ return (mCurrentToken == token) && mIsRunning;
+ }
+
+ /**
+ * Called when an IKE session has been opened
+ *
+ * <p>This method is only ever called once per IkeSession, and MUST run on the mExecutor
+ * thread in order to ensure consistency of the Ikev2VpnRunner fields.
+ */
+ public void onIkeOpened(int token, @NonNull IkeSessionConfiguration ikeConfiguration) {
+ if (!isActiveToken(token)) {
+ Log.d(TAG, "onIkeOpened called for obsolete token " + token);
+ return;
+ }
+
+ mMobikeEnabled =
+ ikeConfiguration.isIkeExtensionEnabled(
+ IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE);
+ onIkeConnectionInfoChanged(token, ikeConfiguration.getIkeSessionConnectionInfo());
+ mRetryCount = 0;
+ }
+
+ /**
+ * Called when an IKE session's {@link IkeSessionConnectionInfo} is available or updated
+ *
+ * <p>This callback is usually fired when an IKE session has been opened or migrated.
+ *
+ * <p>This method is called multiple times over the lifetime of an IkeSession, and MUST run
+ * on the mExecutor thread in order to ensure consistency of the Ikev2VpnRunner fields.
+ */
+ public void onIkeConnectionInfoChanged(
+ int token, @NonNull IkeSessionConnectionInfo ikeConnectionInfo) {
+ if (!isActiveToken(token)) {
+ Log.d(TAG, "onIkeConnectionInfoChanged called for obsolete token " + token);
+ return;
+ }
+
+ // The update on VPN and the IPsec tunnel will be done when migration is fully complete
+ // in onChildMigrated
+ mIkeConnectionInfo = ikeConnectionInfo;
+ }
+
/**
* Called when an IKE Child session has been opened, signalling completion of the startup.
*
* <p>This method is only ever called once per IkeSession, and MUST run on the mExecutor
* thread in order to ensure consistency of the Ikev2VpnRunner fields.
*/
- public void onChildOpened(
- @NonNull Network network, @NonNull ChildSessionConfiguration childConfig) {
- if (!isActiveNetwork(network)) {
- Log.d(TAG, "onOpened called for obsolete network " + network);
+ public void onChildOpened(int token, @NonNull ChildSessionConfiguration childConfig) {
+ if (!isActiveToken(token)) {
+ Log.d(TAG, "onChildOpened called for obsolete token " + token);
// Do nothing; this signals that either: (1) a new/better Network was found,
- // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
- // IKE session was already shut down (exited, or an error was encountered somewhere
- // else). In both cases, all resources and sessions are torn down via
- // resetIkeState().
+ // and the Ikev2VpnRunner has switched to it by restarting a new IKE session in
+ // onDefaultNetworkChanged, or (2) this IKE session was already shut down (exited,
+ // or an error was encountered somewhere else). In both cases, all resources and
+ // sessions are torn down via resetIkeState().
return;
}
@@ -2741,6 +2860,11 @@ public class Vpn {
dnsAddrStrings.add(addr.getHostAddress());
}
+ // The actual network of this IKE session has been set up with is
+ // mIkeConnectionInfo.getNetwork() instead of mActiveNetwork because
+ // mActiveNetwork might have been updated after the setup was triggered.
+ final Network network = mIkeConnectionInfo.getNetwork();
+
final NetworkAgent networkAgent;
final LinkProperties lp;
@@ -2783,8 +2907,8 @@ public class Vpn {
networkAgent.sendLinkProperties(lp);
} catch (Exception e) {
- Log.d(TAG, "Error in ChildOpened for network " + network, e);
- onSessionLost(network, e);
+ Log.d(TAG, "Error in ChildOpened for token " + token, e);
+ onSessionLost(token, e);
}
}
@@ -2792,19 +2916,19 @@ public class Vpn {
* Called when an IPsec transform has been created, and should be applied.
*
* <p>This method is called multiple times over the lifetime of an IkeSession (or default
- * network), and is MUST always be called on the mExecutor thread in order to ensure
+ * network), and MUST always be called on the mExecutor thread in order to ensure
* consistency of the Ikev2VpnRunner fields.
*/
public void onChildTransformCreated(
- @NonNull Network network, @NonNull IpSecTransform transform, int direction) {
- if (!isActiveNetwork(network)) {
- Log.d(TAG, "ChildTransformCreated for obsolete network " + network);
+ int token, @NonNull IpSecTransform transform, int direction) {
+ if (!isActiveToken(token)) {
+ Log.d(TAG, "ChildTransformCreated for obsolete token " + token);
// Do nothing; this signals that either: (1) a new/better Network was found,
- // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
- // IKE session was already shut down (exited, or an error was encountered somewhere
- // else). In both cases, all resources and sessions are torn down via
- // resetIkeState().
+ // and the Ikev2VpnRunner has switched to it by restarting a new IKE session in
+ // onDefaultNetworkChanged, or (2) this IKE session was already shut down (exited,
+ // or an error was encountered somewhere else). In both cases, all resources and
+ // sessions are torn down via resetIkeState().
return;
}
@@ -2813,36 +2937,127 @@ public class Vpn {
// them alive for us
mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
} catch (IOException e) {
- Log.d(TAG, "Transform application failed for network " + network, e);
- onSessionLost(network, e);
+ Log.d(TAG, "Transform application failed for token " + token, e);
+ onSessionLost(token, e);
+ }
+ }
+
+ /**
+ * Called when an IPsec transform has been created, and should be re-applied.
+ *
+ * <p>This method is called multiple times over the lifetime of an IkeSession (or default
+ * network), and MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ public void onChildMigrated(
+ int token,
+ @NonNull IpSecTransform inTransform,
+ @NonNull IpSecTransform outTransform) {
+ if (!isActiveToken(token)) {
+ Log.d(TAG, "onChildMigrated for obsolete token " + token);
+ return;
+ }
+
+ // The actual network of this IKE session has migrated to is
+ // mIkeConnectionInfo.getNetwork() instead of mActiveNetwork because mActiveNetwork
+ // might have been updated after the migration was triggered.
+ final Network network = mIkeConnectionInfo.getNetwork();
+
+ try {
+ synchronized (Vpn.this) {
+ mConfig.underlyingNetworks = new Network[] {network};
+ mNetworkCapabilities =
+ new NetworkCapabilities.Builder(mNetworkCapabilities)
+ .setUnderlyingNetworks(Collections.singletonList(network))
+ .build();
+ mNetworkAgent.setUnderlyingNetworks(Collections.singletonList(network));
+ }
+
+ mTunnelIface.setUnderlyingNetwork(network);
+
+ // Transforms do not need to be persisted; the IkeSession will keep them alive for
+ // us
+ mIpSecManager.applyTunnelModeTransform(
+ mTunnelIface, IpSecManager.DIRECTION_IN, inTransform);
+ mIpSecManager.applyTunnelModeTransform(
+ mTunnelIface, IpSecManager.DIRECTION_OUT, outTransform);
+ } catch (IOException e) {
+ Log.d(TAG, "Transform application failed for token " + token, e);
+ onSessionLost(token, e);
}
}
/**
* Called when a new default network is connected.
*
- * <p>The Ikev2VpnRunner will unconditionally switch to the new network, killing the old IKE
- * state in the process, and starting a new IkeSession instance.
+ * <p>The Ikev2VpnRunner will unconditionally switch to the new network. If the IKE session
+ * has mobility, Ikev2VpnRunner will migrate the existing IkeSession to the new network.
+ * Otherwise, Ikev2VpnRunner will kill the old IKE state, and start a new IkeSession
+ * instance.
*
* <p>This method MUST always be called on the mExecutor thread in order to ensure
* consistency of the Ikev2VpnRunner fields.
*/
public void onDefaultNetworkChanged(@NonNull Network network) {
- Log.d(TAG, "Starting IKEv2/IPsec session on new network: " + network);
+ Log.d(TAG, "onDefaultNetworkChanged: " + network);
+
+ // If there is a new default network brought up, cancel the retry task to prevent
+ // establishing an unnecessary IKE session.
+ cancelRetryNewIkeSessionFuture();
+
+ // If there is a new default network brought up, cancel the obsolete reset and retry
+ // task.
+ cancelHandleNetworkLostTimeout();
+
+ if (!mIsRunning) {
+ Log.d(TAG, "onDefaultNetworkChanged after exit");
+ return; // VPN has been shut down.
+ }
+
+ mActiveNetwork = network;
+ mRetryCount = 0;
+
+ startOrMigrateIkeSession(network);
+ }
+
+ /**
+ * Start a new IKE session.
+ *
+ * <p>This method MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ *
+ * @param underlyingNetwork if the value is {@code null}, which means there is no active
+ * network can be used, do nothing and return immediately. Otherwise, use the
+ * given network to start a new IKE session.
+ */
+ private void startOrMigrateIkeSession(@Nullable Network underlyingNetwork) {
+ if (underlyingNetwork == null) {
+ Log.d(TAG, "There is no active network for starting an IKE session");
+ return;
+ }
try {
- if (!mIsRunning) {
- Log.d(TAG, "onDefaultNetworkChanged after exit");
- return; // VPN has been shut down.
+ if (mSession != null && mMobikeEnabled) {
+ // IKE session can schedule a migration event only when IKE AUTH is finished
+ // and mMobikeEnabled is true.
+ Log.d(
+ TAG,
+ "Migrate IKE Session with token "
+ + mCurrentToken
+ + " to network "
+ + underlyingNetwork);
+ mSession.setNetwork(underlyingNetwork);
+ return;
}
+ Log.d(TAG, "Start new IKE session on network " + underlyingNetwork);
+
// Clear mInterface to prevent Ikev2VpnRunner being cleared when
// interfaceRemoved() is called.
mInterface = null;
// Without MOBIKE, we have no way to seamlessly migrate. Close on old
// (non-default) network, and start the new one.
resetIkeState();
- mActiveNetwork = network;
// Get Ike options from IkeTunnelConnectionParams if it's available in the
// profile.
@@ -2852,12 +3067,12 @@ public class Vpn {
final ChildSessionParams childSessionParams;
if (ikeTunConnParams != null) {
final IkeSessionParams.Builder builder = new IkeSessionParams.Builder(
- ikeTunConnParams.getIkeSessionParams()).setNetwork(network);
+ ikeTunConnParams.getIkeSessionParams()).setNetwork(underlyingNetwork);
ikeSessionParams = builder.build();
childSessionParams = ikeTunConnParams.getTunnelModeChildSessionParams();
} else {
ikeSessionParams = VpnIkev2Utils.buildIkeSessionParams(
- mContext, mProfile, network);
+ mContext, mProfile, underlyingNetwork);
childSessionParams = VpnIkev2Utils.buildChildSessionParams(
mProfile.getAllowedAlgorithms());
}
@@ -2865,29 +3080,50 @@ public class Vpn {
// TODO: Remove the need for adding two unused addresses with
// IPsec tunnels.
final InetAddress address = InetAddress.getLocalHost();
+
+ // When onChildOpened is called and transforms are applied, it is
+ // guaranteed that the underlying network is still "network", because the
+ // all the network switch events will be deferred before onChildOpened is
+ // called. Thus it is safe to build a mTunnelIface before IKE setup.
mTunnelIface =
mIpSecManager.createIpSecTunnelInterface(
- address /* unused */,
- address /* unused */,
- network);
+ address /* unused */, address /* unused */, underlyingNetwork);
NetdUtils.setInterfaceUp(mNetd, mTunnelIface.getInterfaceName());
- mSession = mIkev2SessionCreator.createIkeSession(
- mContext,
- ikeSessionParams,
- childSessionParams,
- mExecutor,
- new VpnIkev2Utils.IkeSessionCallbackImpl(
- TAG, IkeV2VpnRunner.this, network),
- new VpnIkev2Utils.ChildSessionCallbackImpl(
- TAG, IkeV2VpnRunner.this, network));
- Log.d(TAG, "Ike Session started for network " + network);
+ final int token = ++mCurrentToken;
+ mSession =
+ mIkev2SessionCreator.createIkeSession(
+ mContext,
+ ikeSessionParams,
+ childSessionParams,
+ mExecutor,
+ new VpnIkev2Utils.IkeSessionCallbackImpl(
+ TAG, IkeV2VpnRunner.this, token),
+ new VpnIkev2Utils.ChildSessionCallbackImpl(
+ TAG, IkeV2VpnRunner.this, token));
+ Log.d(TAG, "IKE session started for token " + token);
} catch (Exception e) {
- Log.i(TAG, "Setup failed for network " + network + ". Aborting", e);
- onSessionLost(network, e);
+ Log.i(TAG, "Setup failed for token " + mCurrentToken + ". Aborting", e);
+ onSessionLost(mCurrentToken, e);
}
}
+ private void scheduleRetryNewIkeSession() {
+ final long retryDelay = mDeps.getNextRetryDelaySeconds(mRetryCount++);
+ Log.d(TAG, "Retry new IKE session after " + retryDelay + " seconds.");
+ // If the default network is lost during the retry delay, the mActiveNetwork will be
+ // null, and the new IKE session won't be established until there is a new default
+ // network bringing up.
+ mScheduledHandleRetryIkeSessionTimeout =
+ mExecutor.schedule(() -> {
+ startOrMigrateIkeSession(mActiveNetwork);
+
+ // Reset mScheduledHandleRetryIkeSessionTimeout since it's already run on
+ // executor thread.
+ mScheduledHandleRetryIkeSessionTimeout = null;
+ }, retryDelay, TimeUnit.SECONDS);
+ }
+
/** Called when the NetworkCapabilities of underlying network is changed */
public void onDefaultNetworkCapabilitiesChanged(@NonNull NetworkCapabilities nc) {
mUnderlyingNetworkCapabilities = nc;
@@ -2898,6 +3134,99 @@ public class Vpn {
mUnderlyingLinkProperties = lp;
}
+ /**
+ * Handles loss of the default underlying network
+ *
+ * <p>If the IKE Session has mobility, Ikev2VpnRunner will schedule a teardown event with a
+ * delay so that the IKE Session can migrate if a new network is available soon. Otherwise,
+ * Ikev2VpnRunner will kill the IKE session and reset the VPN.
+ *
+ * <p>This method MUST always be called on the mExecutor thread in order to ensure
+ * consistency of the Ikev2VpnRunner fields.
+ */
+ public void onDefaultNetworkLost(@NonNull Network network) {
+ // If the default network is torn down, there is no need to call
+ // startOrMigrateIkeSession() since it will always check if there is an active network
+ // can be used or not.
+ cancelRetryNewIkeSessionFuture();
+
+ if (!isActiveNetwork(network)) {
+ Log.d(TAG, "onDefaultNetworkLost called for obsolete network " + network);
+
+ // Do nothing; this signals that either: (1) a new/better Network was found,
+ // and the Ikev2VpnRunner has switched to it by restarting a new IKE session in
+ // onDefaultNetworkChanged, or (2) this IKE session was already shut down (exited,
+ // or an error was encountered somewhere else). In both cases, all resources and
+ // sessions are torn down via resetIkeState().
+ return;
+ } else {
+ mActiveNetwork = null;
+ }
+
+ if (mScheduledHandleNetworkLostTimeout != null
+ && !mScheduledHandleNetworkLostTimeout.isCancelled()
+ && !mScheduledHandleNetworkLostTimeout.isDone()) {
+ final IllegalStateException exception =
+ new IllegalStateException(
+ "Found a pending mScheduledHandleNetworkLostTimeout");
+ Log.i(
+ TAG,
+ "Unexpected error in onDefaultNetworkLost. Tear down session",
+ exception);
+ handleSessionLost(exception, network);
+ return;
+ }
+
+ if (mSession != null && mMobikeEnabled) {
+ Log.d(
+ TAG,
+ "IKE Session has mobility. Delay handleSessionLost for losing network "
+ + network
+ + " on session with token "
+ + mCurrentToken);
+
+ // Delay the teardown in case a new network will be available soon. For example,
+ // during handover between two WiFi networks, Android will disconnect from the
+ // first WiFi and then connects to the second WiFi.
+ mScheduledHandleNetworkLostTimeout =
+ mExecutor.schedule(
+ () -> {
+ handleSessionLost(null, network);
+ },
+ NETWORK_LOST_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ } else {
+ Log.d(TAG, "Call handleSessionLost for losing network " + network);
+ handleSessionLost(null, network);
+ }
+ }
+
+ private void cancelHandleNetworkLostTimeout() {
+ if (mScheduledHandleNetworkLostTimeout != null
+ && !mScheduledHandleNetworkLostTimeout.isDone()) {
+ // It does not matter what to put in #cancel(boolean), because it is impossible
+ // that the task tracked by mScheduledHandleNetworkLostTimeout is
+ // in-progress since both that task and onDefaultNetworkChanged are submitted to
+ // mExecutor who has only one thread.
+ Log.d(TAG, "Cancel the task for handling network lost timeout");
+ mScheduledHandleNetworkLostTimeout.cancel(false /* mayInterruptIfRunning */);
+ mScheduledHandleNetworkLostTimeout = null;
+ }
+ }
+
+ private void cancelRetryNewIkeSessionFuture() {
+ if (mScheduledHandleRetryIkeSessionTimeout != null
+ && !mScheduledHandleRetryIkeSessionTimeout.isDone()) {
+ // It does not matter what to put in #cancel(boolean), because it is impossible
+ // that the task tracked by mScheduledHandleRetryIkeSessionTimeout is
+ // in-progress since both that task and onDefaultNetworkChanged are submitted to
+ // mExecutor who has only one thread.
+ Log.d(TAG, "Cancel the task for handling new ike session timeout");
+ mScheduledHandleRetryIkeSessionTimeout.cancel(false /* mayInterruptIfRunning */);
+ mScheduledHandleRetryIkeSessionTimeout = null;
+ }
+ }
+
/** Marks the state as FAILED, and disconnects. */
private void markFailedAndDisconnect(Exception exception) {
synchronized (Vpn.this) {
@@ -2916,18 +3245,28 @@ public class Vpn {
* <p>This method MUST always be called on the mExecutor thread in order to ensure
* consistency of the Ikev2VpnRunner fields.
*/
- public void onSessionLost(@NonNull Network network, @Nullable Exception exception) {
- if (!isActiveNetwork(network)) {
- Log.d(TAG, "onSessionLost() called for obsolete network " + network);
+ public void onSessionLost(int token, @Nullable Exception exception) {
+ Log.d(TAG, "onSessionLost() called for token " + token);
+
+ if (!isActiveToken(token)) {
+ Log.d(TAG, "onSessionLost() called for obsolete token " + token);
// Do nothing; this signals that either: (1) a new/better Network was found,
- // and the Ikev2VpnRunner has switched to it in onDefaultNetworkChanged, or (2) this
- // IKE session was already shut down (exited, or an error was encountered somewhere
- // else). In both cases, all resources and sessions are torn down via
- // onSessionLost() and resetIkeState().
+ // and the Ikev2VpnRunner has switched to it by restarting a new IKE session in
+ // onDefaultNetworkChanged, or (2) this IKE session was already shut down (exited,
+ // or an error was encountered somewhere else). In both cases, all resources and
+ // sessions are torn down via resetIkeState().
return;
}
+ handleSessionLost(exception, mActiveNetwork);
+ }
+
+ private void handleSessionLost(@Nullable Exception exception, @Nullable Network network) {
+ // Cancel mScheduledHandleNetworkLostTimeout if the session it is going to terminate is
+ // already terminated due to other failures.
+ cancelHandleNetworkLostTimeout();
+
synchronized (Vpn.this) {
if (exception instanceof IkeProtocolException) {
final IkeProtocolException ikeException = (IkeProtocolException) exception;
@@ -2947,7 +3286,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
ikeException.getErrorType(),
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- mActiveNetwork,
+ network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -2965,7 +3304,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE,
ikeException.getErrorType(),
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- mActiveNetwork,
+ network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -2984,7 +3323,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_LOST,
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- mActiveNetwork,
+ network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -2999,7 +3338,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST,
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- mActiveNetwork,
+ network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3013,7 +3352,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT,
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- mActiveNetwork,
+ network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3027,7 +3366,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_IO,
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- mActiveNetwork,
+ network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3037,15 +3376,16 @@ public class Vpn {
} else if (exception != null) {
Log.wtf(TAG, "onSessionLost: exception = " + exception);
}
+
+ scheduleRetryNewIkeSession();
}
- mActiveNetwork = null;
mUnderlyingNetworkCapabilities = null;
mUnderlyingLinkProperties = null;
// Close all obsolete state, but keep VPN alive incase a usable network comes up.
// (Mirrors VpnService behavior)
- Log.d(TAG, "Resetting state for network: " + network);
+ Log.d(TAG, "Resetting state for token: " + mCurrentToken);
synchronized (Vpn.this) {
// Since this method handles non-fatal errors only, set mInterface to null to
@@ -3090,6 +3430,8 @@ public class Vpn {
mSession.kill(); // Kill here to make sure all resources are released immediately
mSession = null;
}
+ mIkeConnectionInfo = null;
+ mMobikeEnabled = false;
}
/**
diff --git a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
index 17058282d947..857c86de57ca 100644
--- a/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
+++ b/services/core/java/com/android/server/connectivity/VpnIkev2Utils.java
@@ -68,6 +68,7 @@ import android.net.ipsec.ike.IkeRfc822AddrIdentification;
import android.net.ipsec.ike.IkeSaProposal;
import android.net.ipsec.ike.IkeSessionCallback;
import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionConnectionInfo;
import android.net.ipsec.ike.IkeSessionParams;
import android.net.ipsec.ike.IkeTrafficSelector;
import android.net.ipsec.ike.TunnelModeChildSessionParams;
@@ -107,6 +108,7 @@ public class VpnIkev2Utils {
new IkeSessionParams.Builder(context)
.setServerHostname(profile.getServerAddr())
.setNetwork(network)
+ .addIkeOption(IkeSessionParams.IKE_OPTION_MOBIKE)
.setLocalIdentification(localId)
.setRemoteIdentification(remoteId);
setIkeAuth(profile, ikeOptionsBuilder);
@@ -298,72 +300,79 @@ public class VpnIkev2Utils {
static class IkeSessionCallbackImpl implements IkeSessionCallback {
private final String mTag;
private final Vpn.IkeV2VpnRunnerCallback mCallback;
- private final Network mNetwork;
+ private final int mToken;
- IkeSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+ IkeSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, int token) {
mTag = tag;
mCallback = callback;
- mNetwork = network;
+ mToken = token;
}
@Override
public void onOpened(@NonNull IkeSessionConfiguration ikeSessionConfig) {
- Log.d(mTag, "IkeOpened for network " + mNetwork);
- // Nothing to do here.
+ Log.d(mTag, "IkeOpened for token " + mToken);
+ mCallback.onIkeOpened(mToken, ikeSessionConfig);
}
@Override
public void onClosed() {
- Log.d(mTag, "IkeClosed for network " + mNetwork);
- mCallback.onSessionLost(mNetwork, null); // Server requested session closure. Retry?
+ Log.d(mTag, "IkeClosed for token " + mToken);
+ mCallback.onSessionLost(mToken, null); // Server requested session closure. Retry?
}
@Override
public void onClosedExceptionally(@NonNull IkeException exception) {
- Log.d(mTag, "IkeClosedExceptionally for network " + mNetwork, exception);
- mCallback.onSessionLost(mNetwork, exception);
+ Log.d(mTag, "IkeClosedExceptionally for token " + mToken, exception);
+ mCallback.onSessionLost(mToken, exception);
}
@Override
public void onError(@NonNull IkeProtocolException exception) {
- Log.d(mTag, "IkeError for network " + mNetwork, exception);
+ Log.d(mTag, "IkeError for token " + mToken, exception);
// Non-fatal, log and continue.
}
+
+ @Override
+ public void onIkeSessionConnectionInfoChanged(
+ @NonNull IkeSessionConnectionInfo connectionInfo) {
+ Log.d(mTag, "onIkeSessionConnectionInfoChanged for token " + mToken);
+ mCallback.onIkeConnectionInfoChanged(mToken, connectionInfo);
+ }
}
static class ChildSessionCallbackImpl implements ChildSessionCallback {
private final String mTag;
private final Vpn.IkeV2VpnRunnerCallback mCallback;
- private final Network mNetwork;
+ private final int mToken;
- ChildSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, Network network) {
+ ChildSessionCallbackImpl(String tag, Vpn.IkeV2VpnRunnerCallback callback, int token) {
mTag = tag;
mCallback = callback;
- mNetwork = network;
+ mToken = token;
}
@Override
public void onOpened(@NonNull ChildSessionConfiguration childConfig) {
- Log.d(mTag, "ChildOpened for network " + mNetwork);
- mCallback.onChildOpened(mNetwork, childConfig);
+ Log.d(mTag, "ChildOpened for token " + mToken);
+ mCallback.onChildOpened(mToken, childConfig);
}
@Override
public void onClosed() {
- Log.d(mTag, "ChildClosed for network " + mNetwork);
- mCallback.onSessionLost(mNetwork, null);
+ Log.d(mTag, "ChildClosed for token " + mToken);
+ mCallback.onSessionLost(mToken, null);
}
@Override
public void onClosedExceptionally(@NonNull IkeException exception) {
- Log.d(mTag, "ChildClosedExceptionally for network " + mNetwork, exception);
- mCallback.onSessionLost(mNetwork, exception);
+ Log.d(mTag, "ChildClosedExceptionally for token " + mToken, exception);
+ mCallback.onSessionLost(mToken, exception);
}
@Override
public void onIpSecTransformCreated(@NonNull IpSecTransform transform, int direction) {
- Log.d(mTag, "ChildTransformCreated; Direction: " + direction + "; network " + mNetwork);
- mCallback.onChildTransformCreated(mNetwork, transform, direction);
+ Log.d(mTag, "ChildTransformCreated; Direction: " + direction + "; token " + mToken);
+ mCallback.onChildTransformCreated(mToken, transform, direction);
}
@Override
@@ -371,8 +380,15 @@ public class VpnIkev2Utils {
// Nothing to be done; no references to the IpSecTransform are held by the
// Ikev2VpnRunner (or this callback class), and this transform will be closed by the
// IKE library.
- Log.d(mTag,
- "ChildTransformDeleted; Direction: " + direction + "; for network " + mNetwork);
+ Log.d(mTag, "ChildTransformDeleted; Direction: " + direction + "; for token " + mToken);
+ }
+
+ @Override
+ public void onIpSecTransformsMigrated(
+ @NonNull IpSecTransform inIpSecTransform,
+ @NonNull IpSecTransform outIpSecTransform) {
+ Log.d(mTag, "ChildTransformsMigrated; token " + mToken);
+ mCallback.onChildMigrated(mToken, inIpSecTransform, outIpSecTransform);
}
}
@@ -390,7 +406,7 @@ public class VpnIkev2Utils {
@Override
public void onAvailable(@NonNull Network network) {
- Log.d(mTag, "Starting IKEv2/IPsec session on new network: " + network);
+ Log.d(mTag, "onAvailable called for network: " + network);
mExecutor.execute(() -> mCallback.onDefaultNetworkChanged(network));
}
@@ -412,8 +428,8 @@ public class VpnIkev2Utils {
@Override
public void onLost(@NonNull Network network) {
- Log.d(mTag, "Tearing down; lost network: " + network);
- mExecutor.execute(() -> mCallback.onSessionLost(network, null));
+ Log.d(mTag, "onLost called for network: " + network);
+ mExecutor.execute(() -> mCallback.onDefaultNetworkLost(network));
}
}
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index 767b2d18a69a..eccee52f37aa 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -16,21 +16,31 @@
package com.android.server.display;
+import android.annotation.NonNull;
import android.content.Context;
import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManager;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Temperature;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
import android.util.Slog;
-import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.Executor;
/**
* This class monitors various conditions, such as skin temperature throttling status, and limits
@@ -44,28 +54,54 @@ class BrightnessThrottler {
private final Injector mInjector;
private final Handler mHandler;
- private BrightnessThrottlingData mThrottlingData;
+ // We need a separate handler for unit testing. These two handlers are the same throughout the
+ // non-test code.
+ private final Handler mDeviceConfigHandler;
private final Runnable mThrottlingChangeCallback;
private final SkinThermalStatusObserver mSkinThermalStatusObserver;
+ private final DeviceConfigListener mDeviceConfigListener;
+ private final DeviceConfigInterface mDeviceConfig;
+
private int mThrottlingStatus;
+ private BrightnessThrottlingData mThrottlingData;
+ private BrightnessThrottlingData mDdcThrottlingData;
private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason =
BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+ private String mUniqueDisplayId;
+
+ // The most recent string that has been set from DeviceConfig
+ private String mBrightnessThrottlingDataString;
+
+ // This is a collection of brightness throttling data that has been written as overrides from
+ // the DeviceConfig. This will always take priority over the display device config data.
+ private HashMap<String, BrightnessThrottlingData> mBrightnessThrottlingDataOverride =
+ new HashMap<>(1);
BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData,
- Runnable throttlingChangeCallback) {
- this(new Injector(), handler, throttlingData, throttlingChangeCallback);
+ Runnable throttlingChangeCallback, String uniqueDisplayId) {
+ this(new Injector(), handler, handler, throttlingData, throttlingChangeCallback,
+ uniqueDisplayId);
}
- BrightnessThrottler(Injector injector, Handler handler, BrightnessThrottlingData throttlingData,
- Runnable throttlingChangeCallback) {
+ @VisibleForTesting
+ BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler,
+ BrightnessThrottlingData throttlingData, Runnable throttlingChangeCallback,
+ String uniqueDisplayId) {
mInjector = injector;
+
mHandler = handler;
+ mDeviceConfigHandler = deviceConfigHandler;
mThrottlingData = throttlingData;
+ mDdcThrottlingData = throttlingData;
mThrottlingChangeCallback = throttlingChangeCallback;
mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
- resetThrottlingData(mThrottlingData);
+ mUniqueDisplayId = uniqueDisplayId;
+ mDeviceConfig = injector.getDeviceConfig();
+ mDeviceConfigListener = new DeviceConfigListener();
+
+ resetThrottlingData(mThrottlingData, mUniqueDisplayId);
}
boolean deviceSupportsThrottling() {
@@ -86,7 +122,7 @@ class BrightnessThrottler {
void stop() {
mSkinThermalStatusObserver.stopObserving();
-
+ mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener);
// We're asked to stop throttling, so reset brightness restrictions.
mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
@@ -97,9 +133,19 @@ class BrightnessThrottler {
mThrottlingStatus = THROTTLING_INVALID;
}
- void resetThrottlingData(BrightnessThrottlingData throttlingData) {
+ private void resetThrottlingData() {
+ resetThrottlingData(mDdcThrottlingData, mUniqueDisplayId);
+ }
+
+ void resetThrottlingData(BrightnessThrottlingData throttlingData, String displayId) {
stop();
- mThrottlingData = throttlingData;
+
+ mUniqueDisplayId = displayId;
+ mDdcThrottlingData = throttlingData;
+ mDeviceConfigListener.startListening();
+ reloadBrightnessThrottlingDataOverride();
+ mThrottlingData = mBrightnessThrottlingDataOverride.getOrDefault(mUniqueDisplayId,
+ throttlingData);
if (deviceSupportsThrottling()) {
mSkinThermalStatusObserver.startObserving();
@@ -173,14 +219,148 @@ class BrightnessThrottler {
private void dumpLocal(PrintWriter pw) {
pw.println("BrightnessThrottler:");
pw.println(" mThrottlingData=" + mThrottlingData);
+ pw.println(" mDdcThrottlingData=" + mDdcThrottlingData);
+ pw.println(" mUniqueDisplayId=" + mUniqueDisplayId);
pw.println(" mThrottlingStatus=" + mThrottlingStatus);
pw.println(" mBrightnessCap=" + mBrightnessCap);
pw.println(" mBrightnessMaxReason=" +
BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
+ pw.println(" mBrightnessThrottlingDataOverride=" + mBrightnessThrottlingDataOverride);
+ pw.println(" mBrightnessThrottlingDataString=" + mBrightnessThrottlingDataString);
mSkinThermalStatusObserver.dump(pw);
}
+ private String getBrightnessThrottlingDataString() {
+ return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA,
+ /* defaultValue= */ null);
+ }
+
+ private boolean parseAndSaveData(@NonNull String strArray,
+ @NonNull HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData) {
+ boolean validConfig = true;
+ String[] items = strArray.split(",");
+ int i = 0;
+
+ try {
+ String uniqueDisplayId = items[i++];
+
+ // number of throttling points
+ int noOfThrottlingPoints = Integer.parseInt(items[i++]);
+ List<ThrottlingLevel> throttlingLevels = new ArrayList<>(noOfThrottlingPoints);
+
+ // throttling level and point
+ for (int j = 0; j < noOfThrottlingPoints; j++) {
+ String severity = items[i++];
+ int status = parseThermalStatus(severity);
+
+ float brightnessPoint = parseBrightness(items[i++]);
+
+ throttlingLevels.add(new ThrottlingLevel(status, brightnessPoint));
+ }
+ BrightnessThrottlingData toSave =
+ DisplayDeviceConfig.BrightnessThrottlingData.create(throttlingLevels);
+ tempBrightnessThrottlingData.put(uniqueDisplayId, toSave);
+ } catch (NumberFormatException | IndexOutOfBoundsException
+ | UnknownThermalStatusException e) {
+ validConfig = false;
+ Slog.e(TAG, "Throttling data is invalid array: '" + strArray + "'", e);
+ }
+
+ if (i != items.length) {
+ validConfig = false;
+ }
+
+ return validConfig;
+ }
+
+ public void reloadBrightnessThrottlingDataOverride() {
+ HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData =
+ new HashMap<>(1);
+ mBrightnessThrottlingDataString = getBrightnessThrottlingDataString();
+ boolean validConfig = true;
+ mBrightnessThrottlingDataOverride.clear();
+ if (mBrightnessThrottlingDataString != null) {
+ String[] throttlingDataSplits = mBrightnessThrottlingDataString.split(";");
+ for (String s : throttlingDataSplits) {
+ if (!parseAndSaveData(s, tempBrightnessThrottlingData)) {
+ validConfig = false;
+ break;
+ }
+ }
+
+ if (validConfig) {
+ mBrightnessThrottlingDataOverride.putAll(tempBrightnessThrottlingData);
+ tempBrightnessThrottlingData.clear();
+ }
+
+ } else {
+ Slog.w(TAG, "DeviceConfig BrightnessThrottlingData is null");
+ }
+ }
+
+ /**
+ * Listens to config data change and updates the brightness throttling data using
+ * DisplayManager#KEY_BRIGHTNESS_THROTTLING_DATA.
+ * The format should be a string similar to: "local:4619827677550801152,2,moderate,0.5,severe,
+ * 0.379518072;local:4619827677550801151,1,moderate,0.75"
+ * In this order:
+ * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>]
+ * Where the latter part is repeated for each throttling level, and the entirety is repeated
+ * for each display, separated by a semicolon.
+ */
+ public class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
+ public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler);
+
+ public void startListening() {
+ mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ mExecutor, this);
+ }
+
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ reloadBrightnessThrottlingDataOverride();
+ resetThrottlingData();
+ }
+ }
+
+ private float parseBrightness(String intVal) throws NumberFormatException {
+ float value = Float.parseFloat(intVal);
+ if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
+ throw new NumberFormatException("Brightness constraint value out of bounds.");
+ }
+ return value;
+ }
+
+ @PowerManager.ThermalStatus private int parseThermalStatus(@NonNull String value)
+ throws UnknownThermalStatusException {
+ switch (value) {
+ case "none":
+ return PowerManager.THERMAL_STATUS_NONE;
+ case "light":
+ return PowerManager.THERMAL_STATUS_LIGHT;
+ case "moderate":
+ return PowerManager.THERMAL_STATUS_MODERATE;
+ case "severe":
+ return PowerManager.THERMAL_STATUS_SEVERE;
+ case "critical":
+ return PowerManager.THERMAL_STATUS_CRITICAL;
+ case "emergency":
+ return PowerManager.THERMAL_STATUS_EMERGENCY;
+ case "shutdown":
+ return PowerManager.THERMAL_STATUS_SHUTDOWN;
+ default:
+ throw new UnknownThermalStatusException("Invalid Thermal Status: " + value);
+ }
+ }
+
+ private static class UnknownThermalStatusException extends Exception {
+ UnknownThermalStatusException(String message) {
+ super(message);
+ }
+ }
+
private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
private final Injector mInjector;
private final Handler mHandler;
@@ -258,5 +438,10 @@ class BrightnessThrottler {
return IThermalService.Stub.asInterface(
ServiceManager.getService(Context.THERMAL_SERVICE));
}
+
+ @NonNull
+ public DeviceConfigInterface getDeviceConfig() {
+ return DeviceConfigInterface.REAL;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index a25ac210f9c8..2322280d8a9b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -279,9 +279,13 @@ public class DisplayDeviceConfig {
private HighBrightnessModeData mHbmData;
private DensityMapping mDensityMapping;
private String mLoadedFrom = null;
+ private Spline mSdrToHdrRatioSpline;
+ // Brightness Throttling data may be updated via the DeviceConfig. Here we store the original
+ // data, which comes from the ddc, and the current one, which may be the DeviceConfig
+ // overwritten value.
private BrightnessThrottlingData mBrightnessThrottlingData;
- private Spline mSdrToHdrRatioSpline;
+ private BrightnessThrottlingData mOriginalBrightnessThrottlingData;
private DisplayDeviceConfig(Context context) {
mContext = context;
@@ -422,6 +426,10 @@ public class DisplayDeviceConfig {
return config;
}
+ void setBrightnessThrottlingData(BrightnessThrottlingData brightnessThrottlingData) {
+ mBrightnessThrottlingData = brightnessThrottlingData;
+ }
+
/**
* Return the brightness mapping nits array.
*
@@ -637,6 +645,7 @@ public class DisplayDeviceConfig {
+ ", mHbmData=" + mHbmData
+ ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
+ ", mBrightnessThrottlingData=" + mBrightnessThrottlingData
+ + ", mOriginalBrightnessThrottlingData=" + mOriginalBrightnessThrottlingData
+ ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
+ ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
+ ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease
@@ -932,6 +941,7 @@ public class DisplayDeviceConfig {
if (!badConfig) {
mBrightnessThrottlingData = BrightnessThrottlingData.create(throttlingLevels);
+ mOriginalBrightnessThrottlingData = mBrightnessThrottlingData;
}
}
@@ -1407,7 +1417,9 @@ public class DisplayDeviceConfig {
/**
* Container for brightness throttling data.
*/
- static class BrightnessThrottlingData {
+ public static class BrightnessThrottlingData {
+ public List<ThrottlingLevel> throttlingLevels;
+
static class ThrottlingLevel {
public @PowerManager.ThermalStatus int thermalStatus;
public float brightness;
@@ -1421,9 +1433,25 @@ public class DisplayDeviceConfig {
public String toString() {
return "[" + thermalStatus + "," + brightness + "]";
}
- }
- public List<ThrottlingLevel> throttlingLevels;
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ThrottlingLevel)) {
+ return false;
+ }
+ ThrottlingLevel otherThrottlingLevel = (ThrottlingLevel) obj;
+
+ return otherThrottlingLevel.thermalStatus == this.thermalStatus
+ && otherThrottlingLevel.brightness == this.brightness;
+ }
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 31 * result + thermalStatus;
+ result = 31 * result + Float.hashCode(brightness);
+ return result;
+ }
+ }
static public BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels)
{
@@ -1482,12 +1510,30 @@ public class DisplayDeviceConfig {
+ "} ";
}
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof BrightnessThrottlingData)) {
+ return false;
+ }
+
+ BrightnessThrottlingData otherBrightnessThrottlingData = (BrightnessThrottlingData) obj;
+ return throttlingLevels.equals(otherBrightnessThrottlingData.throttlingLevels);
+ }
+
+ @Override
+ public int hashCode() {
+ return throttlingLevels.hashCode();
+ }
+
private BrightnessThrottlingData(List<ThrottlingLevel> inLevels) {
throttlingLevels = new ArrayList<>(inLevels.size());
for (ThrottlingLevel level : inLevels) {
throttlingLevels.add(new ThrottlingLevel(level.thermalStatus, level.brightness));
}
}
-
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d05a902c6593..95c8fef12976 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -461,6 +461,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private boolean mIsRbcActive;
+ // Whether there's a callback to tell listeners the display has changed scheduled to run. When
+ // true it implies a wakelock is being held to guarantee the update happens before we collapse
+ // into suspend and so needs to be cleaned up if the thread is exiting.
+ // Should only be accessed on the Handler thread.
+ private boolean mOnStateChangedPending;
+
+ // Count of proximity messages currently on this DPC's Handler. Used to keep track of how many
+ // suspend blocker acquisitions are pending when shutting down this DPC.
+ // Should only be accessed on the Handler thread.
+ private int mOnProximityPositiveMessages;
+ private int mOnProximityNegativeMessages;
+
// Animators.
private ObjectAnimator mColorFadeOnAnimator;
private ObjectAnimator mColorFadeOffAnimator;
@@ -861,7 +873,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
});
mBrightnessThrottler.resetThrottlingData(
- mDisplayDeviceConfig.getBrightnessThrottlingData());
+ mDisplayDeviceConfig.getBrightnessThrottlingData(), mUniqueDisplayId);
}
private void sendUpdatePowerState() {
@@ -1091,10 +1103,24 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mHbmController.stop();
mBrightnessThrottler.stop();
mHandler.removeCallbacksAndMessages(null);
+
+ // Release any outstanding wakelocks we're still holding because of pending messages.
if (mUnfinishedBusiness) {
mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdUnfinishedBusiness);
mUnfinishedBusiness = false;
}
+ if (mOnStateChangedPending) {
+ mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+ mOnStateChangedPending = false;
+ }
+ for (int i = 0; i < mOnProximityPositiveMessages; i++) {
+ mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
+ }
+ mOnProximityPositiveMessages = 0;
+ for (int i = 0; i < mOnProximityNegativeMessages; i++) {
+ mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
+ }
+ mOnProximityNegativeMessages = 0;
final float brightness = mPowerState != null
? mPowerState.getScreenBrightness()
@@ -1816,7 +1842,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
() -> {
sendUpdatePowerStateLocked();
postBrightnessChangeRunnable();
- });
+ }, mUniqueDisplayId);
}
private void blockScreenOn() {
@@ -2248,8 +2274,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
private void sendOnStateChangedWithWakelock() {
- mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdOnStateChanged);
- mHandler.post(mOnStateChangedRunnable);
+ if (!mOnStateChangedPending) {
+ mOnStateChangedPending = true;
+ mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdOnStateChanged);
+ mHandler.post(mOnStateChangedRunnable);
+ }
}
private void logDisplayPolicyChanged(int newPolicy) {
@@ -2408,6 +2437,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private final Runnable mOnStateChangedRunnable = new Runnable() {
@Override
public void run() {
+ mOnStateChangedPending = false;
mCallbacks.onStateChanged();
mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdOnStateChanged);
}
@@ -2416,17 +2446,20 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private void sendOnProximityPositiveWithWakelock() {
mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxPositive);
mHandler.post(mOnProximityPositiveRunnable);
+ mOnProximityPositiveMessages++;
}
private final Runnable mOnProximityPositiveRunnable = new Runnable() {
@Override
public void run() {
+ mOnProximityPositiveMessages--;
mCallbacks.onProximityPositive();
mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxPositive);
}
};
private void sendOnProximityNegativeWithWakelock() {
+ mOnProximityNegativeMessages++;
mCallbacks.acquireSuspendBlocker(mSuspendBlockerIdProxNegative);
mHandler.post(mOnProximityNegativeRunnable);
}
@@ -2434,6 +2467,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private final Runnable mOnProximityNegativeRunnable = new Runnable() {
@Override
public void run() {
+ mOnProximityNegativeMessages--;
mCallbacks.onProximityNegative();
mCallbacks.releaseSuspendBlocker(mSuspendBlockerIdProxNegative);
}
@@ -2533,6 +2567,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
pw.println(" mReportedToPolicy="
+ reportedToPolicyToString(mReportedScreenStateToPolicy));
pw.println(" mIsRbcActive=" + mIsRbcActive);
+ pw.println(" mOnStateChangePending=" + mOnStateChangedPending);
+ pw.println(" mOnProximityPositiveMessages=" + mOnProximityPositiveMessages);
+ pw.println(" mOnProximityNegativeMessages=" + mOnProximityNegativeMessages);
if (mScreenBrightnessRampAnimator != null) {
pw.println(" mScreenBrightnessRampAnimator.isAnimating()="
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 8de150ac4124..223b8c181fea 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -956,6 +956,8 @@ public final class ColorDisplayService extends SystemService {
R.array.config_availableColorModes);
if (availableColorModes.length > 0) {
colorMode = availableColorModes[0];
+ } else {
+ colorMode = NOT_SET;
}
}
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 098e8f74749c..7d12ede754ef 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -46,7 +46,6 @@ import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.ContentRecordingSession;
-import android.window.WindowContainerToken;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -433,7 +432,7 @@ public final class MediaProjectionManagerService extends SystemService
private IBinder mToken;
private IBinder.DeathRecipient mDeathEater;
private boolean mRestoreSystemAlertWindow;
- private WindowContainerToken mTaskRecordingWindowContainerToken = null;
+ private IBinder mLaunchCookie = null;
MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
boolean isPrivileged) {
@@ -609,14 +608,13 @@ public final class MediaProjectionManagerService extends SystemService
}
@Override // Binder call
- public void setTaskRecordingWindowContainerToken(WindowContainerToken token) {
- // TODO(b/221417940) set the task id to record from sysui, for the package chosen.
- mTaskRecordingWindowContainerToken = token;
+ public void setLaunchCookie(IBinder launchCookie) {
+ mLaunchCookie = launchCookie;
}
@Override // Binder call
- public WindowContainerToken getTaskRecordingWindowContainerToken() {
- return mTaskRecordingWindowContainerToken;
+ public IBinder getLaunchCookie() {
+ return mLaunchCookie;
}
public MediaProjectionInfo getProjectionInfo() {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index de9102a69a2e..6135fe8acbed 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -340,7 +340,8 @@ public class ZenModeHelper {
int newRuleInstanceCount = getCurrentInstanceCount(automaticZenRule.getOwner())
+ getCurrentInstanceCount(automaticZenRule.getConfigurationActivity())
+ 1;
- if (newRuleInstanceCount > RULE_LIMIT_PER_PACKAGE
+ int newPackageRuleCount = getPackageRuleCount(pkg) + 1;
+ if (newPackageRuleCount > RULE_LIMIT_PER_PACKAGE
|| (ruleInstanceLimit > 0 && ruleInstanceLimit < newRuleInstanceCount)) {
throw new IllegalArgumentException("Rule instance limit exceeded");
}
@@ -521,6 +522,23 @@ public class ZenModeHelper {
return count;
}
+ // Equivalent method to getCurrentInstanceCount, but for all rules associated with a specific
+ // package rather than a condition provider service or activity.
+ private int getPackageRuleCount(String pkg) {
+ if (pkg == null) {
+ return 0;
+ }
+ int count = 0;
+ synchronized (mConfig) {
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (pkg.equals(rule.getPkg())) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
public boolean canManageAutomaticZenRule(ZenRule rule) {
final int callingUid = Binder.getCallingUid();
if (callingUid == 0 || callingUid == Process.SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index af0a20ddf337..6cfe093df6d0 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -936,12 +936,15 @@ public class PackageDexOptimizer {
String classLoaderContext, int profileAnalysisResult, boolean downgrade,
int dexoptFlags, String oatDir) {
final boolean shouldBePublic = (dexoptFlags & DEXOPT_PUBLIC) != 0;
- // If the artifacts should be public while the current artifacts are not, we should
- // re-compile anyway.
- if (shouldBePublic && isOdexPrivate(packageName, path, isa, oatDir)) {
- // Ensure compilation by pretending a compiler filter change on the apk/odex location
- // (the reason for the '-'. A positive value means the 'oat' location).
- return adjustDexoptNeeded(-DexFile.DEX2OAT_FOR_FILTER);
+ final boolean isProfileGuidedFilter = (dexoptFlags & DEXOPT_PROFILE_GUIDED) != 0;
+ boolean newProfile = profileAnalysisResult == PROFILE_ANALYSIS_OPTIMIZE;
+
+ if (!newProfile && isProfileGuidedFilter && shouldBePublic
+ && isOdexPrivate(packageName, path, isa, oatDir)) {
+ // The profile that will be used is a cloud profile, while the profile used previously
+ // is a user profile. Typically, this happens after an app starts being used by other
+ // apps.
+ newProfile = true;
}
int dexoptNeeded;
@@ -959,7 +962,6 @@ public class PackageDexOptimizer {
&& profileAnalysisResult == PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES) {
actualCompilerFilter = "verify";
}
- boolean newProfile = profileAnalysisResult == PROFILE_ANALYSIS_OPTIMIZE;
dexoptNeeded = DexFile.getDexOptNeeded(path, isa, actualCompilerFilter,
classLoaderContext, newProfile, downgrade);
} catch (IOException ioe) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a01942d0dfa8..bb23d89d218f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1346,7 +1346,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
private String getDeviceOwnerDeletedPackageMsg() {
DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
return dpm.getResources().getString(PACKAGE_DELETED_BY_DO,
- () -> mContext.getString(R.string.package_updated_device_owner));
+ () -> mContext.getString(R.string.package_deleted_device_owner));
}
@Override
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
index 88b4a94f7027..03e568c4599e 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
@@ -413,8 +413,8 @@ public class LegacyPermissionManagerService extends ILegacyPermissionManager.Stu
return result;
}
mContext.getSystemService(AppOpsManager.class).noteOpNoThrow(
- AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, uid, packageName,
- attributionTag, reason);
+ AppOpsManager.OP_RECORD_AUDIO_HOTWORD, uid, packageName, attributionTag,
+ reason);
return result;
}
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 06a54a461d5e..9bfb40fe11f7 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -1540,6 +1540,7 @@ public class ParsingPackageUtils {
try {
int minVers = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
String minCode = null;
+ boolean minAssigned = false;
int targetVers = ParsingUtils.DEFAULT_TARGET_SDK_VERSION;
String targetCode = null;
int maxVers = Integer.MAX_VALUE;
@@ -1548,9 +1549,11 @@ public class ParsingPackageUtils {
if (val != null) {
if (val.type == TypedValue.TYPE_STRING && val.string != null) {
minCode = val.string.toString();
+ minAssigned = !TextUtils.isEmpty(minCode);
} else {
// If it's not a string, it's an integer.
minVers = val.data;
+ minAssigned = true;
}
}
@@ -1558,7 +1561,7 @@ public class ParsingPackageUtils {
if (val != null) {
if (val.type == TypedValue.TYPE_STRING && val.string != null) {
targetCode = val.string.toString();
- if (minCode == null) {
+ if (!minAssigned) {
minCode = targetCode;
}
} else {
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index b6a413524c5c..452bdf409828 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -189,6 +189,7 @@ public class TestHarnessModeService extends SystemService {
if (adbManager.getAdbTempKeysFile() != null) {
writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath());
}
+ adbManager.notifyKeyFilesUpdated();
}
private void configureUser() {
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 78b1c20ac4b2..d79837be3583 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -61,12 +61,11 @@ final class Vibration {
IGNORED_BACKGROUND,
IGNORED_UNKNOWN_VIBRATION,
IGNORED_UNSUPPORTED,
- IGNORED_FOR_ALARM,
IGNORED_FOR_EXTERNAL,
+ IGNORED_FOR_HIGHER_IMPORTANCE,
IGNORED_FOR_ONGOING,
IGNORED_FOR_POWER,
IGNORED_FOR_RINGER_MODE,
- IGNORED_FOR_RINGTONE,
IGNORED_FOR_SETTINGS,
IGNORED_SUPERSEDED,
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index f0911ca62027..5ac2f4f27452 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -713,14 +713,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
case IGNORED_ERROR_APP_OPS:
Slog.w(TAG, "Would be an error: vibrate from uid " + uid);
break;
- case IGNORED_FOR_ALARM:
+ case IGNORED_FOR_EXTERNAL:
if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
+ Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
}
break;
- case IGNORED_FOR_EXTERNAL:
+ case IGNORED_FOR_HIGHER_IMPORTANCE:
if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
+ Slog.d(TAG, "Ignoring incoming vibration in favor of ongoing vibration"
+ + " with higher importance");
}
break;
case IGNORED_FOR_ONGOING:
@@ -734,12 +735,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
+ attrs);
}
break;
- case IGNORED_FOR_RINGTONE:
- if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of ringtone vibration");
- }
- break;
-
default:
if (DEBUG) {
Slog.d(TAG, "Vibration for uid=" + uid + " and with attrs=" + attrs
@@ -812,20 +807,43 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return null;
}
- if (currentVibration.attrs.getUsage() == VibrationAttributes.USAGE_ALARM) {
- return Vibration.Status.IGNORED_FOR_ALARM;
- }
-
- if (currentVibration.attrs.getUsage() == VibrationAttributes.USAGE_RINGTONE) {
- return Vibration.Status.IGNORED_FOR_RINGTONE;
+ int currentUsage = currentVibration.attrs.getUsage();
+ int newUsage = vib.attrs.getUsage();
+ if (getVibrationImportance(currentUsage) > getVibrationImportance(newUsage)) {
+ // Current vibration has higher importance than this one and should not be cancelled.
+ return Vibration.Status.IGNORED_FOR_HIGHER_IMPORTANCE;
}
if (currentVibration.isRepeating()) {
+ // Current vibration is repeating, assume it's more important.
return Vibration.Status.IGNORED_FOR_ONGOING;
}
+
return null;
}
+ private static int getVibrationImportance(@VibrationAttributes.Usage int usage) {
+ switch (usage) {
+ case VibrationAttributes.USAGE_RINGTONE:
+ return 5;
+ case VibrationAttributes.USAGE_ALARM:
+ return 4;
+ case VibrationAttributes.USAGE_NOTIFICATION:
+ return 3;
+ case VibrationAttributes.USAGE_COMMUNICATION_REQUEST:
+ case VibrationAttributes.USAGE_ACCESSIBILITY:
+ return 2;
+ case VibrationAttributes.USAGE_HARDWARE_FEEDBACK:
+ case VibrationAttributes.USAGE_PHYSICAL_EMULATION:
+ return 1;
+ case VibrationAttributes.USAGE_MEDIA:
+ case VibrationAttributes.USAGE_TOUCH:
+ case VibrationAttributes.USAGE_UNKNOWN:
+ default:
+ return 0;
+ }
+ }
+
/**
* Check if given vibration should be ignored by this service.
*
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 622de57a1078..270891fcf421 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -56,6 +56,10 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
import static com.android.internal.util.FrameworkStatsLog.CAMERA_COMPAT_CONTROL_EVENT_REPORTED__EVENT__APPEARED_APPLY_TREATMENT;
@@ -1376,7 +1380,7 @@ class ActivityMetricsLogger {
return;
}
- logAppCompatStateInternal(activity, state, packageUid, compatStateInfo);
+ logAppCompatStateInternal(activity, state, compatStateInfo);
}
/**
@@ -1416,18 +1420,61 @@ class ActivityMetricsLogger {
}
}
if (activityToLog != null && stateToLog != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) {
- logAppCompatStateInternal(activityToLog, stateToLog, packageUid, compatStateInfo);
+ logAppCompatStateInternal(activityToLog, stateToLog, compatStateInfo);
}
}
+ private static boolean isAppCompateStateChangedToLetterboxed(int state) {
+ return state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO
+ || state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION
+ || state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
+ }
+
private void logAppCompatStateInternal(@NonNull ActivityRecord activity, int state,
- int packageUid, PackageCompatStateInfo compatStateInfo) {
+ PackageCompatStateInfo compatStateInfo) {
compatStateInfo.mLastLoggedState = state;
compatStateInfo.mLastLoggedActivity = activity;
- FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED, packageUid, state);
+ int packageUid = activity.info.applicationInfo.uid;
+
+ int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
+ if (isAppCompateStateChangedToLetterboxed(state)) {
+ positionToLog = activity.mLetterboxUiController.getLetterboxPositionForLogging();
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED,
+ packageUid, state, positionToLog);
+
+ if (DEBUG_METRICS) {
+ Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s, %s)",
+ packageUid, state, positionToLog));
+ }
+ }
+
+ /**
+ * Logs the changing of the letterbox position along with its package UID
+ */
+ void logLetterboxPositionChange(@NonNull ActivityRecord activity, int position) {
+ int packageUid = activity.info.applicationInfo.uid;
+ FrameworkStatsLog.write(FrameworkStatsLog.LETTERBOX_POSITION_CHANGED, packageUid, position);
+
+ if (!mPackageUidToCompatStateInfo.contains(packageUid)) {
+ // There is no last logged activity for this packageUid so we should not log the
+ // position change as we can only log the position change for the current activity
+ return;
+ }
+ final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid);
+ final ActivityRecord lastLoggedActivity = compatStateInfo.mLastLoggedActivity;
+ if (activity != lastLoggedActivity) {
+ // Only log the position change for the current activity to be consistent with
+ // findAppCompatStateToLog and ensure that metrics for the state changes are computed
+ // correctly
+ return;
+ }
+ int state = activity.getAppCompatState();
+ logAppCompatStateInternal(activity, state, compatStateInfo);
if (DEBUG_METRICS) {
- Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s)", packageUid, state));
+ Slog.i(TAG, String.format("LETTERBOX_POSITION_CHANGED(%s, %s)",
+ packageUid, position));
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b3b392c149a1..62427e1d8e52 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2467,8 +2467,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (!newTask && taskSwitch && processRunning && !activityCreated && task.intent != null
&& mActivityComponent.equals(task.intent.getComponent())) {
final ActivityRecord topAttached = task.getActivity(ActivityRecord::attachedToProcess);
- if (topAttached != null && topAttached.isSnapshotCompatible(snapshot)) {
- return STARTING_WINDOW_TYPE_SNAPSHOT;
+ if (topAttached != null) {
+ if (topAttached.isSnapshotCompatible(snapshot)
+ // This trampoline must be the same rotation.
+ && mDisplayContent.getDisplayRotation().rotationForOrientation(mOrientation,
+ mDisplayContent.getRotation()) == snapshot.getRotation()) {
+ return STARTING_WINDOW_TYPE_SNAPSHOT;
+ }
+ // No usable snapshot. And a splash screen may also be weird because an existing
+ // activity may be shown right after the trampoline is finished.
+ return STARTING_WINDOW_TYPE_NONE;
}
}
final boolean isActivityHome = isActivityTypeHome();
@@ -3207,12 +3215,29 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return false;
}
- if (mRootWindowContainer.getTopResumedActivity() == this
- && getDisplayContent().mFocusedApp == this) {
- ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: already on top, "
- + "activity=%s", this);
- return !isState(RESUMED);
+ // If this activity already positions on the top focused task, moving the task to front
+ // is not needed. But we still need to ensure this activity is focused because the
+ // current focused activity could be another activity in the same Task if activities are
+ // displayed on adjacent TaskFragments.
+ final ActivityRecord currentFocusedApp = mDisplayContent.mFocusedApp;
+ if (currentFocusedApp != null && currentFocusedApp.task == task) {
+ final Task topFocusableTask = mDisplayContent.getTask(
+ (t) -> t.isLeafTask() && t.isFocusable(), true /* traverseTopToBottom */);
+ if (task == topFocusableTask) {
+ if (currentFocusedApp == this) {
+ ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: already on top "
+ + "and focused, activity=%s", this);
+ } else {
+ ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: set focused, "
+ + "activity=%s", this);
+ mDisplayContent.setFocusedApp(this);
+ mAtmService.mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+ true /* updateInputWindows */);
+ }
+ return !isState(RESUMED);
+ }
}
+
ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: activity=%s", this);
rootTask.moveToFront(reason, task);
@@ -7788,11 +7813,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
newParentConfiguration.windowConfiguration.getWindowingMode();
final boolean isFixedOrientationLetterboxAllowed =
parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
- || parentWindowingMode == WINDOWING_MODE_FULLSCREEN;
+ || parentWindowingMode == WINDOWING_MODE_FULLSCREEN
+ // Switching from PiP to fullscreen.
+ || (parentWindowingMode == WINDOWING_MODE_PINNED
+ && resolvedConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN);
// TODO(b/181207944): Consider removing the if condition and always run
// resolveFixedOrientationConfiguration() since this should be applied for all cases.
if (isFixedOrientationLetterboxAllowed) {
- resolveFixedOrientationConfiguration(newParentConfiguration, parentWindowingMode);
+ resolveFixedOrientationConfiguration(newParentConfiguration);
}
if (mCompatDisplayInsets != null) {
@@ -8084,8 +8113,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied
* in this method.
*/
- private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig,
- int windowingMode) {
+ private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
mIsEligibleForFixedOrientationLetterbox = false;
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
@@ -8105,11 +8133,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (organizedTf != null && !organizedTf.fillsParent()) {
return;
}
- if (windowingMode == WINDOWING_MODE_PINNED) {
- // PiP bounds have higher priority than the requested orientation. Otherwise the
- // activity may be squeezed into a small piece.
- return;
- }
final Rect resolvedBounds =
getResolvedOverrideConfiguration().windowConfiguration.getBounds();
@@ -8174,7 +8197,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
resolvedBounds.set(containingBounds);
final float letterboxAspectRatioOverride =
- mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+ mLetterboxUiController.getFixedOrientationLetterboxAspectRatio();
final float desiredAspectRatio =
letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
? letterboxAspectRatioOverride : computeAspectRatio(parentBounds);
@@ -8727,18 +8750,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* Returns the min aspect ratio of this activity.
*/
private float getMinAspectRatio() {
- float infoAspectRatio = info.getMinAspectRatio(getRequestedOrientation());
- // Complying with the CDD 7.1.1.2 requirement for unresizble apps:
- // https://source.android.com/compatibility/12/android-12-cdd#7112_screen_aspect_ratio
- return infoAspectRatio < 1f && info.resizeMode == RESIZE_MODE_UNRESIZEABLE
- // TODO(233582832): Consider removing fixed-orientation condition.
- // Some apps switching from tablet to phone layout at the certain size
- // threshold. This may lead to flickering on tablets in landscape orientation
- // if an app sets orientation to portrait dynamically because of aspect ratio
- // restriction applied here.
- && getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
- ? mLetterboxUiController.getDefaultMinAspectRatioForUnresizableApps()
- : infoAspectRatio;
+ return info.getMinAspectRatio(getRequestedOrientation());
}
/**
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index fc412cbdad72..a7c09a4f9739 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -79,6 +79,10 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import android.annotation.NonNull;
@@ -132,6 +136,7 @@ import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.NeededUriGrants;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
import com.android.server.wm.LaunchParamsController.LaunchParams;
+import com.android.server.wm.TaskFragment.EmbeddingCheckResult;
import java.io.PrintWriter;
import java.text.DateFormat;
@@ -2074,24 +2079,6 @@ class ActivityStarter {
}
}
- if (mInTaskFragment != null && !canEmbedActivity(mInTaskFragment, r, newTask, targetTask)) {
- final StringBuilder errorMsg = new StringBuilder("Permission denied: Cannot embed " + r
- + " to " + mInTaskFragment.getTask() + ". newTask=" + newTask + ", targetTask= "
- + targetTask);
- if (newTask && isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE,
- LAUNCH_SINGLE_INSTANCE_PER_TASK, LAUNCH_SINGLE_TASK)) {
- errorMsg.append("\nActivity tries to launch on a new task because the launch mode"
- + " is " + launchModeToString(mLaunchMode));
- } else if (newTask && (mLaunchFlags & (FLAG_ACTIVITY_NEW_DOCUMENT
- | FLAG_ACTIVITY_NEW_TASK)) != 0) {
- errorMsg.append("\nActivity tries to launch on a new task because the launch flags"
- + " contains FLAG_ACTIVITY_NEW_DOCUMENT or FLAG_ACTIVITY_NEW_TASK. "
- + "mLaunchFlag=" + mLaunchFlags);
- }
- Slog.e(TAG, errorMsg.toString());
- return START_PERMISSION_DENIED;
- }
-
// Do not start the activity if target display's DWPC does not allow it.
// We can't return fatal error code here because it will crash the caller of
// startActivity() if they don't catch the exception. We don't expect 3P apps to make
@@ -2118,19 +2105,21 @@ class ActivityStarter {
}
/**
- * Return {@code true} if an activity can be embedded to the TaskFragment.
+ * Returns whether embedding of {@code starting} is allowed.
+ *
* @param taskFragment the TaskFragment for embedding.
* @param starting the starting activity.
- * @param newTask whether the starting activity is going to be launched on a new task.
* @param targetTask the target task for launching activity, which could be different from
* the one who hosting the embedding.
*/
- private boolean canEmbedActivity(@NonNull TaskFragment taskFragment,
- @NonNull ActivityRecord starting, boolean newTask, Task targetTask) {
+ @VisibleForTesting
+ @EmbeddingCheckResult
+ static int canEmbedActivity(@NonNull TaskFragment taskFragment,
+ @NonNull ActivityRecord starting, @NonNull Task targetTask) {
final Task hostTask = taskFragment.getTask();
// Not allowed embedding a separate task or without host task.
- if (hostTask == null || newTask || targetTask != hostTask) {
- return false;
+ if (hostTask == null || targetTask != hostTask) {
+ return EMBEDDING_DISALLOWED_NEW_TASK;
}
return taskFragment.isAllowedToEmbedActivity(starting);
@@ -2576,6 +2565,7 @@ class ActivityStarter {
mInTask = null;
}
mInTaskFragment = inTaskFragment;
+ sendNewTaskFragmentResultRequestIfNeeded();
mStartFlags = startFlags;
// If the onlyIfNeeded flag is set, then we can do this if the activity being launched
@@ -2618,6 +2608,18 @@ class ActivityStarter {
}
}
+ private void sendNewTaskFragmentResultRequestIfNeeded() {
+ if (mStartActivity.resultTo != null && mInTaskFragment != null
+ && mInTaskFragment != mStartActivity.resultTo.getTaskFragment()) {
+ Slog.w(TAG,
+ "Activity is launching as a new TaskFragment, so cancelling activity result.");
+ mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho,
+ mStartActivity.requestCode, RESULT_CANCELED,
+ null /* data */, null /* dataGrants */);
+ mStartActivity.resultTo = null;
+ }
+ }
+
private void computeLaunchingTaskFlags() {
// If the caller is not coming from another activity, but has given us an explicit task into
// which they would like us to launch the new activity, then let's see about doing that.
@@ -2957,19 +2959,16 @@ class ActivityStarter {
mIntentDelivered = true;
}
+ /** Places {@link #mStartActivity} in {@code task} or an embedded {@link TaskFragment}. */
private void addOrReparentStartingActivity(@NonNull Task task, String reason) {
TaskFragment newParent = task;
if (mInTaskFragment != null) {
- // TODO(b/234351413): remove remaining embedded Task logic.
- // mInTaskFragment is created and added to the leaf task by task fragment organizer's
- // request. If the task was resolved and different than mInTaskFragment, reparent the
- // task to mInTaskFragment for embedding.
- if (mInTaskFragment.getTask() != task) {
- if (shouldReparentInTaskFragment(task)) {
- task.reparent(mInTaskFragment, POSITION_TOP);
- }
- } else {
+ int embeddingCheckResult = canEmbedActivity(mInTaskFragment, mStartActivity, task);
+ if (embeddingCheckResult == EMBEDDING_ALLOWED) {
newParent = mInTaskFragment;
+ } else {
+ // Start mStartActivity to task instead if it can't be embedded to mInTaskFragment.
+ sendCanNotEmbedActivityError(mInTaskFragment, embeddingCheckResult);
}
} else {
TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null;
@@ -2981,20 +2980,12 @@ class ActivityStarter {
}
}
if (candidateTf != null && candidateTf.isEmbedded()
- && canEmbedActivity(candidateTf, mStartActivity, false /* newTask */, task)) {
+ && canEmbedActivity(candidateTf, mStartActivity, task) == EMBEDDING_ALLOWED) {
// Use the embedded TaskFragment of the top activity as the new parent if the
// activity can be embedded.
newParent = candidateTf;
}
}
- // Start Activity to the Task if mStartActivity's min dimensions are not satisfied.
- if (newParent.isEmbedded() && newParent.smallerThanMinDimension(mStartActivity)) {
- reason += " - MinimumDimensionViolation";
- mService.mWindowOrganizerController.sendMinimumDimensionViolation(
- newParent, mStartActivity.getMinDimensions(), mRequest.errorCallbackToken,
- reason);
- newParent = task;
- }
if (mStartActivity.getTaskFragment() == null
|| mStartActivity.getTaskFragment() == newParent) {
newParent.addChild(mStartActivity, POSITION_TOP);
@@ -3003,16 +2994,41 @@ class ActivityStarter {
}
}
- private boolean shouldReparentInTaskFragment(Task task) {
- // The task has not been embedded. We should reparent the task to TaskFragment.
- if (!task.isEmbedded()) {
- return true;
+ /**
+ * Notifies the client side that {@link #mStartActivity} cannot be embedded to
+ * {@code taskFragment}.
+ */
+ private void sendCanNotEmbedActivityError(TaskFragment taskFragment,
+ @EmbeddingCheckResult int result) {
+ final String errMsg;
+ switch(result) {
+ case EMBEDDING_DISALLOWED_NEW_TASK: {
+ errMsg = "Cannot embed " + mStartActivity + " that launched on another task"
+ + ",mLaunchMode=" + launchModeToString(mLaunchMode)
+ + ",mLaunchFlag=" + Integer.toHexString(mLaunchFlags);
+ break;
+ }
+ case EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION: {
+ errMsg = "Cannot embed " + mStartActivity
+ + ". TaskFragment's bounds:" + taskFragment.getBounds()
+ + ", minimum dimensions:" + mStartActivity.getMinDimensions();
+ break;
+ }
+ case EMBEDDING_DISALLOWED_UNTRUSTED_HOST: {
+ errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity;
+ break;
+ }
+ default:
+ errMsg = "Unhandled embed result:" + result;
+ }
+ if (taskFragment.isOrganized()) {
+ mService.mWindowOrganizerController.sendTaskFragmentOperationFailure(
+ taskFragment.getTaskFragmentOrganizer(), mRequest.errorCallbackToken,
+ new SecurityException(errMsg));
+ } else {
+ // If the taskFragment is not organized, just dump error message as warning logs.
+ Slog.w(TAG, errMsg);
}
- WindowContainer<?> parent = task.getParent();
- // If the Activity is going to launch on top of embedded Task in the same TaskFragment,
- // we don't need to reparent the Task. Otherwise, the embedded Task should reparent to
- // another TaskFragment.
- return parent.asTaskFragment() != mInTaskFragment;
}
private int adjustLaunchFlagsToDocumentMode(ActivityRecord r, boolean launchSingleInstance,
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 3d661222d226..fb9d7e602210 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -116,7 +116,6 @@ public class AppTransitionController {
private final DisplayContent mDisplayContent;
private final WallpaperController mWallpaperControllerLocked;
private RemoteAnimationDefinition mRemoteAnimationDefinition = null;
- private static final int KEYGUARD_GOING_AWAY_ANIMATION_DURATION = 400;
private static final int TYPE_NONE = 0;
private static final int TYPE_ACTIVITY = 1;
@@ -727,14 +726,17 @@ public class AppTransitionController {
*/
private void overrideWithRemoteAnimationIfSet(@Nullable ActivityRecord animLpActivity,
@TransitionOldType int transit, ArraySet<Integer> activityTypes) {
+ RemoteAnimationAdapter adapter = null;
if (transit == TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE) {
// The crash transition has higher priority than any involved remote animations.
- return;
- }
- final RemoteAnimationAdapter adapter =
- getRemoteAnimationOverride(animLpActivity, transit, activityTypes);
- if (adapter != null
- && mDisplayContent.mAppTransition.getRemoteAnimationController() == null) {
+ } else if (AppTransition.isKeyguardGoingAwayTransitOld(transit)) {
+ adapter = mRemoteAnimationDefinition != null
+ ? mRemoteAnimationDefinition.getAdapter(transit, activityTypes)
+ : null;
+ } else if (mDisplayContent.mAppTransition.getRemoteAnimationController() == null) {
+ adapter = getRemoteAnimationOverride(animLpActivity, transit, activityTypes);
+ }
+ if (adapter != null) {
mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(adapter);
}
}
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index fca4942d4b79..fff7637acc7e 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -63,6 +63,7 @@ final class ContentRecordingController {
*/
void setContentRecordingSessionLocked(@Nullable ContentRecordingSession incomingSession,
@NonNull WindowManagerService wmService) {
+ // TODO(b/219761722) handle a null session arriving due to task setup failing
if (incomingSession != null && (!ContentRecordingSession.isValid(incomingSession)
|| ContentRecordingSession.isSameDisplay(mSession, incomingSession))) {
// Ignore an invalid session, or a session for the same display as currently recording.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ad3b8ee119d3..288777bdb324 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1610,7 +1610,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (mTransitionController.useShellTransitionsRotation()) {
return ROTATION_UNDEFINED;
}
- if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) {
+ if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM
+ || getIgnoreOrientationRequest()) {
return ROTATION_UNDEFINED;
}
if (r.mOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
@@ -2941,9 +2942,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// Set some sort of reasonable bounds on the size of the display that we will try
// to emulate.
final int minSize = 200;
- final int maxScale = 2;
- width = Math.min(Math.max(width, minSize), mInitialDisplayWidth * maxScale);
- height = Math.min(Math.max(height, minSize), mInitialDisplayHeight * maxScale);
+ final int maxScale = 3;
+ final int maxSize = Math.max(mInitialDisplayWidth, mInitialDisplayHeight) * maxScale;
+ width = Math.min(Math.max(width, minSize), maxSize);
+ height = Math.min(Math.max(height, minSize), maxSize);
}
Slog.i(TAG_WM, "Using new display size: " + width + "x" + height);
@@ -6193,6 +6195,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
.getKeyguardController().isAodShowing(mDisplayId);
}
+ /**
+ * @return whether the keyguard is occluded on this display
+ */
+ boolean isKeyguardOccluded() {
+ return mRootWindowContainer.mTaskSupervisor
+ .getKeyguardController().isDisplayOccluded(mDisplayId);
+ }
+
@VisibleForTesting
void removeAllTasks() {
forAllTasks((t) -> { t.getRootTask().removeChild(t, "removeAllTasks"); });
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5c1fc653586e..cff8b93ac947 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1212,7 +1212,8 @@ public class DisplayPolicy {
break;
default:
if (attrs.providedInsets != null) {
- for (InsetsFrameProvider provider : attrs.providedInsets) {
+ for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
+ final InsetsFrameProvider provider = attrs.providedInsets[i];
switch (provider.type) {
case ITYPE_STATUS_BAR:
mStatusBarAlt = win;
@@ -1231,21 +1232,29 @@ public class DisplayPolicy {
mExtraNavBarAltPosition = getAltBarPosition(attrs);
break;
}
+ // The index of the provider and corresponding insets types cannot change at
+ // runtime as ensured in WMS. Make use of the index in the provider directly
+ // to access the latest provided size at runtime.
+ final int index = i;
final TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider =
provider.insetsSize != null
? (displayFrames, windowContainer, inOutFrame) -> {
inOutFrame.inset(win.mGivenContentInsets);
+ final InsetsFrameProvider ifp =
+ win.mAttrs.forRotation(displayFrames.mRotation)
+ .providedInsets[index];
calculateInsetsFrame(displayFrames, windowContainer,
- inOutFrame, provider.source,
- provider.insetsSize);
+ inOutFrame, ifp.source, ifp.insetsSize);
} : null;
final TriConsumer<DisplayFrames, WindowContainer, Rect> imeFrameProvider =
provider.imeInsetsSize != null
? (displayFrames, windowContainer, inOutFrame) -> {
inOutFrame.inset(win.mGivenContentInsets);
+ final InsetsFrameProvider ifp =
+ win.mAttrs.forRotation(displayFrames.mRotation)
+ .providedInsets[index];
calculateInsetsFrame(displayFrames, windowContainer,
- inOutFrame, provider.source,
- provider.imeInsetsSize);
+ inOutFrame, ifp.source, ifp.imeInsetsSize);
} : null;
mDisplayContent.setInsetProvider(provider.type, win, frameProvider,
imeFrameProvider);
@@ -1256,14 +1265,14 @@ public class DisplayPolicy {
}
}
- private void calculateInsetsFrame(DisplayFrames df, WindowContainer coutainer, Rect inOutFrame,
+ private void calculateInsetsFrame(DisplayFrames df, WindowContainer container, Rect inOutFrame,
int source, Insets insetsSize) {
if (source == InsetsFrameProvider.SOURCE_DISPLAY) {
inOutFrame.set(df.mUnrestricted);
} else if (source == InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS) {
- inOutFrame.set(coutainer.getBounds());
+ inOutFrame.set(container.getBounds());
}
- if (insetsSize == null || insetsSize.equals(Insets.NONE)) {
+ if (insetsSize == null) {
return;
}
// Only one side of the provider shall be applied. Check in the order of left - top -
@@ -1276,6 +1285,8 @@ public class DisplayPolicy {
inOutFrame.left = inOutFrame.right - insetsSize.right;
} else if (insetsSize.bottom != 0) {
inOutFrame.top = inOutFrame.bottom - insetsSize.bottom;
+ } else {
+ inOutFrame.setEmpty();
}
}
@@ -1523,13 +1534,14 @@ public class DisplayPolicy {
*/
void simulateLayoutDisplay(DisplayFrames displayFrames) {
final InsetsStateController controller = mDisplayContent.getInsetsStateController();
+ sTmpClientFrames.attachedFrame = null;
for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) {
final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i);
mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation),
displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe,
displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
- UNSPECIFIED_LENGTH, win.getRequestedVisibilities(),
- null /* attachedWindowFrame */, win.mGlobalScale, sTmpClientFrames);
+ UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale,
+ sTmpClientFrames);
final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources();
final InsetsState state = displayFrames.mInsetsState;
for (int index = sources.size() - 1; index >= 0; index--) {
@@ -1541,13 +1553,14 @@ public class DisplayPolicy {
}
void updateInsetsSourceFramesExceptIme(DisplayFrames displayFrames) {
+ sTmpClientFrames.attachedFrame = null;
for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) {
final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i);
mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation),
displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe,
displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
- UNSPECIFIED_LENGTH, win.getRequestedVisibilities(),
- null /* attachedWindowFrame */, win.mGlobalScale, sTmpClientFrames);
+ UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale,
+ sTmpClientFrames);
win.updateSourceFrame(sTmpClientFrames.frame);
}
}
@@ -1577,7 +1590,7 @@ public class DisplayPolicy {
displayFrames = win.getDisplayFrames(displayFrames);
final WindowManager.LayoutParams attrs = win.mAttrs.forRotation(displayFrames.mRotation);
- final Rect attachedWindowFrame = attached != null ? attached.getFrame() : null;
+ sTmpClientFrames.attachedFrame = attached != null ? attached.getFrame() : null;
// If this window has different LayoutParams for rotations, we cannot trust its requested
// size. Because it might have not sent its requested size for the new rotation.
@@ -1587,8 +1600,7 @@ public class DisplayPolicy {
mWindowLayout.computeFrames(attrs, win.getInsetsState(), displayFrames.mDisplayCutoutSafe,
win.getBounds(), win.getWindowingMode(), requestedWidth, requestedHeight,
- win.getRequestedVisibilities(), attachedWindowFrame, win.mGlobalScale,
- sTmpClientFrames);
+ win.getRequestedVisibilities(), win.mGlobalScale, sTmpClientFrames);
win.setFrames(sTmpClientFrames, win.mRequestedWidth, win.mRequestedHeight);
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index d2c71f57e701..b9d83198139d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -620,7 +620,8 @@ public class DisplayRotation {
// We only enable seamless rotation if the top window has requested it and is in the
// fullscreen opaque state. Seamless rotation requires freezing various Surface states and
// won't work well with animations, so we disable it in the animation case for now.
- if (w.getAttrs().rotationAnimation != ROTATION_ANIMATION_SEAMLESS || w.isAnimatingLw()) {
+ if (w.getAttrs().rotationAnimation != ROTATION_ANIMATION_SEAMLESS || w.inMultiWindowMode()
+ || w.isAnimatingLw()) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 2d227b66b3ce..91b2fb63a543 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -37,11 +37,6 @@ final class LetterboxConfiguration {
*/
static final float MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO = 1.0f;
- // Min allowed aspect ratio for unresizable apps which is used when an app doesn't specify
- // android:minAspectRatio in accordance with the CDD 7.1.1.2 requirement:
- // https://source.android.com/compatibility/12/android-12-cdd#7112_screen_aspect_ratio
- static final float MIN_UNRESIZABLE_ASPECT_RATIO = 4 / 3f;
-
/** Enum for Letterbox background type. */
@Retention(RetentionPolicy.SOURCE)
@IntDef({LETTERBOX_BACKGROUND_SOLID_COLOR, LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND,
@@ -109,9 +104,7 @@ final class LetterboxConfiguration {
// MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored.
private float mFixedOrientationLetterboxAspectRatio;
- // Default min aspect ratio for unresizable apps which is used when an app doesn't specify
- // android:minAspectRatio in accordance with the CDD 7.1.1.2 requirement:
- // https://source.android.com/compatibility/12/android-12-cdd#7112_screen_aspect_ratio
+ // Default min aspect ratio for unresizable apps that are eligible for the size compat mode.
private float mDefaultMinAspectRatioForUnresizableApps;
// Corners radius for activities presented in the letterbox mode, values < 0 will be ignored.
@@ -250,13 +243,7 @@ final class LetterboxConfiguration {
}
/**
- * Resets the min aspect ratio for unresizable apps which is used when an app doesn't specify
- * {@code android:minAspectRatio} to {@link
- * R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps}.
- *
- * @throws AssertionError if {@link
- * R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps} is < {@link
- * #MIN_UNRESIZABLE_ASPECT_RATIO}.
+ * Resets the min aspect ratio for unresizable apps that are eligible for size compat mode.
*/
void resetDefaultMinAspectRatioForUnresizableApps() {
setDefaultMinAspectRatioForUnresizableApps(mContext.getResources().getFloat(
@@ -264,25 +251,16 @@ final class LetterboxConfiguration {
}
/**
- * Gets the min aspect ratio for unresizable apps which is used when an app doesn't specify
- * {@code android:minAspectRatio}.
+ * Gets the min aspect ratio for unresizable apps that are eligible for size compat mode.
*/
float getDefaultMinAspectRatioForUnresizableApps() {
return mDefaultMinAspectRatioForUnresizableApps;
}
/**
- * Overrides the min aspect ratio for unresizable apps which is used when an app doesn't
- * specify {@code android:minAspectRatio}.
- *
- * @throws AssertionError if given value is < {@link #MIN_UNRESIZABLE_ASPECT_RATIO}.
+ * Overrides the min aspect ratio for unresizable apps that are eligible for size compat mode.
*/
void setDefaultMinAspectRatioForUnresizableApps(float aspectRatio) {
- if (aspectRatio < MIN_UNRESIZABLE_ASPECT_RATIO) {
- throw new AssertionError(
- "Unexpected min aspect ratio for unresizable apps, it should be <= "
- + MIN_UNRESIZABLE_ASPECT_RATIO + " but was " + aspectRatio);
- }
mDefaultMinAspectRatioForUnresizableApps = aspectRatio;
}
@@ -709,6 +687,24 @@ final class LetterboxConfiguration {
}
}
+ /*
+ * Gets the horizontal position of the letterboxed app window when horizontal reachability is
+ * enabled.
+ */
+ @LetterboxHorizontalReachabilityPosition
+ int getLetterboxPositionForHorizontalReachability() {
+ return mLetterboxPositionForHorizontalReachability;
+ }
+
+ /*
+ * Gets the vertical position of the letterboxed app window when vertical reachability is
+ * enabled.
+ */
+ @LetterboxVerticalReachabilityPosition
+ int getLetterboxPositionForVerticalReachability() {
+ return mLetterboxPositionForVerticalReachability;
+ }
+
/** Returns a string representing the given {@link LetterboxHorizontalReachabilityPosition}. */
static String letterboxHorizontalReachabilityPositionToString(
@LetterboxHorizontalReachabilityPosition int position) {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index df9a87ea1ab0..d65276793700 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -21,6 +21,20 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
import static com.android.server.wm.ActivityRecord.computeAspectRatio;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -28,6 +42,13 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString;
import android.annotation.Nullable;
@@ -211,10 +232,19 @@ final class LetterboxUiController {
: mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier();
}
- float getDefaultMinAspectRatioForUnresizableApps() {
+ float getFixedOrientationLetterboxAspectRatio() {
+ return mActivityRecord.shouldCreateCompatDisplayInsets()
+ ? getDefaultMinAspectRatioForUnresizableApps()
+ : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+ }
+
+ private float getDefaultMinAspectRatioForUnresizableApps() {
if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()
|| mActivityRecord.getDisplayContent() == null) {
- return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps();
+ return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
+ > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
+ ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
+ : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
}
int dividerWindowWidth =
@@ -226,10 +256,10 @@ final class LetterboxUiController {
// Getting the same aspect ratio that apps get in split screen.
Rect bounds = new Rect(mActivityRecord.getDisplayContent().getBounds());
if (bounds.width() >= bounds.height()) {
- bounds.inset(/* dx */ dividerSize, /* dy */ 0);
+ bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
bounds.right = bounds.centerX();
} else {
- bounds.inset(/* dx */ 0, /* dy */ dividerSize);
+ bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2);
bounds.bottom = bounds.centerY();
}
return computeAspectRatio(bounds);
@@ -249,12 +279,26 @@ final class LetterboxUiController {
return;
}
+ int letterboxPositionForHorizontalReachability = mLetterboxConfiguration
+ .getLetterboxPositionForHorizontalReachability();
if (mLetterbox.getInnerFrame().left > x) {
// Moving to the next stop on the left side of the app window: right > center > left.
mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextLeftStop();
+ int changeToLog =
+ letterboxPositionForHorizontalReachability
+ == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
} else if (mLetterbox.getInnerFrame().right < x) {
// Moving to the next stop on the right side of the app window: left > center > right.
mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
+ int changeToLog =
+ letterboxPositionForHorizontalReachability
+ == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
}
// TODO(197549949): Add animation for transition.
@@ -270,13 +314,26 @@ final class LetterboxUiController {
// Only react to clicks at the top and bottom of the letterboxed app window.
return;
}
-
+ int letterboxPositionForVerticalReachability = mLetterboxConfiguration
+ .getLetterboxPositionForVerticalReachability();
if (mLetterbox.getInnerFrame().top > y) {
// Moving to the next stop on the top side of the app window: bottom > center > top.
mLetterboxConfiguration.movePositionForVerticalReachabilityToNextTopStop();
+ int changeToLog =
+ letterboxPositionForVerticalReachability
+ == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
} else if (mLetterbox.getInnerFrame().bottom < y) {
// Moving to the next stop on the bottom side of the app window: top > center > bottom.
mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
+ int changeToLog =
+ letterboxPositionForVerticalReachability
+ == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
}
// TODO(197549949): Add animation for transition.
@@ -567,4 +624,63 @@ final class LetterboxUiController {
return "UNKNOWN_REASON";
}
+ private int letterboxHorizontalReachabilityPositionToLetterboxPosition(
+ @LetterboxConfiguration.LetterboxHorizontalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox horizontal reachability position type: "
+ + position);
+ }
+ }
+
+ private int letterboxVerticalReachabilityPositionToLetterboxPosition(
+ @LetterboxConfiguration.LetterboxVerticalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox vertical reachability position type: "
+ + position);
+ }
+ }
+
+ int getLetterboxPositionForLogging() {
+ int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+ if (isHorizontalReachabilityEnabled()) {
+ int letterboxPositionForHorizontalReachability = getLetterboxConfiguration()
+ .getLetterboxPositionForHorizontalReachability();
+ positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition(
+ letterboxPositionForHorizontalReachability);
+ } else if (isVerticalReachabilityEnabled()) {
+ int letterboxPositionForVerticalReachability = getLetterboxConfiguration()
+ .getLetterboxPositionForVerticalReachability();
+ positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition(
+ letterboxPositionForVerticalReachability);
+ }
+ return positionToLog;
+ }
+
+ private LetterboxConfiguration getLetterboxConfiguration() {
+ return mLetterboxConfiguration;
+ }
+
+ /**
+ * Logs letterbox position changes via {@link ActivityMetricsLogger#logLetterboxPositionChange}.
+ */
+ private void logLetterboxPositionChange(int letterboxPositionChange) {
+ mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
+ .logLetterboxPositionChange(mActivityRecord, letterboxPositionChange);
+ }
}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index ad158c7b45b9..ac1a2b17603a 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -331,8 +331,10 @@ class RemoteAnimationController implements DeathRecipient {
private void invokeAnimationCancelled(String reason) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "cancelAnimation(): reason=%s", reason);
+ final boolean isKeyguardOccluded = mDisplayContent.isKeyguardOccluded();
+
try {
- mRemoteAnimationAdapter.getRunner().onAnimationCancelled();
+ mRemoteAnimationAdapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to notify cancel", e);
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e4337f238498..db730e0cb368 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1239,11 +1239,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
@Override
- void scheduleAnimation() {
- mWmService.scheduleAnimationLocked();
- }
-
- @Override
protected void removeChild(DisplayContent dc) {
super.removeChild(dc);
if (mTopFocusedDisplayId == dc.getDisplayId()) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 30b50839cd35..9b013dac6adf 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -115,8 +115,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
private float mLastReportedAnimatorScale;
private String mPackageName;
private String mRelayoutTag;
- private String mUpdateViewVisibilityTag;
- private String mUpdateWindowLayoutTag;
private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities();
private final InsetsSourceControl[] mDummyControls = new InsetsSourceControl[0];
final boolean mSetsUnrestrictedKeepClearAreas;
@@ -195,27 +193,28 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,
- outActiveControls);
+ outActiveControls, outAttachedFrame);
}
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
- requestedVisibilities, outInputChannel, outInsetsState, outActiveControls);
+ requestedVisibilities, outInputChannel, outInsetsState, outActiveControls,
+ outAttachedFrame);
}
@Override
public int addToDisplayWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, InsetsState outInsetsState) {
+ int viewVisibility, int displayId, InsetsState outInsetsState, Rect outAttachedFrame) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
UserHandle.getUserId(mUid), mDummyRequestedVisibilities, null /* outInputChannel */,
- outInsetsState, mDummyControls);
+ outInsetsState, mDummyControls, outAttachedFrame);
}
@Override
@@ -224,29 +223,13 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
@Override
- public int updateVisibility(IWindow client, WindowManager.LayoutParams attrs,
- int viewVisibility, MergedConfiguration outMergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mUpdateViewVisibilityTag);
- int res = mService.updateViewVisibility(this, client, attrs, viewVisibility,
- outMergedConfiguration, outSurfaceControl, outInsetsState, outActiveControls);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- return res;
- }
-
- @Override
- public void updateLayout(IWindow window, WindowManager.LayoutParams attrs, int flags,
- ClientWindowFrames clientFrames, int requestedWidth, int requestedHeight) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mUpdateWindowLayoutTag);
- mService.updateWindowLayout(this, window, attrs, flags, clientFrames, requestedWidth,
- requestedHeight);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) {
+ mService.setWillReplaceWindows(appToken, childrenOnly);
}
@Override
- public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) {
- mService.setWillReplaceWindows(appToken, childrenOnly);
+ public boolean cancelDraw(IWindow window) {
+ return mService.cancelDraw(this, window);
}
@Override
@@ -711,8 +694,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
if (wpc != null) {
mPackageName = wpc.mInfo.packageName;
mRelayoutTag = "relayoutWindow: " + mPackageName;
- mUpdateViewVisibilityTag = "updateVisibility: " + mPackageName;
- mUpdateWindowLayoutTag = "updateLayout: " + mPackageName;
} else {
Slog.e(TAG_WM, "Unknown process pid=" + mPid);
}
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 68dbb0607ac1..0bb773ae5e41 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -158,14 +158,13 @@ public class StartingSurfaceController {
+ topFullscreenActivity);
return null;
}
- if (topFullscreenActivity.getWindowConfiguration().getRotation()
- != taskSnapshot.getRotation()) {
+ if (activity.mDisplayContent.getRotation() != taskSnapshot.getRotation()) {
// The snapshot should have been checked by ActivityRecord#isSnapshotCompatible
// that the activity will be updated to the same rotation as the snapshot. Since
// the transition is not started yet, fixed rotation transform needs to be applied
// earlier to make the snapshot show in a rotated container.
activity.mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(
- topFullscreenActivity, false /* checkOpening */);
+ activity, false /* checkOpening */);
}
mService.mAtmService.mTaskOrganizerController.addStartingWindow(task,
activity, 0 /* launchTheme */, taskSnapshot);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 64905c8fe2f3..3d127742d7bd 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3624,30 +3624,35 @@ class Task extends TaskFragment {
}
@Override
+ String toFullString() {
+ final StringBuilder sb = new StringBuilder(192);
+ sb.append(this);
+ sb.setLength(sb.length() - 1); // Remove tail '}'.
+ sb.append(" U=");
+ sb.append(mUserId);
+ final Task rootTask = getRootTask();
+ if (rootTask != this) {
+ sb.append(" rootTaskId=");
+ sb.append(rootTask.mTaskId);
+ }
+ sb.append(" visible=");
+ sb.append(shouldBeVisible(null /* starting */));
+ sb.append(" visibleRequested=");
+ sb.append(isVisibleRequested());
+ sb.append(" mode=");
+ sb.append(windowingModeToString(getWindowingMode()));
+ sb.append(" translucent=");
+ sb.append(isTranslucent(null /* starting */));
+ sb.append(" sz=");
+ sb.append(getChildCount());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
public String toString() {
+ if (stringName != null) return stringName;
StringBuilder sb = new StringBuilder(128);
- if (stringName != null) {
- sb.append(stringName);
- sb.append(" U=");
- sb.append(mUserId);
- final Task rootTask = getRootTask();
- if (rootTask != this) {
- sb.append(" rootTaskId=");
- sb.append(rootTask.mTaskId);
- }
- sb.append(" visible=");
- sb.append(shouldBeVisible(null /* starting */));
- sb.append(" visibleRequested=");
- sb.append(isVisibleRequested());
- sb.append(" mode=");
- sb.append(windowingModeToString(getWindowingMode()));
- sb.append(" translucent=");
- sb.append(isTranslucent(null /* starting */));
- sb.append(" sz=");
- sb.append(getChildCount());
- sb.append('}');
- return sb.toString();
- }
sb.append("Task{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" #");
@@ -3662,11 +3667,9 @@ class Task extends TaskFragment {
} else if (affinityIntent != null && affinityIntent.getComponent() != null) {
sb.append(" aI=");
sb.append(affinityIntent.getComponent().flattenToShortString());
- } else {
- sb.append(" ??");
}
- stringName = sb.toString();
- return toString();
+ sb.append('}');
+ return stringName = sb.toString();
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 02e796904b16..8220cae74dc8 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1114,7 +1114,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
// If a task is launching from a created-by-organizer task, it should be launched into the
// same created-by-organizer task as well. Unless, the candidate task is already positioned
// in the another adjacent task.
- if (sourceTask != null) {
+ if (sourceTask != null && (candidateTask == null
+ // A pinned task relaunching should be handled by its task organizer. Skip fallback
+ // launch target of a pinned task from source task.
+ || candidateTask.getWindowingMode() != WINDOWING_MODE_PINNED)) {
Task launchTarget = sourceTask.getCreatedByOrganizerTask();
if (launchTarget != null && launchTarget.getAdjacentTaskFragment() != null) {
if (candidateTask != null) {
@@ -1937,7 +1940,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
continue;
}
final Task rootTask = child.asTask();
- pw.println(doublePrefix + "* " + rootTask);
+ pw.println(doublePrefix + "* " + rootTask.toFullString());
rootTask.dump(pw, triplePrefix, dumpAll);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 391077636654..f8a9d4665acc 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -140,6 +140,45 @@ class TaskFragment extends WindowContainer<WindowContainer> {
static final boolean SHOW_APP_STARTING_PREVIEW = true;
/**
+ * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
+ * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+ * indicate that an Activity can be embedded successfully.
+ */
+ static final int EMBEDDING_ALLOWED = 0;
+ /**
+ * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
+ * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+ * indicate that an Activity can't be embedded because either the Activity does not allow
+ * untrusted embedding, and the embedding host app is not trusted.
+ */
+ static final int EMBEDDING_DISALLOWED_UNTRUSTED_HOST = 1;
+ /**
+ * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
+ * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+ * indicate that an Activity can't be embedded because this taskFragment's bounds are
+ * {@link #smallerThanMinDimension(ActivityRecord)}.
+ */
+ static final int EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION = 2;
+ /**
+ * An embedding check result of
+ * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+ * indicate that an Activity can't be embedded because the Activity is started on a new task.
+ */
+ static final int EMBEDDING_DISALLOWED_NEW_TASK = 3;
+
+ /**
+ * Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
+ * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}.
+ */
+ @IntDef(prefix = {"EMBEDDING_"}, value = {
+ EMBEDDING_ALLOWED,
+ EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
+ EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
+ EMBEDDING_DISALLOWED_NEW_TASK,
+ })
+ @interface EmbeddingCheckResult {}
+
+ /**
* Indicate that the minimal width/height should use the default value.
*
* @see #mMinWidth
@@ -509,20 +548,29 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return false;
}
- boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a) {
+ @EmbeddingCheckResult
+ int isAllowedToEmbedActivity(@NonNull ActivityRecord a) {
return isAllowedToEmbedActivity(a, mTaskFragmentOrganizerUid);
}
/**
* Checks if the organized task fragment is allowed to have the specified activity, which is
- * allowed if an activity allows embedding in untrusted mode, or if the trusted mode can be
- * enabled.
- * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
+ * allowed if an activity allows embedding in untrusted mode, if the trusted mode can be
+ * enabled, or if the organized task fragment bounds are not
+ * {@link #smallerThanMinDimension(ActivityRecord)}.
+ *
* @param uid uid of the TaskFragment organizer.
+ * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
*/
- boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) {
- return isAllowedToEmbedActivityInUntrustedMode(a)
- || isAllowedToEmbedActivityInTrustedMode(a, uid);
+ @EmbeddingCheckResult
+ int isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) {
+ if (!isAllowedToEmbedActivityInUntrustedMode(a)
+ && !isAllowedToEmbedActivityInTrustedMode(a, uid)) {
+ return EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
+ } else if (smallerThanMinDimension(a)) {
+ return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
+ }
+ return EMBEDDING_ALLOWED;
}
boolean smallerThanMinDimension(@NonNull ActivityRecord activity) {
@@ -539,9 +587,8 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
final int minWidth = minDimensions.x;
final int minHeight = minDimensions.y;
- final boolean smaller = taskFragBounds.width() < minWidth
+ return taskFragBounds.width() < minWidth
|| taskFragBounds.height() < minHeight;
- return smaller;
}
/**
@@ -598,7 +645,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// The system is trusted to embed other apps securely and for all users.
return UserHandle.getAppId(uid) == SYSTEM_UID
// Activities from the same UID can be embedded freely by the host.
- || uid == a.getUid();
+ || a.isUid(uid);
}
/**
@@ -2581,6 +2628,32 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
}
+ String toFullString() {
+ final StringBuilder sb = new StringBuilder(128);
+ sb.append(this);
+ sb.setLength(sb.length() - 1); // Remove tail '}'.
+ if (mTaskFragmentOrganizerUid != INVALID_UID) {
+ sb.append(" organizerUid=");
+ sb.append(mTaskFragmentOrganizerUid);
+ }
+ if (mTaskFragmentOrganizerProcessName != null) {
+ sb.append(" organizerProc=");
+ sb.append(mTaskFragmentOrganizerProcessName);
+ }
+ if (mAdjacentTaskFragment != null) {
+ sb.append(" adjacent=");
+ sb.append(mAdjacentTaskFragment);
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return "TaskFragment{" + Integer.toHexString(System.identityHashCode(this))
+ + " mode=" + WindowConfiguration.windowingModeToString(getWindowingMode()) + "}";
+ }
+
boolean dump(String prefix, FileDescriptor fd, PrintWriter pw, boolean dumpAll,
boolean dumpClient, String dumpPackage, final boolean needSep, Runnable header) {
boolean printed = false;
@@ -2619,7 +2692,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
void dumpInner(String prefix, PrintWriter pw, boolean dumpAll, String dumpPackage) {
- pw.print(prefix); pw.print("* "); pw.println(this);
+ pw.print(prefix); pw.print("* "); pw.println(toFullString());
final Rect bounds = getRequestedOverrideBounds();
if (!bounds.isEmpty()) {
pw.println(prefix + " mBounds=" + bounds);
@@ -2640,10 +2713,11 @@ class TaskFragment extends WindowContainer<WindowContainer> {
final String doublePrefix = prefix + " ";
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowContainer<?> child = mChildren.get(i);
- pw.println(prefix + "* " + child);
+ final TaskFragment tf = child.asTaskFragment();
+ pw.println(prefix + "* " + (tf != null ? tf.toFullString() : child));
// Only dump non-activity because full activity info is already printed by
// RootWindowContainer#dumpActivities.
- if (child.asActivityRecord() == null) {
+ if (tf != null) {
child.dump(pw, doublePrefix, dumpAll);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 9aff23ddde14..392d4c2f772b 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.window.TaskFragmentOrganizer.putExceptionInBundle;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
import android.annotation.IntDef;
@@ -235,7 +236,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
+ " is not in a task belong to the organizer app.");
return;
}
- if (!task.isAllowedToEmbedActivity(activity, mOrganizerUid)) {
+ if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
Slog.d(TAG, "Reparent activity=" + activity.token
+ " is not allowed to be embedded.");
return;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 814656db9fa0..534616fb7207 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -47,6 +47,7 @@ import android.graphics.RenderNode;
import android.hardware.HardwareBuffer;
import android.os.Environment;
import android.os.Handler;
+import android.os.Trace;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
@@ -391,8 +392,10 @@ class TaskSnapshotController {
SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task,
TaskSnapshot.Builder builder) {
Point taskSize = new Point();
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "createTaskSnapshot");
final SurfaceControl.ScreenshotHardwareBuffer taskSnapshot = createTaskSnapshot(task,
mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize, builder);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
builder.setTaskSize(taskSize);
return taskSnapshot;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3c0cac0079e8..ae61f24a7a54 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -149,9 +149,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
final @TransitionType int mType;
private int mSyncId = -1;
- // Used for tracking a Transition throughout a lifecycle (i.e. from STATE_COLLECTING to
- // STATE_FINISHED or STATE_ABORT), and should only be used for testing and debugging.
- private int mDebugId = -1;
private @TransitionFlags int mFlags;
private final TransitionController mController;
private final BLASTSyncEngine mSyncEngine;
@@ -295,11 +292,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return mSyncId;
}
- @VisibleForTesting
- int getDebugId() {
- return mDebugId;
- }
-
@TransitionFlags
int getFlags() {
return mFlags;
@@ -315,6 +307,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return mFinishTransaction;
}
+ private boolean isCollecting() {
+ return mState == STATE_COLLECTING || mState == STATE_STARTED;
+ }
+
/** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
void startCollecting(long timeoutMs) {
if (mState != STATE_PENDING) {
@@ -322,7 +318,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
mState = STATE_COLLECTING;
mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
- mDebugId = mSyncId;
mController.mTransitionTracer.logState(this);
}
@@ -353,7 +348,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
if (mState < STATE_COLLECTING) {
throw new IllegalStateException("Transition hasn't started collecting.");
}
- if (mSyncId < 0) return;
+ if (!isCollecting()) {
+ // Too late, transition already started playing, so don't collect.
+ return;
+ }
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
mSyncId, wc);
// "snapshot" all parents (as potential promotion targets). Do this before checking
@@ -403,7 +401,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
* or waiting until after the animation to close).
*/
void collectExistenceChange(@NonNull WindowContainer wc) {
- if (mSyncId < 0) return;
+ if (mState >= STATE_PLAYING) {
+ // Too late to collect. Don't check too-early here since `collect` will check that.
+ return;
+ }
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:"
+ " %s", mSyncId, wc);
collect(wc);
@@ -437,7 +438,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
*/
void setOverrideAnimation(TransitionInfo.AnimationOptions options,
@Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
- if (mSyncId < 0) return;
+ if (!isCollecting()) return;
mOverrideOptions = options;
sendRemoteCallback(mClientAnimationStartCallback);
mClientAnimationStartCallback = startCallback;
@@ -455,7 +456,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
* The transition will wait for all groups to be ready.
*/
void setReady(WindowContainer wc, boolean ready) {
- if (mSyncId < 0) return;
+ if (!isCollecting() || mSyncId < 0) return;
mReadyTracker.setReadyFrom(wc, ready);
applyReady();
}
@@ -473,7 +474,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
* @see ReadyTracker#setAllReady.
*/
void setAllReady() {
- if (mSyncId < 0) return;
+ if (!isCollecting() || mSyncId < 0) return;
mReadyTracker.setAllReady();
applyReady();
}
@@ -672,7 +673,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
SurfaceControl.Transaction inputSinkTransaction = null;
for (int i = 0; i < mParticipants.size(); ++i) {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
- if (ar == null || !ar.isVisible()) continue;
+ if (ar == null || !ar.isVisible() || ar.getParent() == null) continue;
if (inputSinkTransaction == null) {
inputSinkTransaction = new SurfaceControl.Transaction();
}
@@ -889,7 +890,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
// No player registered, so just finish/apply immediately
cleanUpOnFailure();
}
- mSyncId = -1;
mOverrideOptions = null;
reportStartReasonsToLogger();
@@ -1614,7 +1614,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
boolean getLegacyIsReady() {
- return (mState == STATE_STARTED || mState == STATE_COLLECTING) && mSyncId >= 0;
+ return isCollecting() && mSyncId >= 0;
}
static Transition fromBinder(IBinder binder) {
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index b1951e038177..c1927d864320 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -79,7 +79,7 @@ public class TransitionTracer {
final ProtoOutputStream outputStream = new ProtoOutputStream();
final long transitionEntryToken = outputStream.start(TRANSITION);
- outputStream.write(ID, transition.getDebugId());
+ outputStream.write(ID, transition.getSyncId());
outputStream.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
outputStream.write(TRANSITION_TYPE, transition.mType);
outputStream.write(STATE, transition.getState());
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 89c71c909641..33c0fe13af11 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2694,9 +2694,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* will be applied.
*/
void scheduleAnimation() {
- if (mParent != null) {
- mParent.scheduleAnimation();
- }
+ mWmService.scheduleAnimationLocked();
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a48140eb0e04..b5abd3258ad8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -48,6 +48,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
import static android.provider.Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR;
import static android.provider.Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH;
+import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
@@ -89,6 +90,7 @@ import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManagerGlobal.ADD_OKAY;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
@@ -287,6 +289,7 @@ import android.view.displayhash.VerifiedDisplayHash;
import android.window.ClientWindowFrames;
import android.window.ITaskFpsCallback;
import android.window.TaskSnapshot;
+import android.window.WindowContainerToken;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -1445,7 +1448,7 @@ public class WindowManagerService extends IWindowManager.Stub
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
Arrays.fill(outActiveControls, null);
int[] appOp = new int[1];
final boolean isRoundedCornerOverlay = (attrs.privateFlags
@@ -1860,6 +1863,13 @@ public class WindowManagerService extends IWindowManager.Stub
outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
getInsetsSourceControls(win, outActiveControls);
+
+ if (win.mLayoutAttached) {
+ outAttachedFrame.set(win.getParentWindow().getCompatFrame());
+ } else {
+ // Make this invalid which indicates a null attached frame.
+ outAttachedFrame.set(0, 0, -1, -1);
+ }
}
Binder.restoreCallingIdentity(origId);
@@ -2212,6 +2222,20 @@ public class WindowManagerService extends IWindowManager.Stub
== PackageManager.PERMISSION_GRANTED;
}
+ /**
+ * Returns whether this window can proceed with drawing or needs to retry later.
+ */
+ public boolean cancelDraw(Session session, IWindow client) {
+ synchronized (mGlobalLock) {
+ final WindowState win = windowForClientLocked(session, client, false);
+ if (win == null) {
+ return false;
+ }
+
+ return win.cancelAndRedraw();
+ }
+ }
+
public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility, int flags,
ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
@@ -2228,6 +2252,11 @@ public class WindowManagerService extends IWindowManager.Stub
if (win == null) {
return 0;
}
+
+ if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= win.mLastSeqIdSentToRelayout) {
+ result |= RELAYOUT_RES_CANCEL_AND_REDRAW;
+ }
+
final DisplayContent displayContent = win.getDisplayContent();
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
@@ -2642,29 +2671,6 @@ public class WindowManagerService extends IWindowManager.Stub
return result;
}
- int updateViewVisibility(Session session, IWindow client, LayoutParams attrs,
- int viewVisibility, MergedConfiguration outMergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
- // TODO(b/161810301): Finish the implementation.
- return 0;
- }
-
- void updateWindowLayout(Session session, IWindow client, LayoutParams attrs, int flags,
- ClientWindowFrames clientWindowFrames, int requestedWidth, int requestedHeight) {
- final long origId = Binder.clearCallingIdentity();
- synchronized (mGlobalLock) {
- final WindowState win = windowForClientLocked(session, client, false);
- if (win == null) {
- return;
- }
- win.setFrames(clientWindowFrames, requestedWidth, requestedHeight);
-
- // TODO(b/161810301): Finish the implementation.
- }
- Binder.restoreCallingIdentity(origId);
- }
-
public boolean outOfMemoryWindow(Session session, IWindow client) {
final long origId = Binder.clearCallingIdentity();
@@ -6041,9 +6047,7 @@ public class WindowManagerService extends IWindowManager.Stub
/** Note that Locked in this case is on mLayoutToAnim */
void scheduleAnimationLocked() {
- if (mAnimator != null) {
- mAnimator.scheduleAnimation();
- }
+ mAnimator.scheduleAnimation();
}
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
@@ -8279,6 +8283,26 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) {
synchronized (mGlobalLock) {
+ // Allow the controller to handle teardown or a non-task session.
+ if (incomingSession == null
+ || incomingSession.getContentToRecord() != RECORD_CONTENT_TASK) {
+ mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
+ WindowManagerService.this);
+ return;
+ }
+ // For a task session, find the activity identified by the launch cookie.
+ final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie(
+ incomingSession.getTokenToRecord());
+ if (wct == null) {
+ Slog.w(TAG, "Handling a new recording session; unable to find the "
+ + "WindowContainerToken");
+ mContentRecordingController.setContentRecordingSessionLocked(null,
+ WindowManagerService.this);
+ return;
+ }
+ // Replace the launch cookie in the session details with the task's
+ // WindowContainerToken.
+ incomingSession.setTokenToRecord(wct.asBinder());
mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
WindowManagerService.this);
}
@@ -8537,6 +8561,38 @@ public class WindowManagerService extends IWindowManager.Stub
}
/**
+ * Retrieve the {@link WindowContainerToken} of the task that contains the activity started
+ * with the given launch cookie.
+ *
+ * @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an
+ * activity
+ * @return a token representing the task containing the activity started with the given launch
+ * cookie, or {@code null} if the token couldn't be found.
+ */
+ @VisibleForTesting
+ @Nullable
+ WindowContainerToken getTaskWindowContainerTokenForLaunchCookie(@NonNull IBinder launchCookie) {
+ // Find the activity identified by the launch cookie.
+ final ActivityRecord targetActivity = mRoot.getActivity(
+ activity -> activity.mLaunchCookie == launchCookie);
+ if (targetActivity == null) {
+ Slog.w(TAG, "Unable to find the activity for this launch cookie");
+ return null;
+ }
+ if (targetActivity.getTask() == null) {
+ Slog.w(TAG, "Unable to find the task for this launch cookie");
+ return null;
+ }
+ WindowContainerToken taskWindowContainerToken =
+ targetActivity.getTask().mRemoteToken.toWindowContainerToken();
+ if (taskWindowContainerToken == null) {
+ Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName());
+ return null;
+ }
+ return taskWindowContainerToken;
+ }
+
+ /**
* You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY.
*/
private int sanitizeFlagSlippery(int flags, String windowName, int callingUid, int callingPid) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 02f056cd33af..ff43a96d6afc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1370,9 +1370,10 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println(" be ignored and framework implementation will determine aspect ratio.");
pw.println(" --minAspectRatioForUnresizable aspectRatio");
pw.println(" Default min aspect ratio for unresizable apps which is used when an");
- pw.println(" app doesn't specify android:minAspectRatio. An exception will be");
- pw.println(" thrown if aspectRatio < "
- + LetterboxConfiguration.MIN_UNRESIZABLE_ASPECT_RATIO);
+ pw.println(" app is eligible for the size compat mode. If aspectRatio <= "
+ + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+ pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will");
+ pw.println(" be ignored and framework implementation will determine aspect ratio.");
pw.println(" --cornerRadius radius");
pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,");
pw.println(" both it and R.integer.config_letterboxActivityCornersRadius will be");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 1d93c8922d21..97dcb7574e3c 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -46,6 +46,7 @@ import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CON
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -814,7 +815,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
break;
}
- if (!parent.isAllowedToEmbedActivity(activity)) {
+ if (parent.isAllowedToEmbedActivity(activity) != EMBEDDING_ALLOWED) {
final Throwable exception = new SecurityException(
"The task fragment is not trusted to embed the given activity.");
sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
@@ -1057,7 +1058,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
/** A helper method to send minimum dimension violation error to the client. */
- void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
+ private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
IBinder errorCallbackToken, String reason) {
if (taskFragment == null || taskFragment.getTaskFragmentOrganizer() == null) {
return;
@@ -1672,7 +1673,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// We are reparenting activities to a new embedded TaskFragment, this operation is only
// allowed if the new parent is trusted by all reparent activities.
final boolean isEmbeddingDisallowed = oldParent.forAllActivities(activity ->
- !newParentTF.isAllowedToEmbedActivity(activity));
+ newParentTF.isAllowedToEmbedActivity(activity) == EMBEDDING_ALLOWED);
if (isEmbeddingDisallowed) {
final Throwable exception = new SecurityException(
"The new parent is not trusted to embed the activities.");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 46cf5c9348cc..46091d842c2a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -115,6 +115,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RESIZE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
@@ -392,6 +393,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
int mSyncSeqId = 0;
int mLastSeqIdSentToRelayout = 0;
+ /** The last syncId associated with a prepareSync or 0 when no sync is active. */
+ int mPrepareSyncSeqId = 0;
+
/**
* {@code true} when the client was still drawing for sync when the sync-set was finished or
* cancelled. This can happen if the window goes away during a sync. In this situation we need
@@ -809,7 +813,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
};
private final Consumer<SurfaceControl.Transaction> mSetSurfacePositionConsumer = t -> {
- if (mSurfaceControl != null && mSurfaceControl.isValid()) {
+ // Only apply the position to the surface when there's no leash created.
+ if (mSurfaceControl != null && mSurfaceControl.isValid() && !mSurfaceAnimator.hasLeash()) {
t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
}
};
@@ -1246,7 +1251,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mSession.windowAddedLocked();
}
- boolean updateGlobalScale() {
+ void updateGlobalScale() {
if (hasCompatScale()) {
if (mOverrideScale != 1f) {
mGlobalScale = mToken.hasSizeCompatBounds()
@@ -1256,11 +1261,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mGlobalScale = mToken.getSizeCompatScale();
}
mInvGlobalScale = 1f / mGlobalScale;
- return true;
+ return;
}
mGlobalScale = mInvGlobalScale = 1f;
- return false;
}
/**
@@ -1513,23 +1517,23 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final boolean dragResizingChanged = isDragResizeChanged()
&& !isDragResizingChangeReported();
+ final boolean attachedFrameChanged = LOCAL_LAYOUT
+ && mLayoutAttached && getParentWindow().frameChanged();
+
if (DEBUG) {
Slog.v(TAG_WM, "Resizing " + this + ": configChanged=" + configChanged
+ " dragResizingChanged=" + dragResizingChanged
+ " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame);
}
- // We update mLastFrame always rather than in the conditional with the last inset
- // variables, because mFrameSizeChanged only tracks the width and height changing.
- updateLastFrames();
-
// Add a window that is using blastSync to the resizing list if it hasn't been reported
// already. This because the window is waiting on a finishDrawing from the client.
if (didFrameInsetsChange
|| configChanged
|| insetsChanged
|| dragResizingChanged
- || shouldSendRedrawForSync()) {
+ || shouldSendRedrawForSync()
+ || attachedFrameChanged) {
ProtoLog.v(WM_DEBUG_RESIZE,
"Resize reasons for w=%s: %s configChanged=%b dragResizingChanged=%b",
this, mWindowFrames.getInsetsChangedInfo(),
@@ -1585,6 +1589,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
+ private boolean frameChanged() {
+ return !mWindowFrames.mFrame.equals(mWindowFrames.mLastFrame);
+ }
+
boolean getOrientationChanging() {
// In addition to the local state flag, we must also consider the difference in the last
// reported configuration vs. the current state. If the client code has not been informed of
@@ -3836,6 +3844,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mInvGlobalScale != 1.0f && hasCompatScale()) {
outFrames.displayFrame.scale(mInvGlobalScale);
}
+ if (mLayoutAttached) {
+ if (outFrames.attachedFrame == null) {
+ outFrames.attachedFrame = new Rect();
+ }
+ outFrames.attachedFrame.set(getParentWindow().getCompatFrame());
+ }
// Note: in the cases where the window is tied to an activity, we should not send a
// configuration update when the window has requested to be hidden. Doing so can lead to
@@ -3887,6 +3901,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mDragResizingChangeReported = true;
mWindowFrames.clearReportResizeHints();
+ // We update mLastFrame always rather than in the conditional with the last inset
+ // variables, because mFrameSizeChanged only tracks the width and height changing.
+ updateLastFrames();
+
final int prevRotation = mLastReportedConfiguration
.getMergedConfiguration().windowConfiguration.getRotation();
fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration,
@@ -3921,7 +3939,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
mSyncSeqId, resizeMode);
- if (drawPending && prevRotation != mLastReportedConfiguration
+ if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
.getMergedConfiguration().windowConfiguration.getRotation()) {
mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
ProtoLog.v(WM_DEBUG_ORIENTATION,
@@ -4405,6 +4423,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
pw.println(prefix + "Requested visibilities: " + visibilityString);
}
}
+
+ pw.println(prefix + "mPrepareSyncSeqId=" + mPrepareSyncSeqId);
}
@Override
@@ -5896,6 +5916,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mWinAnimator.getSurfaceControl();
}
+ /** Drops a buffer for this window's view-root from a transaction */
+ private void dropBufferFrom(Transaction t) {
+ SurfaceControl viewSurface = getClientViewRootSurface();
+ if (viewSurface == null) return;
+ t.setBuffer(viewSurface, (android.hardware.HardwareBuffer) null);
+ }
+
@Override
boolean prepareSync() {
if (!mDrawHandlers.isEmpty()) {
@@ -5911,7 +5938,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// to draw even if the children draw first or don't need to sync, so we start
// in WAITING state rather than READY.
mSyncState = SYNC_STATE_WAITING_FOR_DRAW;
+
+ if (mPrepareSyncSeqId > 0) {
+ // another prepareSync during existing sync (eg. reparented), so pre-emptively
+ // drop buffer (if exists). If the buffer hasn't been received yet, it will be
+ // dropped in finishDrawing.
+ ProtoLog.d(WM_DEBUG_SYNC_ENGINE, "Preparing to sync a window that was already in the"
+ + " sync, so try dropping buffer. win=%s", this);
+ dropBufferFrom(mSyncTransaction);
+ }
+
mSyncSeqId++;
+ mPrepareSyncSeqId = mSyncSeqId;
requestRedrawForSync();
return true;
}
@@ -5932,6 +5970,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) {
mClientWasDrawingForSync = true;
}
+ mPrepareSyncSeqId = 0;
+ if (cancel) {
+ // This is leaving sync so any buffers left in the sync have a chance of
+ // being applied out-of-order and can also block the buffer queue for this
+ // window. To prevent this, drop the buffer.
+ dropBufferFrom(mSyncTransaction);
+ }
super.finishSync(outMergedTransaction, cancel);
}
@@ -5953,6 +5998,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
.notifyStartingWindowDrawn(mActivityRecord);
}
+ final boolean syncActive = mPrepareSyncSeqId > 0;
+ final boolean syncStillPending = syncActive && mPrepareSyncSeqId > syncSeqId;
+ if (syncStillPending && postDrawTransaction != null) {
+ ProtoLog.d(WM_DEBUG_SYNC_ENGINE, "Got a buffer for request id=%d but latest request is"
+ + " id=%d. Since the buffer is out-of-date, drop it. win=%s", syncSeqId,
+ mPrepareSyncSeqId, this);
+ // sync is waiting for a newer seqId, so this buffer is obsolete and can be dropped
+ // to free up the buffer queue.
+ dropBufferFrom(postDrawTransaction);
+ }
+
final boolean hasSyncHandlers = executeDrawHandlers(postDrawTransaction, syncSeqId);
boolean skipLayout = false;
@@ -5965,10 +6021,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Layout is not needed because the window will be hidden by the fade leash.
postDrawTransaction = null;
skipLayout = true;
- } else if (onSyncFinishedDrawing() && postDrawTransaction != null) {
- mSyncTransaction.merge(postDrawTransaction);
- // Consume the transaction because the sync group will merge it.
- postDrawTransaction = null;
+ } else if (syncActive) {
+ if (!syncStillPending) {
+ onSyncFinishedDrawing();
+ }
+ if (postDrawTransaction != null) {
+ mSyncTransaction.merge(postDrawTransaction);
+ // Consume the transaction because the sync group will merge it.
+ postDrawTransaction = null;
+ }
}
final boolean layoutNeeded =
@@ -6198,4 +6259,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@WindowTraceLogLevel int logLevel) {
dumpDebug(proto, fieldId, logLevel);
}
+
+ public boolean cancelAndRedraw() {
+ // Cancel any draw requests during a sync.
+ return mPrepareSyncSeqId > 0;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index ab72e49f1e4f..437c9344d793 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -448,8 +448,14 @@ class WindowToken extends WindowContainer<WindowState> {
if (mFixedRotationTransformState != null) {
mFixedRotationTransformState.disassociate(this);
}
+ // TODO(b/233855302): Remove TaskFragment override if the DisplayContent uses the same
+ // bounds for screenLayout calculation.
+ final Configuration overrideConfig = new Configuration(config);
+ overrideConfig.screenLayout = TaskFragment.computeScreenLayoutOverride(
+ overrideConfig.screenLayout, overrideConfig.screenWidthDp,
+ overrideConfig.screenHeightDp);
mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames,
- new Configuration(config), mDisplayContent.getRotation());
+ overrideConfig, mDisplayContent.getRotation());
mFixedRotationTransformState.mAssociatedTokens.add(this);
mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames);
onFixedRotationStatePrepared();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 05ea9cc9ac3b..06fb4b0a01d8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1982,6 +1982,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
mOwners.load();
setDeviceOwnershipSystemPropertyLocked();
+ if (mOwners.hasDeviceOwner()) {
+ setGlobalSettingDeviceOwnerType(
+ mOwners.getDeviceOwnerType(mOwners.getDeviceOwnerPackageName()));
+ }
}
}
@@ -8811,6 +8815,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
deleteTransferOwnershipBundleLocked(userId);
toggleBackupServiceActive(UserHandle.USER_SYSTEM, true);
pushUserControlDisabledPackagesLocked(userId);
+ setGlobalSettingDeviceOwnerType(DEVICE_OWNER_TYPE_DEFAULT);
}
private void clearApplicationRestrictions(int userId) {
@@ -18377,6 +18382,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
"Test only admins can only set the device owner type more than once");
mOwners.setDeviceOwnerType(packageName, deviceOwnerType, isAdminTestOnly);
+ setGlobalSettingDeviceOwnerType(deviceOwnerType);
+ }
+
+ // TODO(b/237065504): Allow mainline modules to get the device owner type. This is a workaround
+ // to get the device owner type in PermissionController. See HibernationPolicy.kt.
+ private void setGlobalSettingDeviceOwnerType(int deviceOwnerType) {
+ mInjector.binderWithCleanCallingIdentity(
+ () -> mInjector.settingsGlobalPutInt("device_owner_type", deviceOwnerType));
}
@Override
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ef311c249c5f..66c9f55b0403 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -412,7 +412,7 @@ public final class SystemServer implements Dumpable {
"/apex/com.android.uwb/javalib/service-uwb.jar";
private static final String UWB_SERVICE_CLASS = "com.android.server.uwb.UwbService";
private static final String BLUETOOTH_APEX_SERVICE_JAR_PATH =
- "/apex/com.android.bluetooth/javalib/service-bluetooth.jar";
+ "/apex/com.android.btservices/javalib/service-bluetooth.jar";
private static final String BLUETOOTH_SERVICE_CLASS =
"com.android.server.bluetooth.BluetoothService";
private static final String SAFETY_CENTER_SERVICE_CLASS =
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
index b36aa0617be5..e87dd4b423b2 100644
--- a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
@@ -36,8 +36,6 @@ import android.util.Log;
import androidx.test.InstrumentationRegistry;
-import com.android.server.FgThread;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -48,6 +46,11 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
@@ -88,6 +91,7 @@ public final class AdbDebuggingManagerTest {
private long mOriginalAllowedConnectionTime;
private File mAdbKeyXmlFile;
private File mAdbKeyFile;
+ private FakeTicker mFakeTicker;
@Before
public void setUp() throws Exception {
@@ -96,14 +100,25 @@ public final class AdbDebuggingManagerTest {
if (mAdbKeyFile.exists()) {
mAdbKeyFile.delete();
}
- mManager = new AdbDebuggingManager(mContext, ADB_CONFIRM_COMPONENT, mAdbKeyFile);
mAdbKeyXmlFile = new File(mContext.getFilesDir(), "test_adb_keys.xml");
if (mAdbKeyXmlFile.exists()) {
mAdbKeyXmlFile.delete();
}
+
+ mFakeTicker = new FakeTicker();
+ // Set the ticker time to October 22, 2008 (the day the T-Mobile G1 was released)
+ mFakeTicker.advance(1224658800L);
+
mThread = new AdbDebuggingThreadTest();
- mKeyStore = mManager.new AdbKeyStore(mAdbKeyXmlFile);
- mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper(), mThread, mKeyStore);
+ mManager = new AdbDebuggingManager(
+ mContext, ADB_CONFIRM_COMPONENT, mAdbKeyFile, mAdbKeyXmlFile, mThread, mFakeTicker);
+
+ mHandler = mManager.mHandler;
+ mThread.setHandler(mHandler);
+
+ mHandler.initKeyStore();
+ mKeyStore = mHandler.mAdbKeyStore;
+
mOriginalAllowedConnectionTime = mKeyStore.getAllowedConnectionTime();
mBlockingQueue = new ArrayBlockingQueue<>(1);
}
@@ -122,7 +137,7 @@ public final class AdbDebuggingManagerTest {
private void setAllowedConnectionTime(long connectionTime) {
Settings.Global.putLong(mContext.getContentResolver(),
Settings.Global.ADB_ALLOWED_CONNECTION_TIME, connectionTime);
- };
+ }
@Test
public void testAllowNewKeyOnce() throws Exception {
@@ -158,20 +173,15 @@ public final class AdbDebuggingManagerTest {
// Allow a connection from a new key with the 'Always allow' option selected.
runAdbTest(TEST_KEY_1, true, true, false);
- // Get the last connection time for the currently connected key to verify that it is updated
- // after the disconnect.
- long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
-
- // Sleep for a small amount of time to ensure a difference can be observed in the last
- // connection time after a disconnect.
- Thread.sleep(10);
+ // Advance the clock by 10ms to ensure there's a difference
+ mFakeTicker.advance(10 * 1_000_000);
// Send the disconnect message for the currently connected key to trigger an update of the
// last connection time.
disconnectKey(TEST_KEY_1);
- assertNotEquals(
+ assertEquals(
"The last connection time was not updated after the disconnect",
- lastConnectionTime,
+ mFakeTicker.currentTimeMillis(),
mKeyStore.getLastConnectionTime(TEST_KEY_1));
}
@@ -244,8 +254,8 @@ public final class AdbDebuggingManagerTest {
// Get the current last connection time for comparison after the scheduled job is run
long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- // Sleep a small amount of time to ensure that the updated connection time changes
- Thread.sleep(10);
+ // Advance a small amount of time to ensure that the updated connection time changes
+ mFakeTicker.advance(10);
// Send a message to the handler to update the last connection time for the active key
updateKeyStore();
@@ -269,13 +279,13 @@ public final class AdbDebuggingManagerTest {
persistKeyStore();
assertTrue(
"The key with the 'Always allow' option selected was not persisted in the keystore",
- mManager.new AdbKeyStore(mAdbKeyXmlFile).isKeyAuthorized(TEST_KEY_1));
+ mManager.new AdbKeyStore().isKeyAuthorized(TEST_KEY_1));
// Get the current last connection time to ensure it is updated in the persisted keystore.
long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- // Sleep a small amount of time to ensure the last connection time is updated.
- Thread.sleep(10);
+ // Advance a small amount of time to ensure the last connection time is updated.
+ mFakeTicker.advance(10);
// Send a message to the handler to update the last connection time for the active key.
updateKeyStore();
@@ -286,7 +296,7 @@ public final class AdbDebuggingManagerTest {
assertNotEquals(
"The last connection time in the key file was not updated after the update "
+ "connection time message", lastConnectionTime,
- mManager.new AdbKeyStore(mAdbKeyXmlFile).getLastConnectionTime(TEST_KEY_1));
+ mManager.new AdbKeyStore().getLastConnectionTime(TEST_KEY_1));
// Verify that the key is in the adb_keys file
assertTrue("The key was not in the adb_keys file after persisting the keystore",
isKeyInFile(TEST_KEY_1, mAdbKeyFile));
@@ -327,8 +337,8 @@ public final class AdbDebuggingManagerTest {
// Set the allowed window to a small value to ensure the time is beyond the allowed window.
setAllowedConnectionTime(1);
- // Sleep for a small amount of time to exceed the allowed window.
- Thread.sleep(10);
+ // Advance a small amount of time to exceed the allowed window.
+ mFakeTicker.advance(10);
// The AdbKeyStore has a method to get the time of the next key expiration to ensure the
// scheduled job runs at the time of the next expiration or after 24 hours, whichever occurs
@@ -478,9 +488,12 @@ public final class AdbDebuggingManagerTest {
// Set the current expiration time to a minute from expiration and verify this new value is
// returned.
final long newExpirationTime = 60000;
- mKeyStore.setLastConnectionTime(TEST_KEY_1,
- System.currentTimeMillis() - Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME
- + newExpirationTime, true);
+ mKeyStore.setLastConnectionTime(
+ TEST_KEY_1,
+ mFakeTicker.currentTimeMillis()
+ - Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME
+ + newExpirationTime,
+ true);
expirationTime = mKeyStore.getNextExpirationTime();
if (Math.abs(expirationTime - newExpirationTime) > epsilon) {
fail("The expiration time for a key about to expire, " + expirationTime
@@ -525,7 +538,7 @@ public final class AdbDebuggingManagerTest {
// Get the last connection time for the key to verify that it is updated when the connected
// key message is sent.
long connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONNECTED_KEY,
TEST_KEY_1).sendToTarget();
flushHandlerQueue();
@@ -536,7 +549,7 @@ public final class AdbDebuggingManagerTest {
// Verify that the scheduled job updates the connection time of the key.
connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
updateKeyStore();
assertNotEquals(
"The connection time for the key must be updated when the update keystore message"
@@ -545,7 +558,7 @@ public final class AdbDebuggingManagerTest {
// Verify that the connection time is updated when the key is disconnected.
connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
disconnectKey(TEST_KEY_1);
assertNotEquals(
"The connection time for the key must be updated when the disconnected message is"
@@ -628,11 +641,11 @@ public final class AdbDebuggingManagerTest {
setAllowedConnectionTime(Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
// The untracked keys should be added to the keystore as part of the constructor.
- AdbDebuggingManager.AdbKeyStore adbKeyStore = mManager.new AdbKeyStore(mAdbKeyXmlFile);
+ AdbDebuggingManager.AdbKeyStore adbKeyStore = mManager.new AdbKeyStore();
// Verify that the connection time for each test key is within a small value of the current
// time.
- long time = System.currentTimeMillis();
+ long time = mFakeTicker.currentTimeMillis();
for (String key : testKeys) {
long connectionTime = adbKeyStore.getLastConnectionTime(key);
if (Math.abs(time - connectionTime) > epsilon) {
@@ -651,11 +664,11 @@ public final class AdbDebuggingManagerTest {
runAdbTest(TEST_KEY_1, true, true, false);
runAdbTest(TEST_KEY_2, true, true, false);
- // Sleep a small amount of time to ensure the connection time is updated by the scheduled
+ // Advance a small amount of time to ensure the connection time is updated by the scheduled
// job.
long connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1);
long connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
updateKeyStore();
assertNotEquals(
"The connection time for test key 1 must be updated after the scheduled job runs",
@@ -669,7 +682,7 @@ public final class AdbDebuggingManagerTest {
disconnectKey(TEST_KEY_2);
connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1);
connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
updateKeyStore();
assertNotEquals(
"The connection time for test key 1 must be updated after another key is "
@@ -686,8 +699,6 @@ public final class AdbDebuggingManagerTest {
// to clear the adb authorizations when adb is disabled after a boot a NullPointerException
// was thrown as deleteKeyStore is invoked against the key store. This test ensures the
// key store can be successfully cleared when adb is disabled.
- mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper());
-
clearKeyStore();
}
@@ -723,6 +734,9 @@ public final class AdbDebuggingManagerTest {
// Now remove one of the keys and make sure the other key is still there
mKeyStore.removeKey(TEST_KEY_1);
+ // Wait for the handler queue to receive the MESSAGE_ADB_PERSIST_KEYSTORE
+ flushHandlerQueue();
+
assertFalse("The key was still in the adb_keys file after removing the key",
isKeyInFile(TEST_KEY_1, mAdbKeyFile));
assertTrue("The key was not in the adb_keys file after removing a different key",
@@ -730,6 +744,95 @@ public final class AdbDebuggingManagerTest {
}
@Test
+ public void testAdbKeyStore_addDuplicateKey_doesNotAddDuplicateToAdbKeyFile() throws Exception {
+ setAllowedConnectionTime(0);
+
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+
+ assertEquals("adb_keys contains duplicate keys", 1, adbKeyFileKeys(mAdbKeyFile).size());
+ }
+
+ @Test
+ public void testAdbKeyStore_adbTempKeysFile_readsLastConnectionTimeFromXml() throws Exception {
+ long insertTime = mFakeTicker.currentTimeMillis();
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+
+ mFakeTicker.advance(10);
+ AdbDebuggingManager.AdbKeyStore newKeyStore = mManager.new AdbKeyStore();
+
+ assertEquals(
+ "KeyStore not populated from the XML file.",
+ insertTime,
+ newKeyStore.getLastConnectionTime(TEST_KEY_1));
+ }
+
+ @Test
+ public void test_notifyKeyFilesUpdated_filesDeletedRemovesPreviouslyAddedKey()
+ throws Exception {
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+
+ Files.delete(mAdbKeyXmlFile.toPath());
+ Files.delete(mAdbKeyFile.toPath());
+
+ mManager.notifyKeyFilesUpdated();
+ flushHandlerQueue();
+
+ assertFalse(
+ "Key is authorized after reloading deleted key files. Was state preserved?",
+ mKeyStore.isKeyAuthorized(TEST_KEY_1));
+ }
+
+ @Test
+ public void test_notifyKeyFilesUpdated_newKeyIsAuthorized() throws Exception {
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+
+ // Back up the existing key files
+ Path tempXmlFile = Files.createTempFile("adbKeyXmlFile", ".tmp");
+ Path tempAdbKeysFile = Files.createTempFile("adb_keys", ".tmp");
+ Files.copy(mAdbKeyXmlFile.toPath(), tempXmlFile, StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(mAdbKeyFile.toPath(), tempAdbKeysFile, StandardCopyOption.REPLACE_EXISTING);
+
+ // Delete the existing key files
+ Files.delete(mAdbKeyXmlFile.toPath());
+ Files.delete(mAdbKeyFile.toPath());
+
+ // Notify the manager that adb key files have changed.
+ mManager.notifyKeyFilesUpdated();
+ flushHandlerQueue();
+
+ // Copy the files back
+ Files.copy(tempXmlFile, mAdbKeyXmlFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(tempAdbKeysFile, mAdbKeyFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+
+ // Tell the manager that the key files have changed.
+ mManager.notifyKeyFilesUpdated();
+ flushHandlerQueue();
+
+ assertTrue(
+ "Key is not authorized after reloading key files.",
+ mKeyStore.isKeyAuthorized(TEST_KEY_1));
+ }
+
+ @Test
+ public void testAdbKeyStore_adbWifiConnect_storesBssidWhenAlwaysAllow() throws Exception {
+ String trustedNetwork = "My Network";
+ mKeyStore.addTrustedNetwork(trustedNetwork);
+ persistKeyStore();
+
+ AdbDebuggingManager.AdbKeyStore newKeyStore = mManager.new AdbKeyStore();
+
+ assertTrue(
+ "Persisted trusted network not found in new keystore instance.",
+ newKeyStore.isTrustedNetwork(trustedNetwork));
+ }
+
+ @Test
public void testIsValidMdnsServiceName() {
// Longer than 15 characters
assertFalse(isValidMdnsServiceName("abcd1234abcd1234"));
@@ -1030,28 +1133,27 @@ public final class AdbDebuggingManagerTest {
if (key == null) {
return false;
}
+ return adbKeyFileKeys(keyFile).contains(key);
+ }
+
+ private static List<String> adbKeyFileKeys(File keyFile) throws Exception {
+ List<String> keys = new ArrayList<>();
if (keyFile.exists()) {
try (BufferedReader in = new BufferedReader(new FileReader(keyFile))) {
String currKey;
while ((currKey = in.readLine()) != null) {
- if (key.equals(currKey)) {
- return true;
- }
+ keys.add(currKey);
}
}
}
- return false;
+ return keys;
}
/**
* Helper class that extends AdbDebuggingThread to receive the response from AdbDebuggingManager
* indicating whether the key should be allowed to connect.
*/
- class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread {
- AdbDebuggingThreadTest() {
- mManager.super();
- }
-
+ private class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread {
@Override
public void sendResponse(String msg) {
TestResult result = new TestResult(TestResult.RESULT_RESPONSE_RECEIVED, msg);
@@ -1091,4 +1193,17 @@ public final class AdbDebuggingManagerTest {
return "{mReturnCode = " + mReturnCode + ", mMessage = " + mMessage + "}";
}
}
+
+ private static class FakeTicker implements AdbDebuggingManager.Ticker {
+ private long mCurrentTime;
+
+ private void advance(long milliseconds) {
+ mCurrentTime += milliseconds;
+ }
+
+ @Override
+ public long currentTimeMillis() {
+ return mCurrentTime;
+ }
+ }
}
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 25cf8a86baad..e95924ad7109 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -20,7 +20,9 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricPrompt.DISMISSED_REASON_NEGATIVE;
-import static com.android.server.biometrics.BiometricServiceStateProto.*;
+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 junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -32,6 +34,8 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -280,6 +284,43 @@ public class AuthSessionTest {
}
@Test
+ public void testOnDialogAnimatedInDoesNothingDuringInvalidState() throws Exception {
+ setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
+ final long operationId = 123;
+ final int userId = 10;
+
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ operationId,
+ userId);
+ final IBiometricAuthenticator impl = session.mPreAuthInfo.eligibleSensors.get(0).impl;
+
+ 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());
+ verify(impl, never()).startPreparedClient(anyInt());
+
+ // First invocation should start the client monitor.
+ session.onDialogAnimatedIn();
+ assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
+ verify(impl).startPreparedClient(anyInt());
+
+ // Subsequent invocations should not start the client monitor again.
+ session.onDialogAnimatedIn();
+ session.onDialogAnimatedIn();
+ session.onDialogAnimatedIn();
+ assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
+ verify(impl, times(1)).startPreparedClient(anyInt());
+ }
+
+ @Test
public void testCancelAuthentication_whenStateAuthCalled_invokesCancel()
throws RemoteException {
testInvokesCancel(session -> session.onCancelAuthSession(false /* force */));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index c17347320f52..9e9d70332f00 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -80,11 +80,14 @@ public class BiometricSchedulerOperationTest {
private Handler mHandler;
private BiometricSchedulerOperation mOperation;
+ private boolean mIsDebuggable;
@Before
public void setUp() {
mHandler = new Handler(TestableLooper.get(this).getLooper());
- mOperation = new BiometricSchedulerOperation(mClientMonitor, mClientCallback);
+ mIsDebuggable = false;
+ mOperation = new BiometricSchedulerOperation(mClientMonitor, mClientCallback,
+ () -> mIsDebuggable);
}
@Test
@@ -126,6 +129,34 @@ public class BiometricSchedulerOperationTest {
}
@Test
+ public void testSecondStartWithCookieCrashesWhenDebuggable() {
+ final int cookie = 5;
+ mIsDebuggable = true;
+ when(mClientMonitor.getCookie()).thenReturn(cookie);
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ final boolean started = mOperation.startWithCookie(mOnStartCallback, cookie);
+ assertThat(started).isTrue();
+
+ assertThrows(IllegalStateException.class,
+ () -> mOperation.startWithCookie(mOnStartCallback, cookie));
+ }
+
+ @Test
+ public void testSecondStartWithCookieFailsNicelyWhenNotDebuggable() {
+ final int cookie = 5;
+ mIsDebuggable = false;
+ when(mClientMonitor.getCookie()).thenReturn(cookie);
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ final boolean started = mOperation.startWithCookie(mOnStartCallback, cookie);
+ assertThat(started).isTrue();
+
+ final boolean startedAgain = mOperation.startWithCookie(mOnStartCallback, cookie);
+ assertThat(startedAgain).isFalse();
+ }
+
+ @Test
public void startsWhenReadyAndHalAvailable() {
when(mClientMonitor.getCookie()).thenReturn(0);
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
@@ -170,7 +201,34 @@ public class BiometricSchedulerOperationTest {
}
@Test
+ public void secondStartCrashesWhenDebuggable() {
+ mIsDebuggable = true;
+ when(mClientMonitor.getCookie()).thenReturn(0);
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ final boolean started = mOperation.start(mOnStartCallback);
+ assertThat(started).isTrue();
+
+ assertThrows(IllegalStateException.class, () -> mOperation.start(mOnStartCallback));
+ }
+
+ @Test
+ public void secondStartFailsNicelyWhenNotDebuggable() {
+ mIsDebuggable = false;
+ when(mClientMonitor.getCookie()).thenReturn(0);
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ final boolean started = mOperation.start(mOnStartCallback);
+ assertThat(started).isTrue();
+
+ final boolean startedAgain = mOperation.start(mOnStartCallback);
+ assertThat(startedAgain).isFalse();
+ }
+
+ @Test
public void doesNotStartWithCookie() {
+ // This class only throws exceptions when debuggable.
+ mIsDebuggable = true;
when(mClientMonitor.getCookie()).thenReturn(9);
assertThrows(IllegalStateException.class,
() -> mOperation.start(mock(ClientMonitorCallback.class)));
@@ -178,6 +236,8 @@ public class BiometricSchedulerOperationTest {
@Test
public void cannotRestart() {
+ // This class only throws exceptions when debuggable.
+ mIsDebuggable = true;
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
mOperation.start(mOnStartCallback);
@@ -188,6 +248,8 @@ public class BiometricSchedulerOperationTest {
@Test
public void abortsNotRunning() {
+ // This class only throws exceptions when debuggable.
+ mIsDebuggable = true;
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
mOperation.abort();
@@ -200,7 +262,8 @@ public class BiometricSchedulerOperationTest {
}
@Test
- public void cannotAbortRunning() {
+ public void abortCrashesWhenDebuggableIfOperationIsRunning() {
+ mIsDebuggable = true;
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
mOperation.start(mOnStartCallback);
@@ -209,6 +272,16 @@ public class BiometricSchedulerOperationTest {
}
@Test
+ public void abortFailsNicelyWhenNotDebuggableIfOperationIsRunning() {
+ mIsDebuggable = false;
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ mOperation.start(mOnStartCallback);
+
+ mOperation.abort();
+ }
+
+ @Test
public void cancel() {
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
@@ -254,6 +327,30 @@ public class BiometricSchedulerOperationTest {
}
@Test
+ public void cancelCrashesWhenDebuggableIfOperationIsFinished() {
+ mIsDebuggable = true;
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ mOperation.abort();
+ assertThat(mOperation.isFinished()).isTrue();
+
+ final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
+ assertThrows(IllegalStateException.class, () -> mOperation.cancel(mHandler, cancelCb));
+ }
+
+ @Test
+ public void cancelFailsNicelyWhenNotDebuggableIfOperationIsFinished() {
+ mIsDebuggable = false;
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ mOperation.abort();
+ assertThat(mOperation.isFinished()).isTrue();
+
+ final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
+ mOperation.cancel(mHandler, cancelCb);
+ }
+
+ @Test
public void markCanceling() {
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index f242fda15f06..c80547ce61c0 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -213,7 +213,7 @@ public class VirtualDeviceManagerServiceTest {
mContext.getSystemService(WindowManager.class), threadVerifier);
mAssociationInfo = new AssociationInfo(1, 0, null,
- MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
+ MacAddress.BROADCAST_ADDRESS, "", null, true, false, false, 0, 0);
VirtualDeviceParams params = new VirtualDeviceParams
.Builder()
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
index 0ed90d27db99..6a6cd6c914a2 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -16,13 +16,11 @@
package com.android.server.display;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -34,17 +32,18 @@ import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.Message;
import android.os.PowerManager;
-import android.os.Temperature.ThrottlingStatus;
import android.os.Temperature;
+import android.os.Temperature.ThrottlingStatus;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.os.BackgroundThread;
import com.android.server.display.BrightnessThrottler.Injector;
-import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
import org.junit.Before;
import org.junit.Test;
@@ -55,7 +54,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
@SmallTest
@@ -70,6 +68,8 @@ public class BrightnessThrottlerTest {
@Mock IThermalService mThermalServiceMock;
@Mock Injector mInjectorMock;
+ DisplayModeDirectorTest.FakeDeviceConfig mDeviceConfigFake;
+
@Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
@Before
@@ -83,6 +83,8 @@ public class BrightnessThrottlerTest {
return true;
}
});
+ mDeviceConfigFake = new DisplayModeDirectorTest.FakeDeviceConfig();
+ when(mInjectorMock.getDeviceConfig()).thenReturn(mDeviceConfigFake);
}
@@ -292,6 +294,170 @@ public class BrightnessThrottlerTest {
assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
}
+ @Test public void testUpdateThrottlingData() throws Exception {
+ // Initialise brightness throttling levels
+ // Ensure that they are overridden by setting the data through device config.
+ final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ 0.25f);
+ List<ThrottlingLevel> levels = new ArrayList<>();
+ levels.add(level);
+ final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+ mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.4");
+ final BrightnessThrottler throttler = createThrottlerSupported(data);
+
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+
+ // Set status high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(0.4f, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+
+ // Update thresholds
+ // This data is equivalent to the string "123,1,critical,0.8", passed below
+ final ThrottlingLevel newLevel = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ 0.8f);
+ // Set new (valid) data from device config
+ mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.8");
+
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(newLevel.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+
+ // Set status high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(newLevel.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(newLevel.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ }
+
+ @Test public void testInvalidThrottlingStrings() throws Exception {
+ // Initialise brightness throttling levels
+ // Ensure that they are not overridden by invalid data through device config.
+ final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ 0.25f);
+ List<ThrottlingLevel> levels = new ArrayList<>();
+ levels.add(level);
+ final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+ final BrightnessThrottler throttler = createThrottlerSupported(data);
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+ // None of these are valid so shouldn't override the original data
+ mDeviceConfigFake.setBrightnessThrottlingData("321,1,critical,0.4"); // Not the current id
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData("123,0,critical,0.4"); // Incorrect number
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData("123,2,critical,0.4"); // Incorrect number
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData("123,1,invalid,0.4"); // Invalid level
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,none"); // Invalid brightness
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,-3"); // Invalid brightness
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData("invalid string"); // Invalid format
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData(""); // Invalid format
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ }
+
+ private void testThrottling(BrightnessThrottler throttler, IThermalEventListener listener,
+ float tooLowCap, float tooHighCap) throws Exception {
+ final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ tooHighCap);
+
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(tooLowCap, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+
+ // Set status high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(tooHighCap, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ }
+
+ @Test public void testMultipleConfigPoints() throws Exception {
+ // Initialise brightness throttling levels
+ final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ 0.25f);
+ List<ThrottlingLevel> levels = new ArrayList<>();
+ levels.add(level);
+ final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+
+ // These are identical to the string set below
+ final ThrottlingLevel levelSevere = new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE,
+ 0.9f);
+ final ThrottlingLevel levelCritical = new ThrottlingLevel(
+ PowerManager.THERMAL_STATUS_CRITICAL, 0.5f);
+ final ThrottlingLevel levelEmergency = new ThrottlingLevel(
+ PowerManager.THERMAL_STATUS_EMERGENCY, 0.1f);
+
+ mDeviceConfigFake.setBrightnessThrottlingData(
+ "123,3,severe,0.9,critical,0.5,emergency,0.1");
+ final BrightnessThrottler throttler = createThrottlerSupported(data);
+
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+ // Ensure that the multiple levels set via the string through the device config correctly
+ // override the original display device config ones.
+
+ // levelSevere
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+
+ // Set status high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(0.9f, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+
+ // levelCritical
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(0.9f, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+
+ // Set status high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(0.5f, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+
+ //levelEmergency
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(0.5f, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+
+ // Set status high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(0.1f, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ }
+
private void assertThrottlingLevelsEquals(
List<ThrottlingLevel> expected,
List<ThrottlingLevel> actual) {
@@ -307,12 +473,13 @@ public class BrightnessThrottlerTest {
}
private BrightnessThrottler createThrottlerUnsupported() {
- return new BrightnessThrottler(mInjectorMock, mHandler, null, () -> {});
+ return new BrightnessThrottler(mInjectorMock, mHandler, mHandler, null, () -> {}, null);
}
private BrightnessThrottler createThrottlerSupported(BrightnessThrottlingData data) {
assertNotNull(data);
- return new BrightnessThrottler(mInjectorMock, mHandler, data, () -> {});
+ return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(),
+ data, () -> {}, "123");
}
private Temperature getSkinTemp(@ThrottlingStatus int status) {
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 864f31552ae3..968e1d8c546b 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
@@ -1902,6 +1903,11 @@ public class DisplayModeDirectorTest {
KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
}
+ void setBrightnessThrottlingData(String brightnessThrottlingData) {
+ putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ KEY_BRIGHTNESS_THROTTLING_DATA, brightnessThrottlingData);
+ }
+
void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) {
String thresholds = toPropertyValue(brightnessThresholds);
diff --git a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
index 363c26b63bae..bbed1b60f8bf 100644
--- a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -16,11 +16,14 @@
package com.android.server.display.color;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -1130,6 +1133,15 @@ public class ColorDisplayServiceTest {
eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(), eq(Display.COLOR_MODE_INVALID));
}
+ @Test
+ public void getColorMode_noAvailableModes_returnsNotSet() {
+ when(mResourcesSpy.getIntArray(R.array.config_availableColorModes))
+ .thenReturn(new int[] {});
+ startService();
+ verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), anyInt());
+ assertThat(mBinderService.getColorMode()).isEqualTo(-1);
+ }
+
/**
* Configures Night display to use a custom schedule.
*
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index 75bd2ccbe635..bc2c57e8bf84 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -22,6 +22,7 @@ import static junit.framework.TestCase.fail;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents.Event;
@@ -440,6 +441,7 @@ public class UsageStatsDatabaseTest {
prevDB.readMappingsLocked();
prevDB.init(1);
prevDB.putUsageStats(UsageStatsManager.INTERVAL_DAILY, mIntervalStats);
+ Set<String> prevDBApps = mIntervalStats.packageStats.keySet();
// Create a backup with a specific version
byte[] blob = prevDB.getBackupPayload(KEY_USAGE_STATS, version);
if (version >= 1 && version <= 3) {
@@ -447,6 +449,11 @@ public class UsageStatsDatabaseTest {
"UsageStatsDatabase shouldn't be able to write backups as XML");
return;
}
+ if (version < 1 || version > UsageStatsDatabase.BACKUP_VERSION) {
+ assertFalse(blob != null && blob.length != 0,
+ "UsageStatsDatabase shouldn't be able to write backups for unknown versions");
+ return;
+ }
clearUsageStatsFiles();
@@ -454,9 +461,11 @@ public class UsageStatsDatabaseTest {
newDB.readMappingsLocked();
newDB.init(1);
// Attempt to restore the usage stats from the backup
- newDB.applyRestoredPayload(KEY_USAGE_STATS, blob);
- List<IntervalStats> stats = newDB.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, 0, mEndTime,
- mIntervalStatsVerifier);
+ Set<String> restoredApps = newDB.applyRestoredPayload(KEY_USAGE_STATS, blob);
+ assertTrue(restoredApps.containsAll(prevDBApps),
+ "List of restored apps does not match list backed-up apps list.");
+ List<IntervalStats> stats = newDB.queryUsageStats(
+ UsageStatsManager.INTERVAL_DAILY, 0, mEndTime, mIntervalStatsVerifier);
if (version > UsageStatsDatabase.BACKUP_VERSION || version < 1) {
assertFalse(stats != null && !stats.isEmpty(),
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index c735bb7add0a..8a96febcd1e9 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -778,8 +778,7 @@ public class VibratorManagerServiceTest {
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS));
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
- HAPTIC_FEEDBACK_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), HAPTIC_FEEDBACK_ATTRS);
// Wait before checking it never played a second effect.
assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
@@ -793,49 +792,78 @@ public class VibratorManagerServiceTest {
}
@Test
- public void vibrate_withOngoingAlarmVibration_ignoresEffect() throws Exception {
+ public void vibrate_withNewRepeatingVibration_cancelsOngoingEffect() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createSystemReadyService();
VibrationEffect alarmEffect = VibrationEffect.createWaveform(
new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
- vibrate(service, alarmEffect, new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_ALARM).build());
+ vibrate(service, alarmEffect, ALARM_ATTRS);
// VibrationThread will start this vibration async, so wait before checking it started.
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS));
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
- HAPTIC_FEEDBACK_ATTRS);
+ VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
+ new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+ vibrate(service, repeatingEffect, NOTIFICATION_ATTRS);
- // Wait before checking it never played a second effect.
- assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
- service, /* timeout= */ 50));
+ // VibrationThread will start this vibration async, so wait before checking it started.
+ assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
+ service, TEST_TIMEOUT_MILLIS));
+
+ // The second vibration should have recorded that the vibrators were turned on.
+ verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
}
@Test
- public void vibrate_withOngoingRingtoneVibration_ignoresEffect() throws Exception {
+ public void vibrate_withOngoingHigherImportanceVibration_ignoresEffect() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createSystemReadyService();
- VibrationEffect alarmEffect = VibrationEffect.createWaveform(
+ VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
- vibrate(service, alarmEffect, new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_RINGTONE).build());
+ vibrate(service, effect, ALARM_ATTRS);
// VibrationThread will start this vibration async, so wait before checking it started.
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS));
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
- HAPTIC_FEEDBACK_ATTRS);
+ vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
// Wait before checking it never played a second effect.
assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
service, /* timeout= */ 50));
+
+ // The second vibration shouldn't have recorded that the vibrators were turned on.
+ verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+ }
+
+ @Test
+ public void vibrate_withOngoingLowerImportanceVibration_cancelsOngoingEffect()
+ throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
+ vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait before checking it started.
+ assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, effect, RINGTONE_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait before checking it started.
+ assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
+ service, TEST_TIMEOUT_MILLIS));
+
+ // The second vibration should have recorded that the vibrators were turned on.
+ verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
}
@Test
@@ -1052,15 +1080,15 @@ public class VibratorManagerServiceTest {
IVibrator.CAP_COMPOSE_EFFECTS);
VibratorManagerService service = createSystemReadyService();
- vibrate(service, CombinedVibration.startSequential()
- .addNext(1, VibrationEffect.createOneShot(100, 125))
- .combine(), NOTIFICATION_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
- service, TEST_TIMEOUT_MILLIS));
-
vibrate(service, VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose(), HAPTIC_FEEDBACK_ATTRS);
+ assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, CombinedVibration.startSequential()
+ .addNext(1, VibrationEffect.createOneShot(100, 125))
+ .combine(), NOTIFICATION_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
service, TEST_TIMEOUT_MILLIS));
@@ -1070,25 +1098,25 @@ public class VibratorManagerServiceTest {
assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 3,
service, TEST_TIMEOUT_MILLIS));
+ // Ring vibrations have intensity OFF and are not played.
vibrate(service, VibrationEffect.createOneShot(100, 125), RINGTONE_ATTRS);
assertFalse(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() > 3,
- service, TEST_TIMEOUT_MILLIS));
+ service, /* timeout= */ 50));
+ // Only 3 effects played successfully.
assertEquals(3, fakeVibrator.getAllEffectSegments().size());
+ // Haptic feedback vibrations will be scaled with SCALE_LOW or none if default is low.
+ assertEquals(defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW,
+ 0.5 > ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(0)).getScale());
+
// Notification vibrations will be scaled with SCALE_HIGH or none if default is high.
assertEquals(defaultNotificationIntensity < Vibrator.VIBRATION_INTENSITY_HIGH,
0.6 < fakeVibrator.getAmplitudes().get(0));
- // Haptic feedback vibrations will be scaled with SCALE_LOW or none if default is low.
- assertEquals(defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW,
- 0.5 > ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(1)).getScale());
-
// Alarm vibration will be scaled with SCALE_NONE.
assertEquals(1f,
((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(2)).getScale(), 1e-5);
-
- // Ring vibrations have intensity OFF and are not played.
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index fd1536c5c0f1..4550b56f6fd0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1622,7 +1622,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
+ // We need the package name to be something that's not "android" so there aren't any
+ // existing rules under that package.
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
assertNotNull(id);
}
try {
@@ -1632,12 +1634,41 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
}
+ }
+ @Test
+ public void testAddAutomaticZenRule_beyondSystemLimit_differentComponents() {
+ // Make sure the system limit is enforced per-package even with different component provider
+ // names.
+ for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
+ ScheduleInfo si = new ScheduleInfo();
+ si.startHour = i;
+ AutomaticZenRule zenRule = new AutomaticZenRule("name" + i,
+ null,
+ new ComponentName("android", "ScheduleConditionProvider" + i),
+ ZenModeConfig.toScheduleConditionId(si),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ assertNotNull(id);
+ }
+ try {
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName("android", "ScheduleConditionProviderFinal"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ fail("allowed too many rules to be created");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 2477f6c41d4f..0c3b270518cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -29,6 +29,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
@@ -182,6 +184,10 @@ public class ActivityRecordTests extends WindowTestsBase {
private final String mPackageName = getInstrumentation().getTargetContext().getPackageName();
+ private static final int ORIENTATION_CONFIG_CHANGES =
+ CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT | CONFIG_SCREEN_SIZE
+ | CONFIG_SMALLEST_SCREEN_SIZE;
+
@Before
public void setUp() throws Exception {
setBooted(mAtm);
@@ -487,7 +493,7 @@ public class ActivityRecordTests extends WindowTestsBase {
public void testSetRequestedOrientationUpdatesConfiguration() throws Exception {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
- .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
+ .setConfigChanges(ORIENTATION_CONFIG_CHANGES)
.build();
activity.setState(RESUMED, "Testing");
@@ -710,7 +716,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.setLaunchTaskBehind(true)
- .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
+ .setConfigChanges(ORIENTATION_CONFIG_CHANGES)
.build();
final Task task = activity.getTask();
activity.setState(STOPPED, "Testing");
@@ -779,7 +785,7 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
}
}, 0, 0));
activity.updateOptionsLocked(opts);
@@ -1996,7 +2002,8 @@ public class ActivityRecordTests extends WindowTestsBase {
any() /* window */, any() /* attrs */,
anyInt() /* viewVisibility */, anyInt() /* displayId */,
any() /* requestedVisibilities */, any() /* outInputChannel */,
- any() /* outInsetsState */, any() /* outActiveControls */);
+ any() /* outInsetsState */, any() /* outActiveControls */,
+ any() /* outAttachedFrame */);
mAtm.mWindowManager.mStartingSurfaceController
.createTaskSnapshotSurface(activity, snapshot);
} catch (RemoteException ignored) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 1176786eacc7..c78bc59612d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -37,6 +37,7 @@ import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
+import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
@@ -52,6 +53,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityStarter.canEmbedActivity;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -59,6 +65,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -87,6 +94,7 @@ import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.service.voice.IVoiceInteractionSession;
import android.util.Pair;
+import android.util.Size;
import android.view.Gravity;
import android.window.TaskFragmentOrganizerToken;
@@ -1171,6 +1179,7 @@ public class ActivityStarterTests extends WindowTestsBase {
null /* inTask */, taskFragment);
assertFalse(taskFragment.hasChild());
+ assertNotNull("Target record must be started on Task.", targetRecord.getParent().asTask());
}
@Test
@@ -1341,6 +1350,58 @@ public class ActivityStarterTests extends WindowTestsBase {
any());
}
+ @Test
+ public void testCanEmbedActivity() {
+ final Size minDimensions = new Size(1000, 1000);
+ final WindowLayout windowLayout = new WindowLayout(0, 0, 0, 0, 0,
+ minDimensions.getWidth(), minDimensions.getHeight());
+ final ActivityRecord starting = new ActivityBuilder(mAtm)
+ .setUid(UNIMPORTANT_UID)
+ .setWindowLayout(windowLayout)
+ .build();
+
+ // Task fragment hasn't attached to a task yet. Start activity to a new task.
+ TaskFragment taskFragment = new TaskFragmentBuilder(mAtm).build();
+ final Task task = new TaskBuilder(mSupervisor).build();
+
+ assertEquals(EMBEDDING_DISALLOWED_NEW_TASK,
+ canEmbedActivity(taskFragment, starting, task));
+
+ // Starting activity is going to be started on a task different from task fragment's parent
+ // task. Start activity to a new task.
+ task.addChild(taskFragment, POSITION_TOP);
+ final Task newTask = new TaskBuilder(mSupervisor).build();
+
+ assertEquals(EMBEDDING_DISALLOWED_NEW_TASK,
+ canEmbedActivity(taskFragment, starting, newTask));
+
+ // Make task fragment bounds exceed task bounds.
+ final Rect taskBounds = task.getBounds();
+ taskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.right + 1,
+ taskBounds.bottom + 1);
+
+ assertEquals(EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
+ canEmbedActivity(taskFragment, starting, task));
+
+ taskFragment.setBounds(taskBounds);
+ starting.info.flags |= FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
+
+ assertEquals(EMBEDDING_ALLOWED, canEmbedActivity(taskFragment, starting, task));
+
+ starting.info.flags &= ~FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
+ // Set task fragment's uid as the same as starting activity's uid.
+ taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class),
+ UNIMPORTANT_UID, "test");
+
+ assertEquals(EMBEDDING_ALLOWED, canEmbedActivity(taskFragment, starting, task));
+
+ // Make task fragment bounds smaller than starting activity's minimum dimensions
+ taskFragment.setBounds(0, 0, minDimensions.getWidth() - 1, minDimensions.getHeight() - 1);
+
+ assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
+ canEmbedActivity(taskFragment, starting, task));
+ }
+
private static void startActivityInner(ActivityStarter starter, ActivityRecord target,
ActivityRecord source, ActivityOptions options, Task inTask,
TaskFragment inTaskFragment) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 71f19148d616..b5764f54ff92 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -87,7 +87,7 @@ public class AppChangeTransitionTests extends WindowTestsBase {
}
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 8656a4fecef1..f2d6273f2b26 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -806,7 +806,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
}
@Override
- public void onAnimationCancelled() throws RemoteException {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
mFinishedCallback = null;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 436cf36587d8..74154609b22e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -522,7 +522,7 @@ public class AppTransitionTests extends WindowTestsBase {
}
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
mCancelled = true;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 2c1c38f3bee8..fc41a94d3355 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -181,7 +181,8 @@ public class BackNavigationControllerTests extends WindowTestsBase {
Task task = createTopTaskWithActivity();
WindowState appWindow = task.getTopVisibleAppMainWindow();
- WindowOnBackInvokedDispatcher dispatcher = new WindowOnBackInvokedDispatcher();
+ WindowOnBackInvokedDispatcher dispatcher =
+ new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */);
doAnswer(invocation -> {
appWindow.setOnBackInvokedCallbackInfo(invocation.getArgument(1));
return null;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d737963f80e7..40e266c71328 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1699,6 +1699,13 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(displayContent.mPinnedTaskController.isFreezingTaskConfig(pinnedTask));
assertEquals(pinnedActivity.getConfiguration().orientation,
displayContent.getConfiguration().orientation);
+
+ // No need to apply rotation if the display ignores orientation request.
+ doCallRealMethod().when(displayContent).rotationForActivityInDifferentOrientation(any());
+ pinnedActivity.mOrientation = SCREEN_ORIENTATION_LANDSCAPE;
+ displayContent.setIgnoreOrientationRequest(true);
+ assertEquals(WindowConfiguration.ROTATION_UNDEFINED,
+ displayContent.rotationForActivityInDifferentOrientation(pinnedActivity));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index db3a51ca4791..2956c14155b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -39,7 +39,6 @@ import static com.android.server.wm.SizeCompatTests.rotateDisplay;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -211,10 +210,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
assertThat(activityConfigBounds.width()).isEqualTo(activityBounds.width());
assertThat(activityConfigBounds.height()).isEqualTo(activityBounds.height());
assertThat(activitySizeCompatBounds.height()).isEqualTo(newTaskBounds.height());
- final float defaultAspectRatio = mFirstActivity.mWmService.mLetterboxConfiguration
- .getDefaultMinAspectRatioForUnresizableApps();
- assertEquals(activitySizeCompatBounds.width(),
- newTaskBounds.height() / defaultAspectRatio, 0.5);
+ assertThat(activitySizeCompatBounds.width()).isEqualTo(
+ newTaskBounds.height() * newTaskBounds.height() / newTaskBounds.width());
}
@Test
@@ -234,9 +231,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
assertThat(mFirstActivity.inSizeCompatMode()).isFalse();
assertThat(taskBounds).isEqualTo(dagBounds);
assertThat(activityBounds.width()).isEqualTo(dagBounds.width());
- final float defaultAspectRatio = mFirstActivity.mWmService.mLetterboxConfiguration
- .getDefaultMinAspectRatioForUnresizableApps();
- assertEquals(activityBounds.height(), dagBounds.width() / defaultAspectRatio, 0.5);
+ assertThat(activityBounds.height())
+ .isEqualTo(dagBounds.width() * dagBounds.width() / dagBounds.height());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 204c7e6fb8d4..027f5218f820 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -43,6 +43,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -210,7 +211,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
adapter.onAnimationCancelled(mMockLeash);
- verify(mMockRunner).onAnimationCancelled();
+ verify(mMockRunner).onAnimationCancelled(anyBoolean());
}
@Test
@@ -226,7 +227,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
mClock.fastForward(10500);
mHandler.timeAdvance();
- verify(mMockRunner).onAnimationCancelled();
+ verify(mMockRunner).onAnimationCancelled(anyBoolean());
verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
eq(adapter));
}
@@ -247,12 +248,12 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
mClock.fastForward(10500);
mHandler.timeAdvance();
- verify(mMockRunner, never()).onAnimationCancelled();
+ verify(mMockRunner, never()).onAnimationCancelled(anyBoolean());
mClock.fastForward(52500);
mHandler.timeAdvance();
- verify(mMockRunner).onAnimationCancelled();
+ verify(mMockRunner).onAnimationCancelled(anyBoolean());
verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
eq(adapter));
} finally {
@@ -264,7 +265,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
public void testZeroAnimations() throws Exception {
mController.goodToGo(TRANSIT_OLD_NONE);
verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any());
- verify(mMockRunner).onAnimationCancelled();
+ verify(mMockRunner).onAnimationCancelled(anyBoolean());
}
@Test
@@ -274,7 +275,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any());
- verify(mMockRunner).onAnimationCancelled();
+ verify(mMockRunner).onAnimationCancelled(anyBoolean());
}
@Test
@@ -316,7 +317,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
win.mActivityRecord.removeImmediately();
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any());
- verify(mMockRunner).onAnimationCancelled();
+ verify(mMockRunner).onAnimationCancelled(anyBoolean());
verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_APP_TRANSITION),
eq(adapter));
}
@@ -574,7 +575,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
// Cancel the wallpaper window animator and ensure the runner is not canceled
wallpaperWindowToken.cancelAnimation();
- verify(mMockRunner, never()).onAnimationCancelled();
+ verify(mMockRunner, never()).onAnimationCancelled(anyBoolean());
} finally {
mDisplayContent.mOpeningApps.clear();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 7f70882a70fc..324e244c46f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1471,6 +1471,8 @@ public class SizeCompatTests extends WindowTestsBase {
final float fixedOrientationLetterboxAspectRatio = 1.1f;
mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(
fixedOrientationLetterboxAspectRatio);
+ mActivity.mWmService.mLetterboxConfiguration.setDefaultMinAspectRatioForUnresizableApps(
+ 1.5f);
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
@@ -1496,7 +1498,9 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testSplitAspectRatioForUnresizablePortraitApps() {
// Set up a display in landscape and ignoring orientation request.
- setUpDisplaySizeWithApp(1600, 1400);
+ int screenWidth = 1600;
+ int screenHeight = 1400;
+ setUpDisplaySizeWithApp(screenWidth, screenHeight);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mActivity.mWmService.mLetterboxConfiguration
.setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true);
@@ -1520,6 +1524,7 @@ public class SizeCompatTests extends WindowTestsBase {
new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
// Move activity to split screen which takes half of the screen.
mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(screenWidth), screenHeight);
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
// Checking that there is no size compat mode.
@@ -1528,8 +1533,10 @@ public class SizeCompatTests extends WindowTestsBase {
@Test
public void testSplitAspectRatioForUnresizableLandscapeApps() {
- // Set up a display in landscape and ignoring orientation request.
- setUpDisplaySizeWithApp(1400, 1600);
+ // Set up a display in portrait and ignoring orientation request.
+ int screenWidth = 1400;
+ int screenHeight = 1600;
+ setUpDisplaySizeWithApp(screenWidth, screenHeight);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mActivity.mWmService.mLetterboxConfiguration
.setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true);
@@ -1553,6 +1560,7 @@ public class SizeCompatTests extends WindowTestsBase {
new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
// Move activity to split screen which takes half of the screen.
mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight));
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
// Checking that there is no size compat mode.
@@ -2071,12 +2079,7 @@ public class SizeCompatTests extends WindowTestsBase {
// Activity bounds fill split screen.
final Rect primarySplitBounds = new Rect(organizer.mPrimary.getBounds());
final Rect letterboxedBounds = new Rect(mActivity.getBounds());
- // Activity is letterboxed for aspect ratio.
- assertEquals(primarySplitBounds.height(), letterboxedBounds.height());
- final float defaultAspectRatio = mActivity.mWmService.mLetterboxConfiguration
- .getDefaultMinAspectRatioForUnresizableApps();
- assertEquals(primarySplitBounds.height() / defaultAspectRatio,
- letterboxedBounds.width(), 0.5);
+ assertEquals(primarySplitBounds, letterboxedBounds);
}
@Test
@@ -2618,6 +2621,16 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
}
+ private int getExpectedSplitSize(int dimensionToSplit) {
+ int dividerWindowWidth =
+ mActivity.mWmService.mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_thickness);
+ int dividerInsets =
+ mActivity.mWmService.mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_insets);
+ return (dimensionToSplit - (dividerWindowWidth - dividerInsets * 2)) / 2;
+ }
+
private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
float letterboxHorizontalPositionMultiplier) {
// Set up a display in landscape and ignoring orientation request.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 2a9fcb9d070b..7f09606d1c3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -762,12 +762,20 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
Task actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED,
ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */,
0 /* launchFlags */, candidateTask);
- assertSame(rootTask, actualRootTask.getRootTask());
+ assertSame(rootTask, actualRootTask);
// Verify the launch root task without candidate task
actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED,
ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */,
0 /* launchFlags */);
- assertSame(adjacentRootTask, actualRootTask.getRootTask());
+ assertSame(adjacentRootTask, actualRootTask);
+
+ final Task pinnedTask = createTask(
+ mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ // Verify not adjusting launch target for pinned candidate task
+ actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED,
+ ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */,
+ 0 /* launchFlags */, pinnedTask /* candidateTask */);
+ assertNull(actualRootTask);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 1c3b869e02d0..e47bcc98b908 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.testing.Assert.assertThrows;
@@ -530,7 +531,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mWindowOrganizerController.mLaunchTaskFragments
.put(mFragmentToken, mTaskFragment);
mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token);
- doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity);
+ doReturn(EMBEDDING_ALLOWED).when(mTaskFragment).isAllowedToEmbedActivity(activity);
clearInvocations(mAtm.mRootWindowContainer);
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
@@ -920,7 +921,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
.setOrganizer(mOrganizer)
.setBounds(mTaskFragBounds)
.build();
- doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity);
mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
// Reparent activity to mTaskFragment, which is smaller than activity's
@@ -956,7 +956,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
.setOrganizer(mOrganizer)
.setBounds(mTaskFragBounds)
.build();
- doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity);
mWindowOrganizerController.mLaunchTaskFragments.put(oldFragToken, oldTaskFrag);
mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 228cb65aab38..5f3096356bc5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -475,5 +475,13 @@ public class TaskFragmentTest extends WindowTestsBase {
assertFalse(activity0.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(activity1.isLetterboxedForFixedOrientationAndAspectRatio());
assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
+
+ tf0.setResumedActivity(activity0, "test");
+ tf1.setResumedActivity(activity1, "test");
+ mDisplayContent.mFocusedApp = activity1;
+
+ // Making the activity0 be the focused activity and ensure the focused app is updated.
+ activity0.moveFocusableActivityToTop("test");
+ assertEquals(activity0, mDisplayContent.mFocusedApp);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 5743922d0428..1715a295ded3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -979,7 +979,7 @@ public class WindowContainerTests extends WindowTestsBase {
}
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
}
}, 0, 0, false);
adapter.setCallingPidUid(123, 456);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
index ea18e58fe999..739e783e7c1b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
@@ -72,7 +72,7 @@ public class WindowLayoutTests {
private static final Insets WATERFALL_INSETS = Insets.of(6, 0, 12, 0);
private final WindowLayout mWindowLayout = new WindowLayout();
- private final ClientWindowFrames mOutFrames = new ClientWindowFrames();
+ private final ClientWindowFrames mFrames = new ClientWindowFrames();
private WindowManager.LayoutParams mAttrs;
private InsetsState mState;
@@ -82,7 +82,6 @@ public class WindowLayoutTests {
private int mRequestedWidth;
private int mRequestedHeight;
private InsetsVisibilities mRequestedVisibilities;
- private Rect mAttachedWindowFrame;
private float mCompatScale;
@Before
@@ -100,14 +99,14 @@ public class WindowLayoutTests {
mRequestedWidth = DISPLAY_WIDTH;
mRequestedHeight = DISPLAY_HEIGHT;
mRequestedVisibilities = new InsetsVisibilities();
- mAttachedWindowFrame = null;
mCompatScale = 1f;
+ mFrames.attachedFrame = null;
}
private void computeFrames() {
mWindowLayout.computeFrames(mAttrs, mState, mDisplayCutoutSafe, mWindowBounds,
mWindowingMode, mRequestedWidth, mRequestedHeight, mRequestedVisibilities,
- mAttachedWindowFrame, mCompatScale, mOutFrames);
+ mCompatScale, mFrames);
}
private void addDisplayCutout() {
@@ -145,9 +144,9 @@ public class WindowLayoutTests {
public void defaultParams() {
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame);
}
@Test
@@ -156,9 +155,9 @@ public class WindowLayoutTests {
mRequestedHeight = UNSPECIFIED_LENGTH;
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame);
}
@Test
@@ -172,9 +171,9 @@ public class WindowLayoutTests {
mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame);
- assertRect(0, STATUS_BAR_HEIGHT, width, STATUS_BAR_HEIGHT + height, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
+ assertRect(0, STATUS_BAR_HEIGHT, width, STATUS_BAR_HEIGHT + height, mFrames.frame);
}
@Test
@@ -186,11 +185,24 @@ public class WindowLayoutTests {
computeFrames();
assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
- mOutFrames.displayFrame);
+ mFrames.displayFrame);
assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
- mOutFrames.parentFrame);
+ mFrames.parentFrame);
assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
- mOutFrames.frame);
+ mFrames.frame);
+ }
+
+ @Test
+ public void attachedFrame() {
+ final int bottom = (DISPLAY_HEIGHT - STATUS_BAR_HEIGHT - NAVIGATION_BAR_HEIGHT) / 2;
+ mFrames.attachedFrame = new Rect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, bottom);
+ mRequestedWidth = UNSPECIFIED_LENGTH;
+ mRequestedHeight = UNSPECIFIED_LENGTH;
+ computeFrames();
+
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertEquals(mFrames.attachedFrame, mFrames.parentFrame);
+ assertEquals(mFrames.attachedFrame, mFrames.frame);
}
@Test
@@ -198,9 +210,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsTypes(WindowInsets.Type.statusBars());
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.frame);
}
@Test
@@ -208,9 +220,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsTypes(WindowInsets.Type.navigationBars());
computeFrames();
- assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame);
- assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mOutFrames.frame);
+ assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
+ assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.frame);
}
@Test
@@ -218,9 +230,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetByTopBottom(0, 0, mOutFrames.displayFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.parentFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.frame);
+ assertInsetByTopBottom(0, 0, mFrames.displayFrame);
+ assertInsetByTopBottom(0, 0, mFrames.parentFrame);
+ assertInsetByTopBottom(0, 0, mFrames.frame);
}
@Test
@@ -228,9 +240,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsSides(WindowInsets.Side.all());
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame);
}
@Test
@@ -238,9 +250,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsSides(WindowInsets.Side.TOP);
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.frame);
}
@Test
@@ -248,9 +260,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsSides(0);
computeFrames();
- assertInsetByTopBottom(0, 0, mOutFrames.displayFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.parentFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.frame);
+ assertInsetByTopBottom(0, 0, mFrames.displayFrame);
+ assertInsetByTopBottom(0, 0, mFrames.parentFrame);
+ assertInsetByTopBottom(0, 0, mFrames.frame);
}
@Test
@@ -259,9 +271,9 @@ public class WindowLayoutTests {
mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false);
computeFrames();
- assertInsetByTopBottom(0, 0, mOutFrames.displayFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.parentFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.frame);
+ assertInsetByTopBottom(0, 0, mFrames.displayFrame);
+ assertInsetByTopBottom(0, 0, mFrames.parentFrame);
+ assertInsetByTopBottom(0, 0, mFrames.frame);
}
@Test
@@ -271,9 +283,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsIgnoringVisibility(true);
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame);
}
@Test
@@ -284,9 +296,9 @@ public class WindowLayoutTests {
mAttrs.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mFrames.frame);
}
@Test
@@ -297,11 +309,11 @@ public class WindowLayoutTests {
computeFrames();
assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.displayFrame);
+ mFrames.displayFrame);
assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.parentFrame);
+ mFrames.parentFrame);
assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.frame);
+ mFrames.frame);
}
@Test
@@ -312,11 +324,11 @@ public class WindowLayoutTests {
computeFrames();
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.displayFrame);
+ mFrames.displayFrame);
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.parentFrame);
+ mFrames.parentFrame);
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.frame);
+ mFrames.frame);
}
@Test
@@ -327,9 +339,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.displayFrame);
- assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.parentFrame);
- assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.frame);
+ assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.displayFrame);
+ assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.parentFrame);
+ assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.frame);
}
@Test
@@ -344,9 +356,9 @@ public class WindowLayoutTests {
mAttrs.privateFlags |= PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
computeFrames();
- assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mOutFrames.displayFrame);
- assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mOutFrames.parentFrame);
- assertRect(0, 0, DISPLAY_WIDTH, height + DISPLAY_CUTOUT_HEIGHT, mOutFrames.frame);
+ assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mFrames.displayFrame);
+ assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mFrames.parentFrame);
+ assertRect(0, 0, DISPLAY_WIDTH, height + DISPLAY_CUTOUT_HEIGHT, mFrames.frame);
}
@Test
@@ -359,11 +371,11 @@ public class WindowLayoutTests {
computeFrames();
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.displayFrame);
+ mFrames.displayFrame);
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.parentFrame);
+ mFrames.parentFrame);
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.frame);
+ mFrames.frame);
}
@Test
@@ -373,9 +385,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.displayFrame);
- assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.parentFrame);
- assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.frame);
+ assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.displayFrame);
+ assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.parentFrame);
+ assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.frame);
}
@Test
@@ -386,11 +398,11 @@ public class WindowLayoutTests {
computeFrames();
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.displayFrame);
+ mFrames.displayFrame);
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.parentFrame);
+ mFrames.parentFrame);
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.frame);
+ mFrames.frame);
}
@Test
@@ -400,8 +412,8 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetByTopBottom(0, 0, mOutFrames.displayFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.parentFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.frame);
+ assertInsetByTopBottom(0, 0, mFrames.displayFrame);
+ assertInsetByTopBottom(0, 0, mFrames.parentFrame);
+ assertInsetByTopBottom(0, 0, mFrames.frame);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index a0c20c2c6b68..1a64f5e3a356 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -49,6 +49,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.pm.PackageManager;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -60,6 +61,7 @@ import android.view.InsetsState;
import android.view.InsetsVisibilities;
import android.view.View;
import android.view.WindowManager;
+import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
@@ -281,7 +283,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY,
UserHandle.USER_SYSTEM, new InsetsVisibilities(), null, new InsetsState(),
- new InsetsSourceControl[0]);
+ new InsetsSourceControl[0], new Rect());
verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
any(), anyInt(), anyInt(), any());
@@ -316,4 +318,76 @@ public class WindowManagerServiceTests extends WindowTestsBase {
verify(mWm.mInputManager).setInTouchMode(
!currentTouchMode, callingPid, callingUid, /* hasPermission= */ false);
}
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() {
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null);
+ assertThat(wct).isNull();
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() {
+ Binder cookie = new Binder("test cookie");
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
+ assertThat(wct).isNull();
+
+ final ActivityRecord testActivity = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
+ .build();
+
+ wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
+ assertThat(wct).isNull();
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() {
+ final Binder cookie = new Binder("ginger cookie");
+ final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
+ setupActivityWithLaunchCookie(cookie, launchRootTask);
+
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
+ assertThat(wct).isEqualTo(launchRootTask);
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() {
+ final Binder cookie1 = new Binder("ginger cookie");
+ final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class);
+ setupActivityWithLaunchCookie(cookie1, launchRootTask1);
+
+ setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
+ mock(WindowContainerToken.class));
+
+ setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
+ mock(WindowContainerToken.class));
+
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie1);
+ assertThat(wct).isEqualTo(launchRootTask1);
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() {
+ setupActivityWithLaunchCookie(new Binder("ginger cookie"),
+ mock(WindowContainerToken.class));
+
+ setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
+ mock(WindowContainerToken.class));
+
+ setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
+ mock(WindowContainerToken.class));
+
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(
+ new Binder("some other cookie"));
+ assertThat(wct).isNull();
+ }
+
+ private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
+ final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
+ when(remoteToken.toWindowContainerToken()).thenReturn(wct);
+ final ActivityRecord testActivity = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
+ .build();
+ testActivity.mLaunchCookie = launchCookie;
+ testActivity.getTask().mRemoteToken = remoteToken;
+ }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index cc33f88d396d..26a1e9d8af11 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -16,6 +16,7 @@
package com.android.server.usage;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents;
@@ -24,6 +25,7 @@ import android.app.usage.UsageStatsManager;
import android.os.Build;
import android.os.SystemProperties;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
@@ -55,8 +57,11 @@ import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
* Provides an interface to query for UsageStat data from a Protocol Buffer database.
@@ -1252,6 +1257,10 @@ public class UsageStatsDatabase {
Slog.wtf(TAG, "Attempting to backup UsageStats as XML with version " + version);
return null;
}
+ if (version < 1 || version > BACKUP_VERSION) {
+ Slog.wtf(TAG, "Attempting to backup UsageStats with an unknown version: " + version);
+ return null;
+ }
synchronized (mLock) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (KEY_USAGE_STATS.equals(key)) {
@@ -1300,14 +1309,26 @@ public class UsageStatsDatabase {
}
return baos.toByteArray();
}
+ }
+ /**
+ * Updates the set of packages given to only include those that have been used within the
+ * given timeframe (as defined by {@link UsageStats#getLastTimePackageUsed()}).
+ */
+ private void calculatePackagesUsedWithinTimeframe(
+ IntervalStats stats, Set<String> packagesList, long timeframeMs) {
+ for (UsageStats stat : stats.packageStats.values()) {
+ if (stat.getLastTimePackageUsed() > timeframeMs) {
+ packagesList.add(stat.mPackageName);
+ }
+ }
}
/**
* @hide
*/
@VisibleForTesting
- public void applyRestoredPayload(String key, byte[] payload) {
+ public @NonNull Set<String> applyRestoredPayload(String key, byte[] payload) {
synchronized (mLock) {
if (KEY_USAGE_STATS.equals(key)) {
// Read stats files for the current device configs
@@ -1320,12 +1341,15 @@ public class UsageStatsDatabase {
IntervalStats yearlyConfigSource =
getLatestUsageStats(UsageStatsManager.INTERVAL_YEARLY);
+ final Set<String> packagesRestored = new ArraySet<>();
try {
DataInputStream in = new DataInputStream(new ByteArrayInputStream(payload));
int backupDataVersion = in.readInt();
// Can't handle this backup set
- if (backupDataVersion < 1 || backupDataVersion > BACKUP_VERSION) return;
+ if (backupDataVersion < 1 || backupDataVersion > BACKUP_VERSION) {
+ return packagesRestored;
+ }
// Delete all stats files
// Do this after reading version and before actually restoring
@@ -1333,10 +1357,14 @@ public class UsageStatsDatabase {
deleteDirectoryContents(mIntervalDirs[i]);
}
+ // 90 days before today in epoch
+ final long timeframe = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(90);
int fileCount = in.readInt();
for (int i = 0; i < fileCount; i++) {
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in),
backupDataVersion);
+ calculatePackagesUsedWithinTimeframe(stats, packagesRestored, timeframe);
+ packagesRestored.addAll(stats.packageStats.keySet());
stats = mergeStats(stats, dailyConfigSource);
putUsageStats(UsageStatsManager.INTERVAL_DAILY, stats);
}
@@ -1345,6 +1373,7 @@ public class UsageStatsDatabase {
for (int i = 0; i < fileCount; i++) {
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in),
backupDataVersion);
+ calculatePackagesUsedWithinTimeframe(stats, packagesRestored, timeframe);
stats = mergeStats(stats, weeklyConfigSource);
putUsageStats(UsageStatsManager.INTERVAL_WEEKLY, stats);
}
@@ -1353,6 +1382,7 @@ public class UsageStatsDatabase {
for (int i = 0; i < fileCount; i++) {
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in),
backupDataVersion);
+ calculatePackagesUsedWithinTimeframe(stats, packagesRestored, timeframe);
stats = mergeStats(stats, monthlyConfigSource);
putUsageStats(UsageStatsManager.INTERVAL_MONTHLY, stats);
}
@@ -1361,6 +1391,7 @@ public class UsageStatsDatabase {
for (int i = 0; i < fileCount; i++) {
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in),
backupDataVersion);
+ calculatePackagesUsedWithinTimeframe(stats, packagesRestored, timeframe);
stats = mergeStats(stats, yearlyConfigSource);
putUsageStats(UsageStatsManager.INTERVAL_YEARLY, stats);
}
@@ -1370,7 +1401,9 @@ public class UsageStatsDatabase {
} finally {
indexFilesLocked();
}
+ return packagesRestored;
}
+ return Collections.EMPTY_SET;
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index ef13cd964f6c..f595c3de104e 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -3034,7 +3034,8 @@ public class UsageStatsService extends SystemService implements
if (userStats == null) {
return; // user was stopped or removed
}
- userStats.applyRestoredPayload(key, payload);
+ final Set<String> restoredApps = userStats.applyRestoredPayload(key, payload);
+ mAppStandby.restoreAppsToRare(restoredApps, user);
}
}
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index c609add0b5d7..34c6c1623eec 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -63,6 +63,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Set;
/**
* A per-user UsageStatsService. All methods are meant to be called with the main lock held
@@ -1374,8 +1375,8 @@ class UserUsageStatsService {
return mDatabase.getBackupPayload(key);
}
- void applyRestoredPayload(String key, byte[] payload){
+ Set<String> applyRestoredPayload(String key, byte[] payload) {
checkAndGetTimeLocked();
- mDatabase.applyRestoredPayload(key, payload);
+ return mDatabase.applyRestoredPayload(key, payload);
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 434663bd8167..25db81fa2667 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -31,11 +31,18 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERV
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED;
@@ -146,6 +153,13 @@ final class HotwordDetectionConnection {
private static final int METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK =
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
+ private static final int METRICS_EXTERNAL_SOURCE_DETECTED =
+ HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
+ private static final int METRICS_EXTERNAL_SOURCE_REJECTED =
+ HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
+ private static final int METRICS_EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION =
+ HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
+
private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
// TODO: This may need to be a Handler(looper)
private final ScheduledExecutorService mScheduledExecutorService =
@@ -382,6 +396,10 @@ final class HotwordDetectionConnection {
}
void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE,
+ mVoiceInteractionServiceUid);
+
// Prevent doing the init late, so restart is handled equally to a clean process start.
// TODO(b/191742511): this logic needs a test
if (!mUpdateStateAfterStartFinished.get()
@@ -422,14 +440,23 @@ final class HotwordDetectionConnection {
Slog.d(TAG, "onDetected");
}
synchronized (mLock) {
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
if (!mPerformingSoftwareHotwordDetection) {
Slog.i(TAG, "Hotword detection has already completed");
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
return;
}
mPerformingSoftwareHotwordDetection = false;
try {
enforcePermissionsForDataDelivery();
} catch (SecurityException e) {
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
mSoftwareCallback.onError();
return;
}
@@ -449,6 +476,9 @@ final class HotwordDetectionConnection {
if (DEBUG) {
Slog.wtf(TAG, "onRejected");
}
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
// onRejected isn't allowed here, and we are not expecting it.
}
};
@@ -460,6 +490,9 @@ final class HotwordDetectionConnection {
null,
null,
internalCallback));
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION,
+ mVoiceInteractionServiceUid);
}
public void startListeningFromExternalSource(
@@ -891,6 +924,9 @@ final class HotwordDetectionConnection {
@Override
public void onRejected(HotwordRejectedResult result)
throws RemoteException {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ METRICS_EXTERNAL_SOURCE_REJECTED,
+ mVoiceInteractionServiceUid);
mScheduledExecutorService.schedule(
() -> {
bestEffortClose(serviceAudioSink, audioSource);
@@ -912,6 +948,9 @@ final class HotwordDetectionConnection {
@Override
public void onDetected(HotwordDetectedResult triggerResult)
throws RemoteException {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ METRICS_EXTERNAL_SOURCE_DETECTED,
+ mVoiceInteractionServiceUid);
mScheduledExecutorService.schedule(
() -> {
bestEffortClose(serviceAudioSink, audioSource);
@@ -922,6 +961,9 @@ final class HotwordDetectionConnection {
try {
enforcePermissionsForDataDelivery();
} catch (SecurityException e) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ METRICS_EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION,
+ mVoiceInteractionServiceUid);
callback.onError();
return;
}
@@ -942,6 +984,9 @@ final class HotwordDetectionConnection {
// A copy of this has been created and passed to the hotword validator
bestEffortClose(serviceAudioSource);
});
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION,
+ mVoiceInteractionServiceUid);
}
private class ServiceConnectionFactory {
@@ -1002,7 +1047,12 @@ final class HotwordDetectionConnection {
return;
}
mIsBound = connected;
- if (connected && !mIsLoggedFirstConnect) {
+
+ if (!connected) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED,
+ mVoiceInteractionServiceUid);
+ } else if (!mIsLoggedFirstConnect) {
mIsLoggedFirstConnect = true;
HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 0ddd52dfc76d..64a86db38396 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -22,6 +22,7 @@ import android.annotation.SystemApi;
import android.app.Service;
import android.app.UiModeManager;
import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
@@ -47,7 +48,7 @@ import java.util.List;
* in a call. It also provides the user with a means to initiate calls and see a history of calls
* on their device. A device is bundled with a system provided default dialer/phone app. The user
* may choose a single app to take over this role from the system app. An app which wishes to
- * fulfill one this role uses the {@link android.app.role.RoleManager} to request that they fill the
+ * fulfill this role uses the {@link android.app.role.RoleManager} to request that they fill the
* {@link android.app.role.RoleManager#ROLE_DIALER} role.
* <p>
* The default phone app provides a user interface while the device is in a call, and the device is
@@ -63,13 +64,23 @@ import java.util.List;
* UI, as well as an ongoing call UI.</li>
* </ul>
* <p>
- * Note: If the app filling the {@link android.app.role.RoleManager#ROLE_DIALER} crashes during
- * {@link InCallService} binding, the Telecom framework will automatically fall back to using the
- * dialer app pre-loaded on the device. The system will display a notification to the user to let
- * them know that the app has crashed and that their call was continued using the pre-loaded dialer
- * app.
+ * Note: If the app filling the {@link android.app.role.RoleManager#ROLE_DIALER} returns a
+ * {@code null} {@link InCallService} during binding, the Telecom framework will automatically fall
+ * back to using the dialer app preloaded on the device. The system will display a notification to
+ * the user to let them know that their call was continued using the preloaded dialer app. Your
+ * app should never return a {@code null} binding; doing so means it does not fulfil the
+ * requirements of {@link android.app.role.RoleManager#ROLE_DIALER}.
* <p>
- * The pre-loaded dialer will ALWAYS be used when the user places an emergency call, even if your
+ * Note: If your app fills {@link android.app.role.RoleManager#ROLE_DIALER} and makes changes at
+ * runtime which cause it to no longer fulfil the requirements of this role,
+ * {@link android.app.role.RoleManager} will automatically remove your app from the role and close
+ * your app. For example, if you use
+ * {@link android.content.pm.PackageManager#setComponentEnabledSetting(ComponentName, int, int)} to
+ * programmatically disable the {@link InCallService} your app declares in its manifest, your app
+ * will no longer fulfil the requirements expected of
+ * {@link android.app.role.RoleManager#ROLE_DIALER}.
+ * <p>
+ * The preloaded dialer will ALWAYS be used when the user places an emergency call, even if your
* app fills the {@link android.app.role.RoleManager#ROLE_DIALER} role. To ensure an optimal
* experience when placing an emergency call, the default dialer should ALWAYS use
* {@link android.telecom.TelecomManager#placeCall(Uri, Bundle)} to place calls (including
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 0e5a1775bd6a..315c40ffa9ba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -21,8 +21,10 @@ import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.traces.region.RegionSubject
import com.android.server.wm.traces.common.FlickerComponentName
-val LAUNCHER_COMPONENT = FlickerComponentName("com.google.android.apps.nexuslauncher",
- "com.google.android.apps.nexuslauncher.NexusLauncherActivity")
+val LAUNCHER_COMPONENT = FlickerComponentName(
+ "com.google.android.apps.nexuslauncher",
+ "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
+)
/**
* Checks that [FlickerComponentName.STATUS_BAR] window is visible and above the app windows in
@@ -110,9 +112,9 @@ fun FlickerTestParameter.statusBarLayerIsVisible() {
fun FlickerTestParameter.navBarLayerPositionStart() {
assertLayersStart {
val display = this.entry.displays.minByOrNull { it.id }
- ?: throw RuntimeException("There is no display!")
+ ?: throw RuntimeException("There is no display!")
this.visibleRegion(FlickerComponentName.NAV_BAR)
- .coversExactly(WindowUtils.getNavigationBarPosition(display))
+ .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
}
}
@@ -123,9 +125,9 @@ fun FlickerTestParameter.navBarLayerPositionStart() {
fun FlickerTestParameter.navBarLayerPositionEnd() {
assertLayersEnd {
val display = this.entry.displays.minByOrNull { it.id }
- ?: throw RuntimeException("There is no display!")
+ ?: throw RuntimeException("There is no display!")
this.visibleRegion(FlickerComponentName.NAV_BAR)
- .coversExactly(WindowUtils.getNavigationBarPosition(display))
+ .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
}
}
@@ -244,11 +246,11 @@ fun FlickerTestParameter.replacesLayer(
assertLayersStart {
this.isVisible(originalLayer)
- .isInvisible(newLayer)
+ .isInvisible(newLayer)
}
assertLayersEnd {
this.isInvisible(originalLayer)
- .isVisible(newLayer)
+ .isVisible(newLayer)
}
}