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/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/hardware/display/DisplayManager.java10
-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.java151
-rw-r--r--core/java/android/view/WindowLayout.java20
-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/SizeConfigurationBuckets.java17
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java12
-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/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/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/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/keyguard_bottom_area.xml20
-rw-r--r--packages/SystemUI/res/layout/media_ttt_chip.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/config.xml6
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml15
-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/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.java22
-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.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt3
-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.java69
-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.java28
-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.kt43
-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/CentralSurfaces.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java587
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java698
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java253
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java6
-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/statusbar/policy/PreviewInflater.java150
-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/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.kt19
-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.java74
-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.java84
-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/CentralSurfacesImplTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java3
-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--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/am/TraceErrorLogger.java3
-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.java478
-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/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/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.java54
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java13
-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.java15
-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/Session.java41
-rw-r--r--services/core/java/com/android/server/wm/StartingSurfaceController.java5
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java106
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java89
-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/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/wmtests/src/com/android/server/wm/ActivityRecordTests.java15
-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/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/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
292 files changed, 7281 insertions, 3847 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/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/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/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 f1976319794a..64151d9d6c3f 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;
@@ -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;
@@ -1187,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; */
@@ -1218,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;
@@ -1252,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)) {
@@ -1375,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.
*/
@@ -1740,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;
}
@@ -1767,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);
@@ -2700,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;
@@ -2969,6 +2980,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;
@@ -2977,6 +2990,7 @@ public final class ViewRootImpl implements ViewParent,
}
reportNextDraw();
mSyncBuffer = true;
+ isSyncRequest = true;
}
final boolean surfaceControlChanged =
@@ -3265,6 +3279,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
@@ -3483,7 +3510,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();
}
@@ -8008,69 +8037,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);
@@ -8119,10 +8099,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;
}
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/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/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/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/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/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 242e9ab6beee..41791afa45a3 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;
@@ -381,6 +381,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
@@ -581,8 +582,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) {
@@ -606,6 +608,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);
@@ -616,25 +619,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);
@@ -642,6 +645,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;
@@ -787,6 +795,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,
@@ -800,16 +809,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,
@@ -863,6 +868,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) {
@@ -876,7 +882,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);
}
@@ -1119,6 +1125,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/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/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/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 8f8993f3c8d9..12dfa1042dd7 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -59,26 +59,6 @@
</LinearLayout>
- <com.android.systemui.statusbar.KeyguardAffordanceView
- android:id="@+id/camera_button"
- android:layout_height="@dimen/keyguard_affordance_height"
- android:layout_width="@dimen/keyguard_affordance_width"
- android:layout_gravity="bottom|end"
- android:src="@drawable/ic_camera_alt_24dp"
- android:scaleType="center"
- android:contentDescription="@string/accessibility_camera_button"
- android:tint="?attr/wallpaperTextColor" />
-
- <com.android.systemui.statusbar.KeyguardAffordanceView
- android:id="@+id/left_button"
- android:layout_height="@dimen/keyguard_affordance_height"
- android:layout_width="@dimen/keyguard_affordance_width"
- android:layout_gravity="bottom|start"
- android:src="@*android:drawable/ic_phone"
- android:scaleType="center"
- android:contentDescription="@string/accessibility_phone_button"
- android:tint="?attr/wallpaperTextColor" />
-
<ImageView
android:id="@+id/wallet_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
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/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/config.xml b/packages/SystemUI/res/values/config.xml
index eff4e00fd07b..771973c36053 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -37,12 +37,6 @@
<item>400</item>
</integer-array>
- <!-- Show mic or phone affordance on Keyguard -->
- <bool name="config_keyguardShowLeftAffordance">false</bool>
-
- <!-- Show camera affordance on Keyguard -->
- <bool name="config_keyguardShowCameraAffordance">false</bool>
-
<!-- decay duration (from size_max -> size), in ms -->
<integer name="navigation_bar_deadzone_hold">333</integer>
<integer name="navigation_bar_deadzone_decay">333</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4d365412828e..7f7b8716d27c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -676,9 +676,6 @@
<!-- The minimum background radius when swiping to a side for the camera / phone affordances. -->
<dimen name="keyguard_affordance_min_background_radius">30dp</dimen>
- <!-- The size of the touch targets on the keyguard for the affordances. -->
- <dimen name="keyguard_affordance_touch_target_size">120dp</dimen>
-
<!-- The grow amount for the camera and phone circles when hinting -->
<dimen name="hint_grow_amount_sideways">60dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d88428f972cb..ef672f3a6213 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -313,13 +313,6 @@
<string name="accessibility_scanning_face">Scanning face</string>
<!-- Click action label for accessibility for the smart reply buttons (not shown on-screen).". [CHAR LIMIT=NONE] -->
<string name="accessibility_send_smart_reply">Send</string>
- <!-- Content description of the manage notification button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
- <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] -->
- <string name="phone_label">open phone</string>
- <!-- Click action label for accessibility for the voice assist button. This is not shown on-screen and is an accessibility label for the icon which launches the voice assist from the lock screen.[CHAR LIMIT=NONE] -->
- <string name="voice_assist_label">open voice assist</string>
- <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] -->
- <string name="camera_label">open camera</string>
<!-- Button name for "Cancel". [CHAR LIMIT=NONE] -->
<string name="cancel">Cancel</string>
@@ -2581,4 +2574,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/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..f62445d46345 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -911,12 +911,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, false /* animate */);
Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
+ mOccluded);
}
@@ -2503,10 +2503,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 +3175,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,8 +3218,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
}
@Override
- public void onAnimationCancelled() throws RemoteException {
- super.onAnimationCancelled();
+ public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
+ super.onAnimationCancelled(isKeyguardOccluded);
Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: " + mOccluded);
}
}
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..fe1ac80e24df 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
@@ -31,6 +31,10 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+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 android.widget.LinearLayout
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
@@ -56,6 +60,7 @@ 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
@@ -110,10 +115,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..a5d763c5327b 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
@@ -26,6 +26,7 @@ import android.os.PowerManager
import android.util.Log
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
@@ -52,6 +53,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
windowManager: WindowManager,
viewUtil: ViewUtil,
mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
tapGestureDetector: TapGestureDetector,
powerManager: PowerManager,
@Main private val mainHandler: Handler,
@@ -62,6 +64,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
windowManager,
viewUtil,
mainExecutor,
+ accessibilityManager,
tapGestureDetector,
powerManager,
R.layout.media_ttt_chip_receiver
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..943604cff887 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
@@ -24,6 +24,7 @@ import android.util.Log
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 +54,7 @@ class MediaTttChipControllerSender @Inject constructor(
windowManager: WindowManager,
viewUtil: ViewUtil,
@Main mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
tapGestureDetector: TapGestureDetector,
powerManager: PowerManager,
private val uiEventLogger: MediaTttSenderUiEventLogger
@@ -62,6 +64,7 @@ class MediaTttChipControllerSender @Inject constructor(
windowManager,
viewUtil,
mainExecutor,
+ accessibilityManager,
tapGestureDetector,
powerManager,
R.layout.media_ttt_chip
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..e345aabc6215 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,7 +304,7 @@ 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);
}
}
}
@@ -342,7 +347,7 @@ 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);
}
@@ -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);
@@ -416,12 +421,12 @@ 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;
}
@@ -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,10 +586,27 @@ 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());
}
@@ -627,10 +650,7 @@ 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)) {
@@ -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,17 +941,17 @@ 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
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..6441d2fd94af 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
@@ -579,11 +579,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 +986,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 +995,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 +1080,7 @@ public class ShadeListBuilder implements Dumpable {
if (!Objects.equals(curr, prev)) {
mLogger.logEntryAttachStateChanged(
mIterationCount,
- entry.getKey(),
+ entry,
prev.getParent(),
curr.getParent());
@@ -1209,7 +1219,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..8d1759b8f475 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
@@ -23,8 +23,10 @@ import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotificationLog
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 javax.inject.Inject
class ShadeListBuilderLogger @Inject constructor(
@@ -110,12 +112,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 +131,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 +139,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 +167,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 +187,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 +200,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'"
})
@@ -281,7 +288,7 @@ class ShadeListBuilderLogger @Inject constructor(
val entry = entries[i]
buffer.log(TAG, DEBUG, {
int1 = i
- str1 = entry.key
+ str1 = entry.logKey
}, {
"[$int1] $str1"
})
@@ -289,7 +296,7 @@ class ShadeListBuilderLogger @Inject constructor(
if (entry is GroupEntry) {
entry.summary?.let {
buffer.log(TAG, DEBUG, {
- str1 = it.key
+ str1 = it.logKey
}, {
" [*] $str1 (summary)"
})
@@ -298,7 +305,7 @@ class ShadeListBuilderLogger @Inject constructor(
val child = entry.children[j]
buffer.log(TAG, DEBUG, {
int1 = j
- str1 = child.key
+ str1 = child.logKey
}, {
" [$int1] $str1"
})
@@ -308,7 +315,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
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/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 2c22bc68c198..d25bbbd2a591 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -427,12 +427,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
void onHintFinished();
- void onCameraHintStarted();
-
- void onVoiceAssistHintStarted();
-
- void onPhoneHintStarted();
-
void onTrackingStopped(boolean expand);
// TODO: Figure out way to remove these.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 9060d5f67913..38c37f03f643 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -388,8 +388,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.reset(true /* hide */);
}
- mNotificationPanelViewController.launchCamera(
- mCentralSurfaces.isDeviceInteractive() /* animate */, source);
+ mNotificationPanelViewController.launchCamera(source);
mCentralSurfaces.updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5181af773d9a..4f99deaaf799 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1285,8 +1285,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
backdrop.setScaleY(scale);
});
- mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
-
// Set up the quick settings tile panel
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
if (container != null) {
@@ -3024,8 +3022,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
@Override
public boolean isInLaunchTransition() {
- return mNotificationPanelViewController.isLaunchTransitionRunning()
- || mNotificationPanelViewController.isLaunchTransitionFinished();
+ return mNotificationPanelViewController.isLaunchTransitionFinished();
}
/**
@@ -3057,11 +3054,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
mCommandQueue.appTransitionStarting(mDisplayId, SystemClock.uptimeMillis(),
LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
};
- if (mNotificationPanelViewController.isLaunchTransitionRunning()) {
- mNotificationPanelViewController.setLaunchTransitionEndRunnable(hideRunnable);
- } else {
- hideRunnable.run();
- }
+ hideRunnable.run();
}
private void cancelAfterLaunchTransitionRunnables() {
@@ -3070,7 +3063,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
}
mLaunchTransitionEndRunnable = null;
mLaunchTransitionCancelRunnable = null;
- mNotificationPanelViewController.setLaunchTransitionEndRunnable(null);
}
/**
@@ -3499,24 +3491,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
}
@Override
- public void onCameraHintStarted() {
- mFalsingCollector.onCameraHintStarted();
- mKeyguardIndicationController.showTransientIndication(R.string.camera_hint);
- }
-
- @Override
- public void onVoiceAssistHintStarted() {
- mFalsingCollector.onLeftAffordanceHintStarted();
- mKeyguardIndicationController.showTransientIndication(R.string.voice_hint);
- }
-
- @Override
- public void onPhoneHintStarted() {
- mFalsingCollector.onLeftAffordanceHintStarted();
- mKeyguardIndicationController.showTransientIndication(R.string.phone_hint);
- }
-
- @Override
public void onTrackingStopped(boolean expand) {
}
@@ -3700,8 +3674,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
mWakeUpCoordinator.setFullyAwake(true);
mWakeUpCoordinator.setWakingUp(false);
if (mLaunchCameraWhenFinishedWaking) {
- mNotificationPanelViewController.launchCamera(
- false /* animate */, mLastCameraLaunchSource);
+ mNotificationPanelViewController.launchCamera(mLastCameraLaunchSource);
mLaunchCameraWhenFinishedWaking = false;
}
if (mLaunchEmergencyActionWhenFinishedWaking) {
@@ -4343,9 +4316,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
if (!mUserSetup) {
animateCollapseQuickSettings();
}
- if (mNotificationPanelViewController != null) {
- mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
- }
updateQsExpansionEnabled();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
deleted file mode 100644
index 2922b4c98d34..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ /dev/null
@@ -1,587 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.classifier.Classifier;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.KeyguardAffordanceView;
-import com.android.wm.shell.animation.FlingAnimationUtils;
-
-/**
- * A touch handler of the keyguard which is responsible for launching phone and camera affordances.
- */
-public class KeyguardAffordanceHelper {
-
- public static final long HINT_PHASE1_DURATION = 200;
- private static final long HINT_PHASE2_DURATION = 350;
- private static final float BACKGROUND_RADIUS_SCALE_FACTOR = 0.25f;
- private static final int HINT_CIRCLE_OPEN_DURATION = 500;
-
- private final Context mContext;
- private final Callback mCallback;
-
- private FlingAnimationUtils mFlingAnimationUtils;
- private VelocityTracker mVelocityTracker;
- private boolean mSwipingInProgress;
- private float mInitialTouchX;
- private float mInitialTouchY;
- private float mTranslation;
- private float mTranslationOnDown;
- private int mTouchSlop;
- private int mMinTranslationAmount;
- private int mMinFlingVelocity;
- private int mHintGrowAmount;
- private KeyguardAffordanceView mLeftIcon;
- private KeyguardAffordanceView mRightIcon;
- private Animator mSwipeAnimator;
- private final FalsingManager mFalsingManager;
- private int mMinBackgroundRadius;
- private boolean mMotionCancelled;
- private int mTouchTargetSize;
- private View mTargetedView;
- private boolean mTouchSlopExeeded;
- private AnimatorListenerAdapter mFlingEndListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mSwipeAnimator = null;
- mSwipingInProgress = false;
- mTargetedView = null;
- }
- };
- private Runnable mAnimationEndRunnable = new Runnable() {
- @Override
- public void run() {
- mCallback.onAnimationToSideEnded();
- }
- };
-
- KeyguardAffordanceHelper(Callback callback, Context context, FalsingManager falsingManager) {
- mContext = context;
- mCallback = callback;
- initIcons();
- updateIcon(mLeftIcon, 0.0f, mLeftIcon.getRestingAlpha(), false, false, true, false);
- updateIcon(mRightIcon, 0.0f, mRightIcon.getRestingAlpha(), false, false, true, false);
- mFalsingManager = falsingManager;
- initDimens();
- }
-
- private void initDimens() {
- final ViewConfiguration configuration = ViewConfiguration.get(mContext);
- mTouchSlop = configuration.getScaledPagingTouchSlop();
- mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
- mMinTranslationAmount = mContext.getResources().getDimensionPixelSize(
- R.dimen.keyguard_min_swipe_amount);
- mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
- R.dimen.keyguard_affordance_min_background_radius);
- mTouchTargetSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.keyguard_affordance_touch_target_size);
- mHintGrowAmount =
- mContext.getResources().getDimensionPixelSize(R.dimen.hint_grow_amount_sideways);
- mFlingAnimationUtils = new FlingAnimationUtils(mContext.getResources().getDisplayMetrics(),
- 0.4f);
- }
-
- private void initIcons() {
- mLeftIcon = mCallback.getLeftIcon();
- mRightIcon = mCallback.getRightIcon();
- updatePreviews();
- }
-
- public void updatePreviews() {
- mLeftIcon.setPreviewView(mCallback.getLeftPreview());
- mRightIcon.setPreviewView(mCallback.getRightPreview());
- }
-
- public boolean onTouchEvent(MotionEvent event) {
- int action = event.getActionMasked();
- if (mMotionCancelled && action != MotionEvent.ACTION_DOWN) {
- return false;
- }
- final float y = event.getY();
- final float x = event.getX();
-
- boolean isUp = false;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- View targetView = getIconAtPosition(x, y);
- if (targetView == null || (mTargetedView != null && mTargetedView != targetView)) {
- mMotionCancelled = true;
- return false;
- }
- if (mTargetedView != null) {
- cancelAnimation();
- } else {
- mTouchSlopExeeded = false;
- }
- startSwiping(targetView);
- mInitialTouchX = x;
- mInitialTouchY = y;
- mTranslationOnDown = mTranslation;
- initVelocityTracker();
- trackMovement(event);
- mMotionCancelled = false;
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- mMotionCancelled = true;
- endMotion(true /* forceSnapBack */, x, y);
- break;
- case MotionEvent.ACTION_MOVE:
- trackMovement(event);
- float xDist = x - mInitialTouchX;
- float yDist = y - mInitialTouchY;
- float distance = (float) Math.hypot(xDist, yDist);
- if (!mTouchSlopExeeded && distance > mTouchSlop) {
- mTouchSlopExeeded = true;
- }
- if (mSwipingInProgress) {
- if (mTargetedView == mRightIcon) {
- distance = mTranslationOnDown - distance;
- distance = Math.min(0, distance);
- } else {
- distance = mTranslationOnDown + distance;
- distance = Math.max(0, distance);
- }
- setTranslation(distance, false /* isReset */, false /* animateReset */);
- }
- break;
-
- case MotionEvent.ACTION_UP:
- isUp = true;
- case MotionEvent.ACTION_CANCEL:
- boolean hintOnTheRight = mTargetedView == mRightIcon;
- trackMovement(event);
- endMotion(!isUp, x, y);
- if (!mTouchSlopExeeded && isUp) {
- mCallback.onIconClicked(hintOnTheRight);
- }
- break;
- }
- return true;
- }
-
- private void startSwiping(View targetView) {
- mCallback.onSwipingStarted(targetView == mRightIcon);
- mSwipingInProgress = true;
- mTargetedView = targetView;
- }
-
- private View getIconAtPosition(float x, float y) {
- if (leftSwipePossible() && isOnIcon(mLeftIcon, x, y)) {
- return mLeftIcon;
- }
- if (rightSwipePossible() && isOnIcon(mRightIcon, x, y)) {
- return mRightIcon;
- }
- return null;
- }
-
- public boolean isOnAffordanceIcon(float x, float y) {
- return isOnIcon(mLeftIcon, x, y) || isOnIcon(mRightIcon, x, y);
- }
-
- private boolean isOnIcon(View icon, float x, float y) {
- float iconX = icon.getX() + icon.getWidth() / 2.0f;
- float iconY = icon.getY() + icon.getHeight() / 2.0f;
- double distance = Math.hypot(x - iconX, y - iconY);
- return distance <= mTouchTargetSize / 2;
- }
-
- private void endMotion(boolean forceSnapBack, float lastX, float lastY) {
- if (mSwipingInProgress) {
- flingWithCurrentVelocity(forceSnapBack, lastX, lastY);
- } else {
- mTargetedView = null;
- }
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- }
-
- private boolean rightSwipePossible() {
- return mRightIcon.getVisibility() == View.VISIBLE;
- }
-
- private boolean leftSwipePossible() {
- return mLeftIcon.getVisibility() == View.VISIBLE;
- }
-
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return false;
- }
-
- public void startHintAnimation(boolean right,
- Runnable onFinishedListener) {
- cancelAnimation();
- startHintAnimationPhase1(right, onFinishedListener);
- }
-
- private void startHintAnimationPhase1(final boolean right, final Runnable onFinishedListener) {
- final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
- ValueAnimator animator = getAnimatorToRadius(right, mHintGrowAmount);
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCancelled) {
- mSwipeAnimator = null;
- mTargetedView = null;
- onFinishedListener.run();
- } else {
- startUnlockHintAnimationPhase2(right, onFinishedListener);
- }
- }
- });
- animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- animator.setDuration(HINT_PHASE1_DURATION);
- animator.start();
- mSwipeAnimator = animator;
- mTargetedView = targetView;
- }
-
- /**
- * Phase 2: Move back.
- */
- private void startUnlockHintAnimationPhase2(boolean right, final Runnable onFinishedListener) {
- ValueAnimator animator = getAnimatorToRadius(right, 0);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mSwipeAnimator = null;
- mTargetedView = null;
- onFinishedListener.run();
- }
- });
- animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- animator.setDuration(HINT_PHASE2_DURATION);
- animator.setStartDelay(HINT_CIRCLE_OPEN_DURATION);
- animator.start();
- mSwipeAnimator = animator;
- }
-
- private ValueAnimator getAnimatorToRadius(final boolean right, int radius) {
- final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
- ValueAnimator animator = ValueAnimator.ofFloat(targetView.getCircleRadius(), radius);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float newRadius = (float) animation.getAnimatedValue();
- targetView.setCircleRadiusWithoutAnimation(newRadius);
- float translation = getTranslationFromRadius(newRadius);
- mTranslation = right ? -translation : translation;
- updateIconsFromTranslation(targetView);
- }
- });
- return animator;
- }
-
- private void cancelAnimation() {
- if (mSwipeAnimator != null) {
- mSwipeAnimator.cancel();
- }
- }
-
- private void flingWithCurrentVelocity(boolean forceSnapBack, float lastX, float lastY) {
- float vel = getCurrentVelocity(lastX, lastY);
-
- // We snap back if the current translation is not far enough
- boolean snapBack = false;
- if (mCallback.needsAntiFalsing()) {
- snapBack = snapBack || mFalsingManager.isFalseTouch(
- mTargetedView == mRightIcon
- ? Classifier.RIGHT_AFFORDANCE : Classifier.LEFT_AFFORDANCE);
- }
- snapBack = snapBack || isBelowFalsingThreshold();
-
- // or if the velocity is in the opposite direction.
- boolean velIsInWrongDirection = vel * mTranslation < 0;
- snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection;
- vel = snapBack ^ velIsInWrongDirection ? 0 : vel;
- fling(vel, snapBack || forceSnapBack, mTranslation < 0);
- }
-
- private boolean isBelowFalsingThreshold() {
- return Math.abs(mTranslation) < Math.abs(mTranslationOnDown) + getMinTranslationAmount();
- }
-
- private int getMinTranslationAmount() {
- float factor = mCallback.getAffordanceFalsingFactor();
- return (int) (mMinTranslationAmount * factor);
- }
-
- private void fling(float vel, final boolean snapBack, boolean right) {
- float target = right ? -mCallback.getMaxTranslationDistance()
- : mCallback.getMaxTranslationDistance();
- target = snapBack ? 0 : target;
-
- ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target);
- mFlingAnimationUtils.apply(animator, mTranslation, target, vel);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mTranslation = (float) animation.getAnimatedValue();
- }
- });
- animator.addListener(mFlingEndListener);
- if (!snapBack) {
- startFinishingCircleAnimation(vel * 0.375f, mAnimationEndRunnable, right);
- mCallback.onAnimationToSideStarted(right, mTranslation, vel);
- } else {
- reset(true);
- }
- animator.start();
- mSwipeAnimator = animator;
- if (snapBack) {
- mCallback.onSwipingAborted();
- }
- }
-
- private void startFinishingCircleAnimation(float velocity, Runnable animationEndRunnable,
- boolean right) {
- KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
- targetView.finishAnimation(velocity, animationEndRunnable);
- }
-
- private void setTranslation(float translation, boolean isReset, boolean animateReset) {
- translation = rightSwipePossible() ? translation : Math.max(0, translation);
- translation = leftSwipePossible() ? translation : Math.min(0, translation);
- float absTranslation = Math.abs(translation);
- if (translation != mTranslation || isReset) {
- KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon;
- KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon;
- float alpha = absTranslation / getMinTranslationAmount();
-
- // We interpolate the alpha of the other icons to 0
- float fadeOutAlpha = 1.0f - alpha;
- fadeOutAlpha = Math.max(fadeOutAlpha, 0.0f);
-
- boolean animateIcons = isReset && animateReset;
- boolean forceNoCircleAnimation = isReset && !animateReset;
- float radius = getRadiusFromTranslation(absTranslation);
- boolean slowAnimation = isReset && isBelowFalsingThreshold();
- if (!isReset) {
- updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(),
- false, false, false, false);
- } else {
- updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(),
- animateIcons, slowAnimation, true /* isReset */, forceNoCircleAnimation);
- }
- updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(),
- animateIcons, slowAnimation, isReset, forceNoCircleAnimation);
-
- mTranslation = translation;
- }
- }
-
- private void updateIconsFromTranslation(KeyguardAffordanceView targetView) {
- float absTranslation = Math.abs(mTranslation);
- float alpha = absTranslation / getMinTranslationAmount();
-
- // We interpolate the alpha of the other icons to 0
- float fadeOutAlpha = 1.0f - alpha;
- fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
-
- // We interpolate the alpha of the targetView to 1
- KeyguardAffordanceView otherView = targetView == mRightIcon ? mLeftIcon : mRightIcon;
- updateIconAlpha(targetView, alpha + fadeOutAlpha * targetView.getRestingAlpha(), false);
- updateIconAlpha(otherView, fadeOutAlpha * otherView.getRestingAlpha(), false);
- }
-
- private float getTranslationFromRadius(float circleSize) {
- float translation = (circleSize - mMinBackgroundRadius)
- / BACKGROUND_RADIUS_SCALE_FACTOR;
- return translation > 0.0f ? translation + mTouchSlop : 0.0f;
- }
-
- private float getRadiusFromTranslation(float translation) {
- if (translation <= mTouchSlop) {
- return 0.0f;
- }
- return (translation - mTouchSlop) * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius;
- }
-
- public void animateHideLeftRightIcon() {
- cancelAnimation();
- updateIcon(mRightIcon, 0f, 0f, true, false, false, false);
- updateIcon(mLeftIcon, 0f, 0f, true, false, false, false);
- }
-
- private void updateIcon(KeyguardAffordanceView view, float circleRadius, float alpha,
- boolean animate, boolean slowRadiusAnimation, boolean force,
- boolean forceNoCircleAnimation) {
- if (view.getVisibility() != View.VISIBLE && !force) {
- return;
- }
- if (forceNoCircleAnimation) {
- view.setCircleRadiusWithoutAnimation(circleRadius);
- } else {
- view.setCircleRadius(circleRadius, slowRadiusAnimation);
- }
- updateIconAlpha(view, alpha, animate);
- }
-
- private void updateIconAlpha(KeyguardAffordanceView view, float alpha, boolean animate) {
- float scale = getScale(alpha, view);
- alpha = Math.min(1.0f, alpha);
- view.setImageAlpha(alpha, animate);
- view.setImageScale(scale, animate);
- }
-
- private float getScale(float alpha, KeyguardAffordanceView icon) {
- float scale = alpha / icon.getRestingAlpha() * 0.2f +
- KeyguardAffordanceView.MIN_ICON_SCALE_AMOUNT;
- return Math.min(scale, KeyguardAffordanceView.MAX_ICON_SCALE_AMOUNT);
- }
-
- private void trackMovement(MotionEvent event) {
- if (mVelocityTracker != null) {
- mVelocityTracker.addMovement(event);
- }
- }
-
- private void initVelocityTracker() {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- }
- mVelocityTracker = VelocityTracker.obtain();
- }
-
- private float getCurrentVelocity(float lastX, float lastY) {
- if (mVelocityTracker == null) {
- return 0;
- }
- mVelocityTracker.computeCurrentVelocity(1000);
- float aX = mVelocityTracker.getXVelocity();
- float aY = mVelocityTracker.getYVelocity();
- float bX = lastX - mInitialTouchX;
- float bY = lastY - mInitialTouchY;
- float bLen = (float) Math.hypot(bX, bY);
- // Project the velocity onto the distance vector: a * b / |b|
- float projectedVelocity = (aX * bX + aY * bY) / bLen;
- if (mTargetedView == mRightIcon) {
- projectedVelocity = -projectedVelocity;
- }
- return projectedVelocity;
- }
-
- public void onConfigurationChanged() {
- initDimens();
- initIcons();
- }
-
- public void onRtlPropertiesChanged() {
- initIcons();
- }
-
- public void reset(boolean animate) {
- cancelAnimation();
- setTranslation(0.0f, true /* isReset */, animate);
- mMotionCancelled = true;
- if (mSwipingInProgress) {
- mCallback.onSwipingAborted();
- mSwipingInProgress = false;
- }
- }
-
- public boolean isSwipingInProgress() {
- return mSwipingInProgress;
- }
-
- public void launchAffordance(boolean animate, boolean left) {
- if (mSwipingInProgress) {
- // We don't want to mess with the state if the user is actually swiping already.
- return;
- }
- KeyguardAffordanceView targetView = left ? mLeftIcon : mRightIcon;
- KeyguardAffordanceView otherView = left ? mRightIcon : mLeftIcon;
- startSwiping(targetView);
-
- // Do not animate the circle expanding if the affordance isn't visible,
- // otherwise the circle will be meaningless.
- if (targetView.getVisibility() != View.VISIBLE) {
- animate = false;
- }
-
- if (animate) {
- fling(0, false, !left);
- updateIcon(otherView, 0.0f, 0, true, false, true, false);
- } else {
- mCallback.onAnimationToSideStarted(!left, mTranslation, 0);
- mTranslation = left ? mCallback.getMaxTranslationDistance()
- : mCallback.getMaxTranslationDistance();
- updateIcon(otherView, 0.0f, 0.0f, false, false, true, false);
- targetView.instantFinishAnimation();
- mFlingEndListener.onAnimationEnd(null);
- mAnimationEndRunnable.run();
- }
- }
-
- public interface Callback {
-
- /**
- * Notifies the callback when an animation to a side page was started.
- *
- * @param rightPage Is the page animated to the right page?
- */
- void onAnimationToSideStarted(boolean rightPage, float translation, float vel);
-
- /**
- * Notifies the callback the animation to a side page has ended.
- */
- void onAnimationToSideEnded();
-
- float getMaxTranslationDistance();
-
- void onSwipingStarted(boolean rightIcon);
-
- void onSwipingAborted();
-
- void onIconClicked(boolean rightIcon);
-
- KeyguardAffordanceView getLeftIcon();
-
- KeyguardAffordanceView getRightIcon();
-
- View getLeftPreview();
-
- View getRightPreview();
-
- /**
- * @return The factor the minimum swipe amount should be multiplied with.
- */
- float getAffordanceFalsingFactor();
-
- boolean needsAntiFalsing();
- }
-}
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..61113a05af16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -16,56 +16,30 @@
package com.android.systemui.statusbar.phone;
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
-import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
-import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
-import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
-import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON;
-import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNLOCK;
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;
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;
-import android.telecom.TelecomManager;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
@@ -73,81 +47,35 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
-import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.camera.CameraIntents;
-import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsActivity;
import com.android.systemui.controls.ui.ControlsUiController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.IntentButtonProvider;
-import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
-import com.android.systemui.plugins.IntentButtonProvider.IntentButton.IconState;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
-import com.android.systemui.statusbar.KeyguardAffordanceView;
-import com.android.systemui.statusbar.policy.AccessibilityController;
-import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.ExtensionController.Extension;
-import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.PreviewInflater;
-import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
-import com.android.systemui.tuner.TunerService;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
-import java.util.List;
-
/**
* Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
* text.
*/
-public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
- KeyguardStateController.Callback,
- AccessibilityController.AccessibilityStateChangedCallback {
-
- final static String TAG = "CentralSurfaces/KeyguardBottomAreaView";
-
- public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
- public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
- public static final String CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = "power_double_tap";
- public static final String CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = "lift_to_launch_ml";
+public class KeyguardBottomAreaView extends FrameLayout {
- public static final String EXTRA_CAMERA_LAUNCH_SOURCE
- = "com.android.systemui.camera_launch_source";
-
- private static final String LEFT_BUTTON_PLUGIN
- = "com.android.systemui.action.PLUGIN_LOCKSCREEN_LEFT_BUTTON";
- private static final String RIGHT_BUTTON_PLUGIN
- = "com.android.systemui.action.PLUGIN_LOCKSCREEN_RIGHT_BUTTON";
-
- private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
- private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
+ private static final String TAG = "CentralSurfaces/KeyguardBottomAreaView";
private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
- // TODO(b/179494051): May no longer be needed
- private final boolean mShowLeftAffordance;
- private final boolean mShowCameraAffordance;
-
- private KeyguardAffordanceView mRightAffordanceView;
- private KeyguardAffordanceView mLeftAffordanceView;
-
private ImageView mWalletButton;
private ImageView mQRCodeScannerButton;
private ImageView mControlsButton;
private boolean mHasCard = false;
- private WalletCardRetriever mCardRetriever = new WalletCardRetriever();
+ private final WalletCardRetriever mCardRetriever = new WalletCardRetriever();
private QuickAccessWalletController mQuickAccessWalletController;
private QRCodeScannerController mQRCodeScannerController;
private ControlsComponent mControlsComponent;
@@ -157,68 +85,42 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private ViewGroup mIndicationArea;
private TextView mIndicationText;
private TextView mIndicationTextBottom;
- private ViewGroup mPreviewContainer;
private ViewGroup mOverlayContainer;
- private View mLeftPreview;
- private View mCameraPreview;
-
private ActivityStarter mActivityStarter;
private KeyguardStateController mKeyguardStateController;
- private FlashlightController mFlashlightController;
- private PreviewInflater mPreviewInflater;
- private AccessibilityController mAccessibilityController;
private CentralSurfaces mCentralSurfaces;
- 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;
- private IntentButton mRightButton = new DefaultRightButton();
- private Extension<IntentButton> mRightExtension;
- private String mRightButtonStr;
- private IntentButton mLeftButton = new DefaultLeftButton();
- private Extension<IntentButton> mLeftExtension;
- private String mLeftButtonStr;
private boolean mDozing;
private int mIndicationBottomMargin;
private int mIndicationPadding;
private float mDarkAmount;
private int mBurnInXOffset;
private int mBurnInYOffset;
- private ActivityIntentHelper mActivityIntentHelper;
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
- private ControlsListingController.ControlsListingCallback mListingCallback =
- new ControlsListingController.ControlsListingCallback() {
- public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) {
- post(() -> {
- boolean available = !serviceInfos.isEmpty();
-
- if (available != mControlServicesAvailable) {
- mControlServicesAvailable = available;
- updateControlsVisibility();
- updateAffordanceColors();
- }
- });
+
+ private final ControlsListingController.ControlsListingCallback mListingCallback =
+ serviceInfos -> post(() -> {
+ boolean available = !serviceInfos.isEmpty();
+
+ if (available != mControlServicesAvailable) {
+ mControlServicesAvailable = available;
+ updateControlsVisibility();
+ updateAffordanceColors();
}
- };
+ });
+
+ private final KeyguardStateController.Callback mKeyguardStateCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ if (mKeyguardStateController.isShowing()) {
+ if (mQuickAccessWalletController != null) {
+ mQuickAccessWalletController.queryWalletCards(mCardRetriever);
+ }
+ }
+ }
+ };
public KeyguardBottomAreaView(Context context) {
this(context, null);
@@ -235,43 +137,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mShowLeftAffordance = getResources().getBoolean(R.bool.config_keyguardShowLeftAffordance);
- mShowCameraAffordance = getResources()
- .getBoolean(R.bool.config_keyguardShowCameraAffordance);
}
- private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- String label = null;
- if (host == mRightAffordanceView) {
- label = getResources().getString(R.string.camera_label);
- } else if (host == mLeftAffordanceView) {
- if (mLeftIsVoiceAssist) {
- label = getResources().getString(R.string.voice_assist_label);
- } else {
- label = getResources().getString(R.string.phone_label);
- }
- }
- info.addAction(new AccessibilityAction(ACTION_CLICK, label));
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (action == ACTION_CLICK) {
- if (host == mRightAffordanceView) {
- launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
- return true;
- } else if (host == mLeftAffordanceView) {
- launchLeftAffordance();
- return true;
- }
- }
- return super.performAccessibilityAction(host, action, args);
- }
- };
-
public void initFrom(KeyguardBottomAreaView oldBottomArea) {
setCentralSurfaces(oldBottomArea.mCentralSurfaces);
@@ -302,11 +169,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext),
- new ActivityIntentHelper(mContext));
mOverlayContainer = findViewById(R.id.overlay_container);
- mRightAffordanceView = findViewById(R.id.camera_button);
- mLeftAffordanceView = findViewById(R.id.left_button);
mWalletButton = findViewById(R.id.wallet_button);
mQRCodeScannerButton = findViewById(R.id.qr_code_scanner_button);
mControlsButton = findViewById(R.id.controls_button);
@@ -318,18 +181,11 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
R.dimen.keyguard_indication_margin_bottom);
mBurnInYOffset = getResources().getDimensionPixelSize(
R.dimen.default_burn_in_prevention_offset);
- updateCameraVisibility();
mKeyguardStateController = Dependency.get(KeyguardStateController.class);
- mKeyguardStateController.addCallback(this);
+ mKeyguardStateController.addCallback(mKeyguardStateCallback);
setClipChildren(false);
setClipToPadding(false);
- mRightAffordanceView.setOnClickListener(this);
- mLeftAffordanceView.setOnClickListener(this);
- initAccessibility();
mActivityStarter = Dependency.get(ActivityStarter.class);
- mFlashlightController = Dependency.get(FlashlightController.class);
- mAccessibilityController = Dependency.get(AccessibilityController.class);
- mActivityIntentHelper = new ActivityIntentHelper(getContext());
mIndicationPadding = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_area_padding);
@@ -338,51 +194,18 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
updateControlsVisibility();
}
- /**
- * Set the container where the previews are rendered.
- */
- public void setPreviewContainer(ViewGroup previewContainer) {
- mPreviewContainer = previewContainer;
- inflateCameraPreview();
- updateLeftAffordance();
- }
-
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mAccessibilityController.addStateChangedCallback(this);
- mRightExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
- .withPlugin(IntentButtonProvider.class, RIGHT_BUTTON_PLUGIN,
- p -> p.getIntentButton())
- .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_RIGHT_BUTTON))
- .withDefault(() -> new DefaultRightButton())
- .withCallback(button -> setRightButton(button))
- .build();
- mLeftExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
- .withPlugin(IntentButtonProvider.class, LEFT_BUTTON_PLUGIN,
- p -> p.getIntentButton())
- .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_LEFT_BUTTON))
- .withDefault(() -> new DefaultLeftButton())
- .withCallback(button -> setLeftButton(button))
- .build();
final IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
- getContext().registerReceiverAsUser(mDevicePolicyReceiver,
- UserHandle.ALL, filter, null, null);
- mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
- mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
- mKeyguardStateController.addCallback(this);
+ mKeyguardStateController.addCallback(mKeyguardStateCallback);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mKeyguardStateController.removeCallback(this);
- mAccessibilityController.removeStateChangedCallback(this);
- mRightExtension.destroy();
- mLeftExtension.destroy();
- getContext().unregisterReceiver(mDevicePolicyReceiver);
- mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
+ mKeyguardStateController.removeCallback(mKeyguardStateCallback);
if (mQuickAccessWalletController != null) {
mQuickAccessWalletController.unregisterWalletChangeObservers(
@@ -401,11 +224,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
}
- private void initAccessibility() {
- mLeftAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
- mRightAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
- }
-
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -427,19 +245,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
getResources().getDimensionPixelSize(
com.android.internal.R.dimen.text_size_small_material));
- ViewGroup.LayoutParams lp = mRightAffordanceView.getLayoutParams();
- lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
- lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
- mRightAffordanceView.setLayoutParams(lp);
- updateRightAffordanceIcon();
-
- lp = mLeftAffordanceView.getLayoutParams();
- lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
- lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
- mLeftAffordanceView.setLayoutParams(lp);
- updateLeftAffordanceIcon();
-
- lp = mWalletButton.getLayoutParams();
+ ViewGroup.LayoutParams lp = mWalletButton.getLayoutParams();
lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width);
lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height);
mWalletButton.setLayoutParams(lp);
@@ -462,74 +268,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
updateAffordanceColors();
}
- private void updateRightAffordanceIcon() {
- IconState state = mRightButton.getIcon();
- mRightAffordanceView.setVisibility(!mDozing && state.isVisible ? View.VISIBLE : View.GONE);
- if (state.drawable != mRightAffordanceView.getDrawable()
- || state.tint != mRightAffordanceView.shouldTint()) {
- mRightAffordanceView.setImageDrawable(state.drawable, state.tint);
- }
- mRightAffordanceView.setContentDescription(state.contentDescription);
- }
-
public void setCentralSurfaces(CentralSurfaces centralSurfaces) {
mCentralSurfaces = centralSurfaces;
- updateCameraVisibility(); // in case onFinishInflate() was called too early
- }
-
- public void setAffordanceHelper(KeyguardAffordanceHelper affordanceHelper) {
- mAffordanceHelper = affordanceHelper;
- }
-
- public void setUserSetupComplete(boolean userSetupComplete) {
- mUserSetupComplete = userSetupComplete;
- updateCameraVisibility();
- updateLeftAffordanceIcon();
- }
-
- private Intent getCameraIntent() {
- return mRightButton.getIntent();
- }
-
- /**
- * Resolves the intent to launch the camera application.
- */
- public ResolveInfo resolveCameraIntent() {
- return mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
- PackageManager.MATCH_DEFAULT_ONLY,
- KeyguardUpdateMonitor.getCurrentUser());
- }
-
- private void updateCameraVisibility() {
- if (mRightAffordanceView == null) {
- // Things are not set up yet; reply hazy, ask again later
- return;
- }
- mRightAffordanceView.setVisibility(!mDozing && mShowCameraAffordance
- && mRightButton.getIcon().isVisible ? View.VISIBLE : View.GONE);
- }
-
- /**
- * Set an alternate icon for the left assist affordance (replace the mic icon)
- */
- public void setLeftAssistIcon(Drawable drawable) {
- mLeftAssistIcon = drawable;
- updateLeftAffordanceIcon();
- }
-
- private void updateLeftAffordanceIcon() {
- if (!mShowLeftAffordance || mDozing) {
- mLeftAffordanceView.setVisibility(GONE);
- return;
- }
-
- IconState state = mLeftButton.getIcon();
- mLeftAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE);
- if (state.drawable != mLeftAffordanceView.getDrawable()
- || state.tint != mLeftAffordanceView.shouldTint()) {
- mLeftAffordanceView.setImageDrawable(state.drawable, state.tint);
- }
- mLeftAffordanceView.setContentDescription(state.contentDescription);
}
private void updateWalletVisibility() {
@@ -575,128 +315,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
}
- public boolean isLeftVoiceAssist() {
- return mLeftIsVoiceAssist;
- }
-
- private boolean isPhoneVisible() {
- PackageManager pm = mContext.getPackageManager();
- return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- && pm.resolveActivity(PHONE_INTENT, 0) != null;
- }
-
- @Override
- public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
- mRightAffordanceView.setClickable(touchExplorationEnabled);
- mLeftAffordanceView.setClickable(touchExplorationEnabled);
- mRightAffordanceView.setFocusable(accessibilityEnabled);
- mLeftAffordanceView.setFocusable(accessibilityEnabled);
- }
-
- @Override
- public void onClick(View v) {
- if (v == mRightAffordanceView) {
- launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
- } else if (v == mLeftAffordanceView) {
- launchLeftAffordance();
- }
- }
-
- 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);
- boolean wouldLaunchResolverActivity = mActivityIntentHelper.wouldLaunchResolverActivity(
- intent, KeyguardUpdateMonitor.getCurrentUser());
- if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
- 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
- // the default animation is used. This doesn't look good for
- // the camera (as it rotates the camera contents out of sync
- // with physical reality). So, we ask the WindowManager to
- // force the crossfade animation if an orientation change
- // happens to occur during the launch.
- ActivityOptions o = ActivityOptions.makeBasic();
- o.setDisallowEnterPictureInPictureWhileLaunching(true);
- o.setRotationAnimationHint(
- WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
- try {
- result = ActivityTaskManager.getService().startActivityAsUser(
- null, getContext().getBasePackageName(),
- getContext().getAttributionTag(), intent,
- intent.resolveTypeIfNeeded(getContext().getContentResolver()),
- null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, o.toBundle(),
- UserHandle.CURRENT.getIdentifier());
- } 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));
- }
- });
- }
- }
-
public void setDarkAmount(float darkAmount) {
if (darkAmount == mDarkAmount) {
return;
@@ -705,83 +323,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();
- } else {
- launchPhone();
- }
- }
-
- @VisibleForTesting
- void launchVoiceAssist() {
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- Dependency.get(AssistManager.class).launchVoiceAssistFromKeyguard();
- }
- };
- if (!mKeyguardStateController.canDismissLockScreen()) {
- Dependency.get(Dependency.BACKGROUND_EXECUTOR).execute(runnable);
- } else {
- boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
- && Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
- mCentralSurfaces.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
- dismissShade, false /* afterKeyguardGone */, true /* deferred */);
- }
- }
-
- private boolean canLaunchVoiceAssist() {
- return Dependency.get(AssistManager.class).canVoiceAssistBeLaunchedFromKeyguard();
- }
-
- private void launchPhone() {
- final TelecomManager tm = TelecomManager.from(mContext);
- if (tm.isInCall()) {
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- tm.showInCallScreen(false /* showDialpad */);
- }
- });
- } else {
- boolean dismissShade = !TextUtils.isEmpty(mLeftButtonStr)
- && Dependency.get(TunerService.class).getValue(LOCKSCREEN_LEFT_UNLOCK, 1) != 0;
- mActivityStarter.startActivity(mLeftButton.getIntent(), dismissShade);
- }
- }
-
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- if (changedView == this && visibility == VISIBLE) {
- updateCameraVisibility();
- }
- }
-
- public KeyguardAffordanceView getLeftView() {
- return mLeftAffordanceView;
- }
-
- public KeyguardAffordanceView getRightView() {
- return mRightAffordanceView;
- }
-
- public View getLeftPreview() {
- return mLeftPreview;
- }
-
- public View getRightPreview() {
- return mCameraPreview;
- }
-
public View getIndicationArea() {
return mIndicationArea;
}
@@ -791,66 +332,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
return false;
}
- @Override
- public void onUnlockedChanged() {
- updateCameraVisibility();
- }
-
- @Override
- public void onKeyguardShowingChanged() {
- if (mKeyguardStateController.isShowing()) {
- if (mQuickAccessWalletController != null) {
- mQuickAccessWalletController.queryWalletCards(mCardRetriever);
- }
- }
- }
-
- private void inflateCameraPreview() {
- if (mPreviewContainer == null) {
- return;
- }
- View previewBefore = mCameraPreview;
- boolean visibleBefore = false;
- if (previewBefore != null) {
- mPreviewContainer.removeView(previewBefore);
- visibleBefore = previewBefore.getVisibility() == View.VISIBLE;
- }
- mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
- if (mCameraPreview != null) {
- mPreviewContainer.addView(mCameraPreview);
- mCameraPreview.setVisibility(visibleBefore ? View.VISIBLE : View.INVISIBLE);
- }
- if (mAffordanceHelper != null) {
- mAffordanceHelper.updatePreviews();
- }
- }
-
- private void updateLeftPreview() {
- if (mPreviewContainer == null) {
- return;
- }
- View previewBefore = mLeftPreview;
- if (previewBefore != null) {
- mPreviewContainer.removeView(previewBefore);
- }
-
- if (mLeftIsVoiceAssist) {
- if (Dependency.get(AssistManager.class).getVoiceInteractorComponentName() != null) {
- mLeftPreview = mPreviewInflater.inflatePreviewFromService(
- Dependency.get(AssistManager.class).getVoiceInteractorComponentName());
- }
- } else {
- mLeftPreview = mPreviewInflater.inflatePreview(mLeftButton.getIntent());
- }
- if (mLeftPreview != null) {
- mPreviewContainer.addView(mLeftPreview);
- mLeftPreview.setVisibility(View.INVISIBLE);
- }
- if (mAffordanceHelper != null) {
- mAffordanceHelper.updatePreviews();
- }
- }
-
public void startFinishDozeAnimation() {
long delay = 0;
if (mWalletButton.getVisibility() == View.VISIBLE) {
@@ -862,13 +343,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
if (mControlsButton.getVisibility() == View.VISIBLE) {
startFinishDozeAnimationElement(mControlsButton, delay);
}
- if (mLeftAffordanceView.getVisibility() == View.VISIBLE) {
- startFinishDozeAnimationElement(mLeftAffordanceView, delay);
- delay += DOZE_ANIMATION_STAGGER_DELAY;
- }
- if (mRightAffordanceView.getVisibility() == View.VISIBLE) {
- startFinishDozeAnimationElement(mRightAffordanceView, delay);
- }
}
private void startFinishDozeAnimationElement(View element, long delay) {
@@ -882,58 +356,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
.setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
}
- private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- post(new Runnable() {
- @Override
- public void run() {
- updateCameraVisibility();
- }
- });
- }
- };
-
- private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onUserSwitchComplete(int userId) {
- updateCameraVisibility();
- }
-
- @Override
- public void onUserUnlocked() {
- inflateCameraPreview();
- updateCameraVisibility();
- updateLeftAffordance();
- }
- };
-
- public void updateLeftAffordance() {
- updateLeftAffordanceIcon();
- updateLeftPreview();
- }
-
- private void setRightButton(IntentButton button) {
- mRightButton = button;
- updateRightAffordanceIcon();
- updateCameraVisibility();
- inflateCameraPreview();
- }
-
- private void setLeftButton(IntentButton button) {
- mLeftButton = button;
- if (!(mLeftButton instanceof DefaultLeftButton)) {
- mLeftIsVoiceAssist = false;
- }
- updateLeftAffordance();
- }
-
public void setDozing(boolean dozing, boolean animate) {
mDozing = dozing;
- updateCameraVisibility();
- updateLeftAffordanceIcon();
updateWalletVisibility();
updateControlsVisibility();
updateQRCodeButtonVisibility();
@@ -972,77 +397,12 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
* Sets the alpha of the indication areas and affordances, excluding the lock icon.
*/
public void setAffordanceAlpha(float alpha) {
- mLeftAffordanceView.setAlpha(alpha);
- mRightAffordanceView.setAlpha(alpha);
mIndicationArea.setAlpha(alpha);
mWalletButton.setAlpha(alpha);
mQRCodeScannerButton.setAlpha(alpha);
mControlsButton.setAlpha(alpha);
}
- private class DefaultLeftButton implements IntentButton {
-
- private IconState mIconState = new IconState();
-
- @Override
- public IconState getIcon() {
- mLeftIsVoiceAssist = canLaunchVoiceAssist();
- if (mLeftIsVoiceAssist) {
- mIconState.isVisible = mUserSetupComplete && mShowLeftAffordance;
- if (mLeftAssistIcon == null) {
- mIconState.drawable = mContext.getDrawable(R.drawable.ic_mic_26dp);
- } else {
- mIconState.drawable = mLeftAssistIcon;
- }
- mIconState.contentDescription = mContext.getString(
- R.string.accessibility_voice_assist_button);
- } else {
- mIconState.isVisible = mUserSetupComplete && mShowLeftAffordance
- && isPhoneVisible();
- mIconState.drawable = mContext.getDrawable(
- com.android.internal.R.drawable.ic_phone);
- mIconState.contentDescription = mContext.getString(
- R.string.accessibility_phone_button);
- }
- return mIconState;
- }
-
- @Override
- public Intent getIntent() {
- return PHONE_INTENT;
- }
- }
-
- private class DefaultRightButton implements IntentButton {
-
- private IconState mIconState = new IconState();
-
- @Override
- public IconState getIcon() {
- boolean isCameraDisabled = (mCentralSurfaces != null)
- && !mCentralSurfaces.isCameraAllowedByAdmin();
- mIconState.isVisible = !isCameraDisabled
- && mShowCameraAffordance
- && mUserSetupComplete
- && resolveCameraIntent() != null;
- mIconState.drawable = mContext.getDrawable(R.drawable.ic_camera_alt_24dp);
- mIconState.contentDescription =
- mContext.getString(R.string.accessibility_camera_button);
- return mIconState;
- }
-
- @Override
- public Intent getIntent() {
- boolean canDismissLs = mKeyguardStateController.canDismissLockScreen();
- boolean secure = mKeyguardStateController.isMethodSecure();
- if (secure && !canDismissLs) {
- return CameraIntents.getSecureCameraIntent(getContext());
- } else {
- return CameraIntents.getInsecureCameraIntent(getContext());
- }
- }
- }
-
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
int bottom = insets.getDisplayCutout() != null
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..c757cba31967 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -50,8 +50,6 @@ import android.app.ActivityManager;
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.ContentResolver;
-import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Canvas;
@@ -145,7 +143,6 @@ import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.GestureRecorder;
-import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -252,9 +249,6 @@ public class NotificationPanelViewController extends PanelViewController {
private final OnOverscrollTopChangedListener
mOnOverscrollTopChangedListener =
new OnOverscrollTopChangedListener();
- private final KeyguardAffordanceHelperCallback
- mKeyguardAffordanceHelperCallback =
- new KeyguardAffordanceHelperCallback();
private final OnEmptySpaceClickListener
mOnEmptySpaceClickListener =
new OnEmptySpaceClickListener();
@@ -338,8 +332,6 @@ public class NotificationPanelViewController extends PanelViewController {
// Current max allowed keyguard notifications determined by measuring the panel
private int mMaxAllowedKeyguardNotifications;
- private ViewGroup mPreviewContainer;
- private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarView mKeyguardStatusBar;
@@ -437,8 +429,6 @@ public class NotificationPanelViewController extends PanelViewController {
*/
private boolean mQsAnimatorExpand;
private boolean mIsLaunchTransitionFinished;
- private boolean mIsLaunchTransitionRunning;
- private Runnable mLaunchAnimationEndRunnable;
private boolean mOnlyAffordanceInThisMotion;
private ValueAnimator mQsSizeChangeAnimator;
@@ -455,10 +445,8 @@ public class NotificationPanelViewController extends PanelViewController {
private boolean mClosingWithAlphaFadeOut;
private boolean mHeadsUpAnimatingAway;
private boolean mLaunchingAffordance;
- private boolean mAffordanceHasPreview;
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
- private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
private Runnable mHeadsUpExistenceChangedRunnable = () -> {
setHeadsUpAnimatingAway(false);
@@ -491,7 +479,6 @@ public class NotificationPanelViewController extends PanelViewController {
private float mLinearDarkAmount;
private boolean mPulsing;
- private boolean mUserSetupComplete;
private boolean mHideIconsDuringLaunchAnimation = true;
private int mStackScrollerMeasuringPass;
/**
@@ -1008,8 +995,6 @@ public class NotificationPanelViewController extends PanelViewController {
mOnEmptySpaceClickListener);
addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
- mPreviewContainer = mView.findViewById(R.id.preview_container);
- mKeyguardBottomArea.setPreviewContainer(mPreviewContainer);
initBottomArea();
@@ -1033,7 +1018,6 @@ public class NotificationPanelViewController extends PanelViewController {
mView.setRtlChangeListener(layoutDirection -> {
if (layoutDirection != mOldLayoutDirection) {
- mAffordanceHelper.onRtlPropertiesChanged();
mOldLayoutDirection = layoutDirection;
}
});
@@ -1267,7 +1251,6 @@ public class NotificationPanelViewController extends PanelViewController {
mKeyguardBottomArea = (KeyguardBottomAreaView) mLayoutInflater.inflate(
R.layout.keyguard_bottom_area, mView, false);
mKeyguardBottomArea.initFrom(oldBottomArea);
- mKeyguardBottomArea.setPreviewContainer(mPreviewContainer);
mView.addView(mKeyguardBottomArea, index);
initBottomArea();
mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
@@ -1304,11 +1287,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
private void initBottomArea() {
- mAffordanceHelper = new KeyguardAffordanceHelper(
- mKeyguardAffordanceHelperCallback, mView.getContext(), mFalsingManager);
- mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
mKeyguardBottomArea.setCentralSurfaces(mCentralSurfaces);
- mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
mKeyguardBottomArea.setFalsingManager(mFalsingManager);
mKeyguardBottomArea.initWallet(mQuickAccessWalletController);
mKeyguardBottomArea.initControls(mControlsComponent);
@@ -1670,14 +1649,9 @@ public class NotificationPanelViewController extends PanelViewController {
setQsExpansionEnabled();
}
- @Override
public void resetViews(boolean animate) {
mIsLaunchTransitionFinished = false;
mBlockTouches = false;
- if (!mLaunchingAffordance) {
- mAffordanceHelper.reset(false);
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
- }
mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
if (animate && !isFullyCollapsed()) {
@@ -2220,11 +2194,6 @@ public class NotificationPanelViewController extends PanelViewController {
return isFullyCollapsed() || mBarState != StatusBarState.SHADE;
}
- @Override
- protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
- return !mAffordanceHelper.isOnAffordanceIcon(x, y);
- }
-
private void onQsTouch(MotionEvent event) {
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
@@ -3389,9 +3358,6 @@ public class NotificationPanelViewController extends PanelViewController {
mQsExpandImmediate = true;
setShowShelfOnly(true);
}
- if (mBarState == KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
- mAffordanceHelper.animateHideLeftRightIcon();
- }
mNotificationStackScrollLayoutController.onPanelTrackingStarted();
cancelPendingPanelCollapse();
}
@@ -3405,12 +3371,6 @@ public class NotificationPanelViewController extends PanelViewController {
true /* animate */);
}
mNotificationStackScrollLayoutController.onPanelTrackingStopped();
- if (expand && (mBarState == KEYGUARD
- || mBarState == StatusBarState.SHADE_LOCKED)) {
- if (!mHintAnimationRunning) {
- mAffordanceHelper.reset(true);
- }
- }
// If we unlocked from a swipe, the user's finger might still be down after the
// unlock animation ends. We need to wait until ACTION_UP to enable blurs again.
@@ -3483,10 +3443,6 @@ public class NotificationPanelViewController extends PanelViewController {
return mIsLaunchTransitionFinished;
}
- public boolean isLaunchTransitionRunning() {
- return mIsLaunchTransitionRunning;
- }
-
@Override
public void setIsLaunchAnimationRunning(boolean running) {
boolean wasRunning = mIsLaunchAnimationRunning;
@@ -3505,10 +3461,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- public void setLaunchTransitionEndRunnable(Runnable r) {
- mLaunchAnimationEndRunnable = r;
- }
-
private void updateDozingVisibilities(boolean animate) {
mKeyguardBottomArea.setDozing(mDozing, animate);
if (!mDozing && animate) {
@@ -3687,30 +3639,8 @@ public class NotificationPanelViewController extends PanelViewController {
&& mBarState == StatusBarState.SHADE;
}
- public void launchCamera(boolean animate, int source) {
- if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
- } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
- } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) {
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER;
- } else {
-
- // Default.
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
- }
-
- // If we are launching it when we are occluded already we don't want it to animate,
- // nor setting these flags, since the occluded state doesn't change anymore, hence it's
- // never reset.
- if (!isFullyCollapsed()) {
- setLaunchingAffordance(true);
- } else {
- animate = false;
- }
- mAffordanceHasPreview = mKeyguardBottomArea.getRightPreview() != null;
- mAffordanceHelper.launchAffordance(
- animate, mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+ /** Launches the camera. */
+ public void launchCamera(int source) {
}
public void onAffordanceLaunchEnded() {
@@ -3723,9 +3653,6 @@ public class NotificationPanelViewController extends PanelViewController {
*/
private void setLaunchingAffordance(boolean launchingAffordance) {
mLaunchingAffordance = launchingAffordance;
- mKeyguardAffordanceHelperCallback.getLeftIcon().setLaunchingAffordance(launchingAffordance);
- mKeyguardAffordanceHelperCallback.getRightIcon().setLaunchingAffordance(
- launchingAffordance);
mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
}
@@ -3733,24 +3660,14 @@ public class NotificationPanelViewController extends PanelViewController {
* Return true when a bottom affordance is launching an occluded activity with a splash screen.
*/
public boolean isLaunchingAffordanceWithPreview() {
- return mLaunchingAffordance && mAffordanceHasPreview;
+ return mLaunchingAffordance;
}
/**
* Whether the camera application can be launched for the camera launch gesture.
*/
public boolean canCameraGestureBeLaunched() {
- if (!mCentralSurfaces.isCameraAllowedByAdmin()) {
- return false;
- }
-
- ResolveInfo resolveInfo = mKeyguardBottomArea.resolveCameraIntent();
- String
- packageToLaunch =
- (resolveInfo == null || resolveInfo.activityInfo == null) ? null
- : resolveInfo.activityInfo.packageName;
- return packageToLaunch != null && (mBarState != StatusBarState.SHADE || !isForegroundApp(
- packageToLaunch)) && !mAffordanceHelper.isSwipingInProgress();
+ return false;
}
/**
@@ -3839,9 +3756,6 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void setTouchAndAnimationDisabled(boolean disabled) {
super.setTouchAndAnimationDisabled(disabled);
- if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
- mAffordanceHelper.reset(false /* animate */);
- }
mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled);
}
@@ -3924,11 +3838,6 @@ public class NotificationPanelViewController extends PanelViewController {
return mKeyguardBottomArea;
}
- public void setUserSetupComplete(boolean userSetupComplete) {
- mUserSetupComplete = userSetupComplete;
- mKeyguardBottomArea.setUserSetupComplete(userSetupComplete);
- }
-
public void applyLaunchAnimationProgress(float linearProgress) {
boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
@@ -4282,10 +4191,6 @@ public class NotificationPanelViewController extends PanelViewController {
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
}
boolean handled = false;
- if ((!mIsExpanding || mHintAnimationRunning) && !mQsExpanded
- && mBarState != StatusBarState.SHADE && !mDozing) {
- handled |= mAffordanceHelper.onTouchEvent(event);
- }
if (mOnlyAffordanceInThisMotion) {
return true;
}
@@ -4528,147 +4433,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- private class KeyguardAffordanceHelperCallback implements KeyguardAffordanceHelper.Callback {
- @Override
- public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
- boolean
- start =
- mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? rightPage
- : !rightPage;
- mIsLaunchTransitionRunning = true;
- mLaunchAnimationEndRunnable = null;
- float displayDensity = mCentralSurfaces.getDisplayDensity();
- int lengthDp = Math.abs((int) (translation / displayDensity));
- int velocityDp = Math.abs((int) (vel / displayDensity));
- if (start) {
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_DIALER, lengthDp, velocityDp);
- mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_DIALER);
- mFalsingCollector.onLeftAffordanceOn();
- if (mFalsingCollector.shouldEnforceBouncer()) {
- mCentralSurfaces.executeRunnableDismissingKeyguard(
- () -> mKeyguardBottomArea.launchLeftAffordance(), null,
- true /* dismissShade */, false /* afterKeyguardGone */,
- true /* deferred */);
- } else {
- mKeyguardBottomArea.launchLeftAffordance();
- }
- } else {
- if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals(
- mLastCameraLaunchSource)) {
- mLockscreenGestureLogger.write(
- MetricsEvent.ACTION_LS_CAMERA, lengthDp, velocityDp);
- mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_CAMERA);
- }
- mFalsingCollector.onCameraOn();
- if (mFalsingCollector.shouldEnforceBouncer()) {
- mCentralSurfaces.executeRunnableDismissingKeyguard(
- () -> mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource), null,
- true /* dismissShade */, false /* afterKeyguardGone */,
- true /* deferred */);
- } else {
- mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
- }
- }
- mCentralSurfaces.startLaunchTransitionTimeout();
- mBlockTouches = true;
- }
-
- @Override
- public void onAnimationToSideEnded() {
- mIsLaunchTransitionRunning = false;
- mIsLaunchTransitionFinished = true;
- if (mLaunchAnimationEndRunnable != null) {
- mLaunchAnimationEndRunnable.run();
- mLaunchAnimationEndRunnable = null;
- }
- mCentralSurfaces.readyForKeyguardDone();
- }
-
- @Override
- public float getMaxTranslationDistance() {
- return (float) Math.hypot(mView.getWidth(), getHeight());
- }
-
- @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;
- }
-
- @Override
- public void onSwipingAborted() {
- mFalsingCollector.onAffordanceSwipingAborted();
- mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
- }
-
- @Override
- public void onIconClicked(boolean rightIcon) {
- if (mHintAnimationRunning) {
- return;
- }
- mHintAnimationRunning = true;
- mAffordanceHelper.startHintAnimation(rightIcon, () -> {
- mHintAnimationRunning = false;
- mCentralSurfaces.onHintFinished();
- });
- rightIcon =
- mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
- : rightIcon;
- if (rightIcon) {
- mCentralSurfaces.onCameraHintStarted();
- } else {
- if (mKeyguardBottomArea.isLeftVoiceAssist()) {
- mCentralSurfaces.onVoiceAssistHintStarted();
- } else {
- mCentralSurfaces.onPhoneHintStarted();
- }
- }
- }
-
- @Override
- public KeyguardAffordanceView getLeftIcon() {
- return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getRightView() : mKeyguardBottomArea.getLeftView();
- }
-
- @Override
- public KeyguardAffordanceView getRightIcon() {
- return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getLeftView() : mKeyguardBottomArea.getRightView();
- }
-
- @Override
- public View getLeftPreview() {
- return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getRightPreview() : mKeyguardBottomArea.getLeftPreview();
- }
-
- @Override
- public View getRightPreview() {
- return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getLeftPreview() : mKeyguardBottomArea.getRightPreview();
- }
-
- @Override
- public float getAffordanceFalsingFactor() {
- return mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
- }
-
- @Override
- public boolean needsAntiFalsing() {
- return mBarState == KEYGUARD;
- }
- }
-
private class OnEmptySpaceClickListener implements
NotificationStackScrollLayout.OnEmptySpaceClickListener {
@Override
@@ -5127,15 +4891,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- private class OnConfigurationChangedListener extends
- PanelViewController.OnConfigurationChangedListener {
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mAffordanceHelper.onConfigurationChanged();
- }
- }
-
private class OnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
// the same types of insets that are handled in NotificationShadeWindowView
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..d2fc1af010b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -484,8 +484,6 @@ public abstract class PanelViewController {
protected abstract boolean shouldGestureWaitForTouchSlop();
- protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y);
-
protected void onTrackingStopped(boolean expand) {
mTracking = false;
mCentralSurfaces.onTrackingStopped(expand);
@@ -1163,8 +1161,6 @@ public abstract class PanelViewController {
mTouchDisabled ? "T" : "f"));
}
- public abstract void resetViews(boolean animate);
-
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
}
@@ -1335,7 +1331,7 @@ public abstract class PanelViewController {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
- mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y);
+ mIgnoreXTouchSlop = true;
}
switch (event.getActionMasked()) {
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/statusbar/policy/PreviewInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
deleted file mode 100644
index 3d317143eb51..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2014 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.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.ActivityIntentHelper;
-import com.android.systemui.statusbar.phone.KeyguardPreviewContainer;
-
-import java.util.List;
-
-/**
- * Utility class to inflate previews for phone and camera affordance.
- */
-public class PreviewInflater {
-
- private static final String TAG = "PreviewInflater";
-
- private static final String META_DATA_KEYGUARD_LAYOUT = "com.android.keyguard.layout";
- private final ActivityIntentHelper mActivityIntentHelper;
-
- private Context mContext;
- private LockPatternUtils mLockPatternUtils;
-
- public PreviewInflater(Context context, LockPatternUtils lockPatternUtils,
- ActivityIntentHelper activityIntentHelper) {
- mContext = context;
- mLockPatternUtils = lockPatternUtils;
- mActivityIntentHelper = activityIntentHelper;
- }
-
- public View inflatePreview(Intent intent) {
- WidgetInfo info = getWidgetInfo(intent);
- return inflatePreview(info);
- }
-
- public View inflatePreviewFromService(ComponentName componentName) {
- WidgetInfo info = getWidgetInfoFromService(componentName);
- return inflatePreview(info);
- }
-
- private KeyguardPreviewContainer inflatePreview(WidgetInfo info) {
- if (info == null) {
- return null;
- }
- View v = inflateWidgetView(info);
- if (v == null) {
- return null;
- }
- KeyguardPreviewContainer container = new KeyguardPreviewContainer(mContext, null);
- container.addView(v);
- return container;
- }
-
- private View inflateWidgetView(WidgetInfo widgetInfo) {
- View widgetView = null;
- try {
- Context appContext = mContext.createPackageContext(
- widgetInfo.contextPackage, Context.CONTEXT_RESTRICTED);
- LayoutInflater appInflater = (LayoutInflater)
- appContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- appInflater = appInflater.cloneInContext(appContext);
- widgetView = appInflater.inflate(widgetInfo.layoutId, null, false);
- } catch (PackageManager.NameNotFoundException|RuntimeException e) {
- Log.w(TAG, "Error creating widget view", e);
- }
- return widgetView;
- }
-
- private WidgetInfo getWidgetInfoFromService(ComponentName componentName) {
- PackageManager packageManager = mContext.getPackageManager();
- // Look for the preview specified in the service meta-data
- try {
- Bundle metaData = packageManager.getServiceInfo(
- componentName, PackageManager.GET_META_DATA).metaData;
- return getWidgetInfoFromMetaData(componentName.getPackageName(), metaData);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Failed to load preview; " + componentName.flattenToShortString()
- + " not found", e);
- }
- return null;
- }
-
- private WidgetInfo getWidgetInfoFromMetaData(String contextPackage,
- Bundle metaData) {
- if (metaData == null) {
- return null;
- }
- int layoutId = metaData.getInt(META_DATA_KEYGUARD_LAYOUT);
- if (layoutId == 0) {
- return null;
- }
- WidgetInfo info = new WidgetInfo();
- info.contextPackage = contextPackage;
- info.layoutId = layoutId;
- return info;
- }
-
- private WidgetInfo getWidgetInfo(Intent intent) {
- PackageManager packageManager = mContext.getPackageManager();
- int flags = PackageManager.MATCH_DEFAULT_ONLY
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
- intent, flags, KeyguardUpdateMonitor.getCurrentUser());
- if (appList.size() == 0) {
- return null;
- }
- ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
- flags | PackageManager.GET_META_DATA,
- KeyguardUpdateMonitor.getCurrentUser());
- if (mActivityIntentHelper.wouldLaunchResolverActivity(resolved, appList)) {
- return null;
- }
- if (resolved == null || resolved.activityInfo == null) {
- return null;
- }
- return getWidgetInfoFromMetaData(resolved.activityInfo.packageName,
- resolved.activityInfo.metaData);
- }
-
- private static class WidgetInfo {
- String contextPackage;
- int layoutId;
- }
-}
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/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..1527f0d0d71f 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,6 +366,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
windowManager,
viewUtil,
mainExecutor,
+ accessibilityManager,
tapGestureDetector,
powerManager,
R.layout.media_ttt_chip
@@ -364,7 +379,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
}
inner class ChipInfo : ChipInfoCommon {
- override fun getTimeoutMs() = TIMEOUT_MS
+ override fun getTimeoutMs() = 1L
}
}
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..f286349971d2 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"));
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..95460583e857 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;
@@ -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
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/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 2faff0ced70a..266f0e95f2ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -848,7 +848,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mCentralSurfaces.showKeyguardImpl();
// Starting a pulse should change the scrim controller to the pulsing state
- when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true);
when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(true);
mCentralSurfaces.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
@@ -885,7 +884,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mCentralSurfaces.showKeyguardImpl();
// Starting a pulse should change the scrim controller to the pulsing state
- when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true);
when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(false);
mCentralSurfaces.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
index 31465f45af42..4b557dc423ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
@@ -12,12 +12,12 @@ import com.android.systemui.statusbar.policy.AccessibilityController
import com.android.systemui.statusbar.policy.FlashlightController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.tuner.TunerService
+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.MockitoAnnotations
-import java.util.concurrent.Executor
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -51,6 +51,5 @@ class KeyguardBottomAreaTest : SysuiTestCase() {
null, false) as KeyguardBottomAreaView
other.initFrom(mKeyguardBottomArea)
- other.launchVoiceAssist()
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 79c7e55d4d09..046af95c8c1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -106,7 +106,6 @@ import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -401,8 +400,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mNotificationStackScrollLayoutController.getHeadsUpCallback())
.thenReturn(mHeadsUpCallback);
when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
- when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
- when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class));
when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
when(mView.findViewById(R.id.keyguard_status_view))
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/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/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/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 77d3392da993..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,
@@ -2605,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);
}
/**
@@ -2642,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;
@@ -2653,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
@@ -2709,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;
}
@@ -2743,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;
@@ -2785,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);
}
}
@@ -2794,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;
}
@@ -2815,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.
@@ -2854,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());
}
@@ -2867,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;
@@ -2900,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) {
@@ -2918,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;
@@ -2949,7 +3286,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
ikeException.getErrorType(),
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- mActiveNetwork,
+ network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -2967,7 +3304,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE,
ikeException.getErrorType(),
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- mActiveNetwork,
+ network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -2986,7 +3323,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_LOST,
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- mActiveNetwork,
+ network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3001,7 +3338,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST,
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- mActiveNetwork,
+ network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3015,7 +3352,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT,
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- mActiveNetwork,
+ network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3029,7 +3366,7 @@ public class Vpn {
VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_IO,
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- mActiveNetwork,
+ network,
getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
mUnderlyingNetworkCapabilities),
getRedactedLinkPropertiesOfUnderlyingNetwork(
@@ -3039,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
@@ -3092,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/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/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 9a050e3400d8..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);
@@ -8172,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);
@@ -8725,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..960b1bcd5c42 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2576,6 +2576,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 +2619,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.
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 a1bf91514d8b..288777bdb324 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2942,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);
@@ -6194,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/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/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 36464b824437..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) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5d2e34bb1b28..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();
@@ -8277,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);
}
@@ -8535,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/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 74e15cf08c69..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
@@ -1247,7 +1251,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mSession.windowAddedLocked();
}
- boolean updateGlobalScale() {
+ void updateGlobalScale() {
if (hasCompatScale()) {
if (mOverrideScale != 1f) {
mGlobalScale = mToken.hasSizeCompatBounds()
@@ -1257,11 +1261,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mGlobalScale = mToken.getSizeCompatScale();
}
mInvGlobalScale = 1f / mGlobalScale;
- return true;
+ return;
}
mGlobalScale = mInvGlobalScale = 1f;
- return false;
}
/**
@@ -1514,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(),
@@ -1586,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
@@ -3837,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
@@ -3888,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,
@@ -4406,6 +4423,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
pw.println(prefix + "Requested visibilities: " + visibilityString);
}
}
+
+ pw.println(prefix + "mPrepareSyncSeqId=" + mPrepareSyncSeqId);
}
@Override
@@ -5897,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()) {
@@ -5912,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;
}
@@ -5933,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);
}
@@ -5954,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;
@@ -5966,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 =
@@ -6199,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/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/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/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/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/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/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)
}
}