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/service/java/com/android/server/DeviceIdleController.java65
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java5
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java2
-rw-r--r--boot/Android.bp4
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/app/TEST_MAPPING3
-rw-r--r--core/java/android/companion/AssociationInfo.java46
-rw-r--r--core/java/android/content/TEST_MAPPING3
-rw-r--r--core/java/android/content/res/CompatibilityInfo.java5
-rw-r--r--core/java/android/debug/AdbManagerInternal.java6
-rw-r--r--core/java/android/debug/OWNERS1
-rw-r--r--core/java/android/hardware/display/DisplayManager.java10
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java29
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java37
-rw-r--r--core/java/android/os/Environment.java4
-rw-r--r--core/java/android/os/Parcel.java16
-rw-r--r--core/java/android/os/TEST_MAPPING3
-rw-r--r--core/java/android/os/ZygoteProcess.java17
-rw-r--r--core/java/android/provider/Settings.java2
-rw-r--r--core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java4
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java37
-rw-r--r--core/java/android/util/FeatureFlagUtils.java13
-rw-r--r--core/java/android/view/HandwritingInitiator.java102
-rw-r--r--core/java/android/view/IRemoteAnimationRunner.aidl2
-rw-r--r--core/java/android/view/IWindowManager.aidl5
-rw-r--r--core/java/android/view/IWindowSession.aidl48
-rw-r--r--core/java/android/view/TEST_MAPPING3
-rw-r--r--core/java/android/view/ViewRootImpl.java157
-rw-r--r--core/java/android/view/WindowLayout.java20
-rw-r--r--core/java/android/view/WindowManager.java5
-rw-r--r--core/java/android/view/WindowManagerGlobal.java5
-rw-r--r--core/java/android/view/WindowlessWindowLayout.java8
-rw-r--r--core/java/android/view/WindowlessWindowManager.java29
-rw-r--r--core/java/android/view/inputmethod/InputMethod.java10
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java84
-rw-r--r--core/java/android/widget/Editor.java20
-rw-r--r--core/java/android/widget/TextView.java30
-rw-r--r--core/java/android/window/ClientWindowFrames.java13
-rw-r--r--core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java30
-rw-r--r--core/java/com/android/internal/inputmethod/EditableInputConnection.java31
-rw-r--r--core/java/com/android/internal/inputmethod/IInputMethod.aidl2
-rw-r--r--core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java42
-rw-r--r--core/java/com/android/internal/os/TEST_MAPPING9
-rw-r--r--core/java/com/android/internal/os/ZygoteConfig.java75
-rw-r--r--core/java/com/android/internal/os/ZygoteServer.java73
-rw-r--r--core/java/com/android/internal/power/TEST_MAPPING3
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl4
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItemView.java5
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp15
-rw-r--r--core/jni/com_android_internal_os_Zygote.h8
-rw-r--r--core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp1
-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.xml14
-rw-r--r--core/res/res/values/public-staging.xml2
-rw-r--r--core/tests/coretests/AndroidManifest.xml15
-rw-r--r--core/tests/coretests/res/xml/ime_meta_handwriting.xml21
-rw-r--r--core/tests/coretests/src/android/view/stylus/HandwritingImeService.java32
-rw-r--r--core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java91
-rw-r--r--core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java144
-rw-r--r--data/keyboards/Vendor_0957_Product_0006.idc25
-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/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/PipTransition.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java4
-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.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt2
-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--libs/androidfw/CursorWindow.cpp1
-rw-r--r--location/java/android/location/LocationManager.java14
-rw-r--r--location/java/android/location/package.html20
-rw-r--r--media/java/android/media/AudioManager.java48
-rw-r--r--media/java/android/media/MediaDrm.java2
-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/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt8
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt21
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java2
-rw-r--r--packages/SystemUI/res-keyguard/font/clock.xml28
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml32
-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.xml6
-rw-r--r--packages/SystemUI/res/layout/hybrid_conversation_notification.xml6
-rw-r--r--packages/SystemUI/res/layout/hybrid_notification.xml9
-rw-r--r--packages/SystemUI/res/layout/media_ttt_chip.xml5
-rw-r--r--packages/SystemUI/res/values-h800dp/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/attrs.xml6
-rw-r--r--packages/SystemUI/res/values/dimens.xml13
-rw-r--r--packages/SystemUI/res/values/strings.xml8
-rw-r--r--packages/SystemUI/res/values/styles.xml24
-rw-r--r--packages/SystemUI/screenshot/Android.bp48
-rw-r--r--packages/SystemUI/screenshot/AndroidManifest.xml28
-rw-r--r--packages/SystemUI/screenshot/res/values/themes.xml25
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java193
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt22
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestRule.kt206
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestSpec.kt78
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt51
-rw-r--r--packages/SystemUI/shared/res/layout/clock_default_large.xml1
-rw-r--r--packages/SystemUI/shared/res/layout/clock_default_small.xml2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt18
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt39
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt40
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java29
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt153
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java182
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java213
-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/keyguard/KeyguardStatusView.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt188
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java8
-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.java8
-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/MediaDataManager.kt2
-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/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/connectivity/MobileSignalController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt5
-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/ShadeListBuilder.java8
-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/RowAppearanceCoordinator.kt2
-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.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.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/RowContentBindStage.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java9
-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/HeadsUpManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt259
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java68
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java218
-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/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/shared/clocks/ClockRegistryTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt78
-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/coordinator/KeyguardCoordinatorTest.java90
-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/RowAppearanceCoordinatorTest.kt2
-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.kt2
-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/fragment/CollapsedStatusBarFragmentTest.java40
-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/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.java366
-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/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java54
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/BackupHelper.java782
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissionGroup.java1574
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissions.java227
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/model/Permission.java414
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/ArrayUtils.java61
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/LocationUtils.java135
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/SoftRestrictedPermissionPolicy.java84
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/Utils.java819
-rw-r--r--services/core/java/com/android/server/AnimationThread.java2
-rw-r--r--services/core/java/com/android/server/PermissionThread.java94
-rw-r--r--services/core/java/com/android/server/PersistentDataBlockService.java17
-rw-r--r--services/core/java/com/android/server/adb/AdbDebuggingManager.java543
-rw-r--r--services/core/java/com/android/server/adb/AdbService.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java16
-rw-r--r--services/core/java/com/android/server/am/OWNERS7
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java9
-rw-r--r--services/core/java/com/android/server/am/TEST_MAPPING3
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java55
-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/hdmi/HdmiCecController.java3
-rwxr-xr-xservices/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java22
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java12
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java2
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java40
-rw-r--r--services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java5
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java332
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java130
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodMenuController.java2
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java4
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodUtils.java568
-rw-r--r--services/core/java/com/android/server/inputmethod/LocaleUtils.java23
-rw-r--r--services/core/java/com/android/server/inputmethod/SubtypeUtils.java297
-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/OneTimePermissionUserManager.java4
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java11
-rw-r--r--services/core/java/com/android/server/policy/PermissionPolicyService.java19
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java5
-rw-r--r--services/core/java/com/android/server/testharness/TestHarnessModeService.java1
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java3
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java50
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java27
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java16
-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/DragState.java13
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java30
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java18
-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/SurfaceAnimationThread.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java93
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java37
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java13
-rw-r--r--services/java/com/android/server/SystemServer.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java205
-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/hdmi/BaseAbsoluteVolumeControlTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java46
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java132
-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.java5
-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/SystemServiceTestsBase.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java14
-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.java3
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java11
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java11
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java17
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java17
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java1
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java9
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java2
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbAudioChannelCluster.java41
-rw-r--r--services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java54
-rw-r--r--telecomm/java/android/telecom/InCallService.java25
-rw-r--r--telephony/java/android/telephony/AccessNetworkUtils.java107
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java31
-rw-r--r--telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl14
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt18
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt68
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt73
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt77
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt34
-rw-r--r--tools/localedata/OWNERS2
350 files changed, 8195 insertions, 8293 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/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 2eb86c1b720f..40d1b4c9b267 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -73,6 +73,7 @@ import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArrayMap;
@@ -2329,8 +2330,8 @@ public class DeviceIdleController extends SystemService
// a battery update the next time the level drops.
mCharging = true;
mActiveReason = ACTIVE_REASON_UNKNOWN;
- mState = STATE_ACTIVE;
- mLightState = LIGHT_STATE_ACTIVE;
+ moveToStateLocked(STATE_ACTIVE, "boot");
+ moveToLightStateLocked(LIGHT_STATE_ACTIVE, "boot");
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
mPreIdleFactor = 1.0f;
mLastPreIdleFactor = 1.0f;
@@ -3173,8 +3174,7 @@ public class DeviceIdleController extends SystemService
+ ", changeLightIdle=" + changeLightIdle);
}
if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
- EventLogTags.writeDeviceIdle(STATE_ACTIVE, activeReason);
- mState = STATE_ACTIVE;
+ moveToStateLocked(STATE_ACTIVE, activeReason);
mInactiveTimeout = newInactiveTimeout;
resetIdleManagementLocked();
// Don't reset maintenance window start time if we're in a light idle maintenance window
@@ -3184,8 +3184,7 @@ public class DeviceIdleController extends SystemService
}
if (changeLightIdle) {
- EventLogTags.writeDeviceIdleLight(LIGHT_STATE_ACTIVE, activeReason);
- mLightState = LIGHT_STATE_ACTIVE;
+ moveToLightStateLocked(LIGHT_STATE_ACTIVE, activeReason);
resetLightIdleManagementLocked();
// Only report active if light is also ACTIVE.
scheduleReportActiveLocked(activeReason, activeUid);
@@ -3258,11 +3257,7 @@ public class DeviceIdleController extends SystemService
// values, so returning here is safe.
return;
}
- if (DEBUG) {
- Slog.d(TAG, "Moved from "
- + stateToString(mState) + " to STATE_QUICK_DOZE_DELAY");
- }
- mState = STATE_QUICK_DOZE_DELAY;
+ moveToStateLocked(STATE_QUICK_DOZE_DELAY, "no activity");
// Make sure any motion sensing or locating is stopped.
resetIdleManagementLocked();
if (isUpcomingAlarmClock()) {
@@ -3277,10 +3272,8 @@ public class DeviceIdleController extends SystemService
// recently closed app) needs to finish running.
scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT, false);
}
- EventLogTags.writeDeviceIdle(mState, "no activity");
} else if (mState == STATE_ACTIVE) {
- mState = STATE_INACTIVE;
- if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
+ moveToStateLocked(STATE_INACTIVE, "no activity");
resetIdleManagementLocked();
long delay = mInactiveTimeout;
if (shouldUseIdleTimeoutFactorLocked()) {
@@ -3296,12 +3289,10 @@ public class DeviceIdleController extends SystemService
} else {
scheduleAlarmLocked(delay, false);
}
- EventLogTags.writeDeviceIdle(mState, "no activity");
}
}
if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
- mLightState = LIGHT_STATE_INACTIVE;
- if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
+ moveToLightStateLocked(LIGHT_STATE_INACTIVE, "no activity");
resetLightIdleManagementLocked();
scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
mConstants.FLEX_TIME_SHORT);
@@ -3309,7 +3300,6 @@ public class DeviceIdleController extends SystemService
// timeout and a single light idle period.
scheduleLightMaintenanceAlarmLocked(
mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT + mConstants.LIGHT_IDLE_TIMEOUT);
- EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
}
}
@@ -3429,12 +3419,7 @@ public class DeviceIdleController extends SystemService
// time from now.
scheduleLightAlarmLocked(mCurLightIdleBudget, mConstants.FLEX_TIME_SHORT);
scheduleLightMaintenanceAlarmLocked(mCurLightIdleBudget + mNextLightIdleDelay);
- if (DEBUG) {
- Slog.d(TAG, "Moved from " + lightStateToString(mLightState)
- + " to LIGHT_STATE_IDLE_MAINTENANCE");
- }
- mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
- EventLogTags.writeDeviceIdleLight(mLightState, reason);
+ moveToLightStateLocked(LIGHT_STATE_IDLE_MAINTENANCE, reason);
addEvent(EVENT_LIGHT_MAINTENANCE, null);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
} else {
@@ -3443,9 +3428,7 @@ public class DeviceIdleController extends SystemService
// We'll only wait for another full idle period, however, and then give up.
scheduleLightMaintenanceAlarmLocked(mNextLightIdleDelay);
cancelLightAlarmLocked();
- if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
- mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
- EventLogTags.writeDeviceIdleLight(mLightState, reason);
+ moveToLightStateLocked(LIGHT_STATE_WAITING_FOR_NETWORK, reason);
}
} else {
if (mMaintenanceStartTime != 0) {
@@ -3469,9 +3452,7 @@ public class DeviceIdleController extends SystemService
// maintenance window, so reschedule the alarm starting from now.
scheduleLightMaintenanceAlarmLocked(mNextLightIdleDelay);
cancelLightAlarmLocked();
- if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
- mLightState = LIGHT_STATE_IDLE;
- EventLogTags.writeDeviceIdleLight(mLightState, reason);
+ moveToLightStateLocked(LIGHT_STATE_IDLE, reason);
addEvent(EVENT_LIGHT_IDLE, null);
mGoingIdleWakeLock.acquire();
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
@@ -3535,12 +3516,11 @@ public class DeviceIdleController extends SystemService
moveToStateLocked(STATE_IDLE_PENDING, reason);
break;
case STATE_IDLE_PENDING:
- moveToStateLocked(STATE_SENSING, reason);
cancelLocatingLocked();
mLocated = false;
mLastGenericLocation = null;
mLastGpsLocation = null;
- updateActiveConstraintsLocked();
+ moveToStateLocked(STATE_SENSING, reason);
// Wait for open constraints and an accelerometer reading before moving on.
if (mUseMotionSensor && mAnyMotionDetector.hasSensor()) {
@@ -3609,7 +3589,7 @@ public class DeviceIdleController extends SystemService
}
moveToStateLocked(STATE_IDLE, reason);
if (mLightState != LIGHT_STATE_OVERRIDE) {
- mLightState = LIGHT_STATE_OVERRIDE;
+ moveToLightStateLocked(LIGHT_STATE_OVERRIDE, "deep");
cancelAllLightAlarmsLocked();
}
addEvent(EVENT_DEEP_IDLE, null);
@@ -3637,14 +3617,27 @@ public class DeviceIdleController extends SystemService
}
@GuardedBy("this")
+ private void moveToLightStateLocked(int state, String reason) {
+ if (DEBUG) {
+ Slog.d(TAG, String.format("Moved from LIGHT_STATE_%s to LIGHT_STATE_%s.",
+ lightStateToString(mLightState), lightStateToString(state)));
+ }
+ mLightState = state;
+ EventLogTags.writeDeviceIdleLight(mLightState, reason);
+ // This is currently how to set the current state in a trace.
+ Trace.traceCounter(Trace.TRACE_TAG_SYSTEM_SERVER, "DozeLightState", state);
+ }
+
+ @GuardedBy("this")
private void moveToStateLocked(int state, String reason) {
- final int oldState = mState;
- mState = state;
if (DEBUG) {
Slog.d(TAG, String.format("Moved from STATE_%s to STATE_%s.",
- stateToString(oldState), stateToString(mState)));
+ stateToString(mState), stateToString(state)));
}
+ mState = state;
EventLogTags.writeDeviceIdle(mState, reason);
+ // This is currently how to set the current state in a trace.
+ Trace.traceCounter(Trace.TRACE_TAG_SYSTEM_SERVER, "DozeDeepState", state);
updateActiveConstraintsLocked();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index f7fe9cab60ae..68801bcac4ba 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -43,6 +43,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
+import android.os.Trace;
import android.os.UserHandle;
import android.util.EventLog;
import android.util.IndentingPrintWriter;
@@ -361,6 +362,9 @@ public final class JobServiceContext implements ServiceConnection {
job.getJob().getPriority(),
job.getEffectivePriority(),
job.getNumFailures());
+ // Use the context's ID to distinguish traces since there'll only be one job running
+ // per context.
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, job.getBatteryName(), getId());
try {
mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid());
} catch (RemoteException e) {
@@ -1024,6 +1028,7 @@ public final class JobServiceContext implements ServiceConnection {
completedJob.getJob().getPriority(),
completedJob.getEffectivePriority(),
completedJob.getNumFailures());
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_SYSTEM_SERVER, completedJob.getBatteryName(), getId());
try {
mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
internalStopReason);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 251cf5640de7..e0f58e3fa6e3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -496,7 +496,7 @@ public final class JobStatus {
this.batteryName = this.sourceTag != null
? this.sourceTag + ":" + job.getService().getPackageName()
: job.getService().flattenToShortString();
- this.tag = "*job*/" + this.batteryName;
+ this.tag = "*job*/" + this.batteryName + "#" + job.getId();
this.earliestRunTimeElapsedMillis = earliestRunTimeElapsedMillis;
this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
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/api/system-current.txt b/core/api/system-current.txt
index 02c9f6862e53..e1ce5bd71423 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -387,6 +387,7 @@ package android {
public static final class R.bool {
field public static final int config_enableQrCodeScannerOnLockScreen = 17891336; // 0x1110008
+ field public static final int config_safetyProtectionEnabled;
field public static final int config_sendPackageName = 17891328; // 0x1110000
field public static final int config_showDefaultAssistant = 17891329; // 0x1110001
field public static final int config_showDefaultEmergency = 17891330; // 0x1110002
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 908d3f802764..5b0bd9614f83 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -121,6 +121,9 @@
"include-annotation": "android.platform.test.annotations.Presubmit"
},
{
+ "exclude-annotation": "android.platform.test.annotations.LargeTest"
+ },
+ {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
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/TEST_MAPPING b/core/java/android/content/TEST_MAPPING
index 5bb845d5a1a1..dac79e7124bd 100644
--- a/core/java/android/content/TEST_MAPPING
+++ b/core/java/android/content/TEST_MAPPING
@@ -7,6 +7,9 @@
"include-annotation": "android.platform.test.annotations.Presubmit"
},
{
+ "exclude-annotation": "android.platform.test.annotations.LargeTest"
+ },
+ {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 439c6396f1d0..608e34bd07b4 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -420,7 +420,10 @@ public class CompatibilityInfo implements Parcelable {
* Translate a Rect in screen coordinates into the app window's coordinates.
*/
@UnsupportedAppUsage
- public void translateRectInScreenToAppWindow(Rect rect) {
+ public void translateRectInScreenToAppWindow(@Nullable Rect rect) {
+ if (rect == null) {
+ return;
+ }
rect.scale(applicationInvertedScale);
}
diff --git a/core/java/android/debug/AdbManagerInternal.java b/core/java/android/debug/AdbManagerInternal.java
index d730129507d7..e448706fabfe 100644
--- a/core/java/android/debug/AdbManagerInternal.java
+++ b/core/java/android/debug/AdbManagerInternal.java
@@ -55,6 +55,12 @@ public abstract class AdbManagerInternal {
public abstract File getAdbTempKeysFile();
/**
+ * Notify the AdbManager that the key files have changed and any in-memory state should be
+ * reloaded.
+ */
+ public abstract void notifyKeyFilesUpdated();
+
+ /**
* Starts adbd for a transport.
*/
public abstract void startAdbdForTransport(byte transportType);
diff --git a/core/java/android/debug/OWNERS b/core/java/android/debug/OWNERS
new file mode 100644
index 000000000000..b97f7956d115
--- /dev/null
+++ b/core/java/android/debug/OWNERS
@@ -0,0 +1 @@
+include platform/packages/modules/adb:/OWNERS
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/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index ee34ffe33ff6..b47e92d09989 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -195,15 +195,14 @@ class IInputMethodWrapper extends IInputMethod.Stub
case DO_START_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
final IBinder startInputToken = (IBinder) args.arg1;
- final IRemoteInputConnection remoteIc =
- (IRemoteInputConnection) ((SomeArgs) args.arg2).arg1;
- final ImeOnBackInvokedDispatcher imeDispatcher =
- (ImeOnBackInvokedDispatcher) ((SomeArgs) args.arg2).arg2;
+ final IRemoteInputConnection remoteIc = (IRemoteInputConnection) args.arg2;
final EditorInfo info = (EditorInfo) args.arg3;
- final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
- final boolean restarting = args.argi5 == 1;
+ final ImeOnBackInvokedDispatcher imeDispatcher =
+ (ImeOnBackInvokedDispatcher) args.arg4;
+ final CancellationGroup cancellationGroup = (CancellationGroup) args.arg5;
+ final boolean restarting = args.argi1 == 1;
@InputMethodNavButtonFlags
- final int navButtonFlags = args.argi6;
+ final int navButtonFlags = args.argi2;
final InputConnection ic = remoteIc != null
? new RemoteInputConnection(mTarget, remoteIc, cancellationGroup)
: null;
@@ -352,18 +351,22 @@ class IInputMethodWrapper extends IInputMethod.Stub
@BinderThread
@Override
public void startInput(IBinder startInputToken, IRemoteInputConnection inputConnection,
- EditorInfo attribute, boolean restarting,
+ EditorInfo editorInfo, boolean restarting,
@InputMethodNavButtonFlags int navButtonFlags,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
if (mCancellationGroup == null) {
Log.e(TAG, "startInput must be called after bindInput.");
mCancellationGroup = new CancellationGroup();
}
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = inputConnection;
- args.arg2 = imeDispatcher;
- mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken,
- args, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags));
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = startInputToken;
+ args.arg2 = inputConnection;
+ args.arg3 = editorInfo;
+ args.argi1 = restarting ? 1 : 0;
+ args.argi2 = navButtonFlags;
+ args.arg4 = imeDispatcher;
+ args.arg5 = mCancellationGroup;
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_INPUT, args));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 9c146b58de49..e442e6dcd8a8 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -366,7 +366,7 @@ public class InputMethodService extends AbstractInputMethodService {
**/
private RingBuffer<MotionEvent> mPendingEvents;
private ImeOnBackInvokedDispatcher mImeDispatcher;
- private Boolean mBackCallbackRegistered = false;
+ private boolean mBackCallbackRegistered = false;
private final CompatOnBackInvokedCallback mCompatBackCallback = this::compatHandleBack;
private Runnable mImeSurfaceRemoverRunnable;
private Runnable mFinishHwRunnable;
@@ -796,10 +796,10 @@ public class InputMethodService extends AbstractInputMethodService {
*/
@MainThread
@Override
- public void startInput(InputConnection ic, EditorInfo attribute) {
- if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
+ public void startInput(InputConnection ic, EditorInfo editorInfo) {
+ if (DEBUG) Log.v(TAG, "startInput(): editor=" + editorInfo);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.startInput");
- doStartInput(ic, attribute, false);
+ doStartInput(ic, editorInfo, false);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -808,10 +808,10 @@ public class InputMethodService extends AbstractInputMethodService {
*/
@MainThread
@Override
- public void restartInput(InputConnection ic, EditorInfo attribute) {
- if (DEBUG) Log.v(TAG, "restartInput(): editor=" + attribute);
+ public void restartInput(InputConnection ic, EditorInfo editorInfo) {
+ if (DEBUG) Log.v(TAG, "restartInput(): editor=" + editorInfo);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.restartInput");
- doStartInput(ic, attribute, true);
+ doStartInput(ic, editorInfo, true);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -2315,11 +2315,11 @@ public class InputMethodService extends AbstractInputMethodService {
* setup here. You are guaranteed that {@link #onCreateInputView()} will
* have been called some time before this function is called.
*
- * @param info Description of the type of text being edited.
+ * @param editorInfo Description of the type of text being edited.
* @param restarting Set to true if we are restarting input on the
* same text field as before.
*/
- public void onStartInputView(EditorInfo info, boolean restarting) {
+ public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
// Intentionally empty
}
@@ -2360,11 +2360,11 @@ public class InputMethodService extends AbstractInputMethodService {
* editor is hidden but wants to show its candidates UI as text is
* entered through some other mechanism.
*
- * @param info Description of the type of text being edited.
+ * @param editorInfo Description of the type of text being edited.
* @param restarting Set to true if we are restarting input on the
* same text field as before.
*/
- public void onStartCandidatesView(EditorInfo info, boolean restarting) {
+ public void onStartCandidatesView(EditorInfo editorInfo, boolean restarting) {
// Intentionally empty
}
@@ -2902,7 +2902,7 @@ public class InputMethodService extends AbstractInputMethodService {
unregisterCompatOnBackInvokedCallback();
}
- void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) {
+ void doStartInput(InputConnection ic, EditorInfo editorInfo, boolean restarting) {
if (!restarting && mInputStarted) {
doFinishInput();
}
@@ -2910,13 +2910,13 @@ public class InputMethodService extends AbstractInputMethodService {
null /* icProto */);
mInputStarted = true;
mStartedInputConnection = ic;
- mInputEditorInfo = attribute;
+ mInputEditorInfo = editorInfo;
initialize();
mInlineSuggestionSessionController.notifyOnStartInput(
- attribute == null ? null : attribute.packageName,
- attribute == null ? null : attribute.autofillId);
+ editorInfo == null ? null : editorInfo.packageName,
+ editorInfo == null ? null : editorInfo.autofillId);
if (DEBUG) Log.v(TAG, "CALL: onStartInput");
- onStartInput(attribute, restarting);
+ onStartInput(editorInfo, restarting);
if (mDecorViewVisible) {
if (mShowInputRequested) {
if (DEBUG) Log.v(TAG, "CALL: onStartInputView");
@@ -2987,11 +2987,6 @@ public class InputMethodService extends AbstractInputMethodService {
* the text. This is called whether or not the input method has requested
* extracted text updates, although if so it will not receive this call
* if the extracted text has changed as well.
- *
- * <p>Be careful about changing the text in reaction to this call with
- * methods such as setComposingText, commitText or
- * deleteSurroundingText. If the cursor moves as a result, this method
- * will be called again, which may result in an infinite loop.
*
* <p>The default implementation takes care of updating the cursor in
* the extract text, if it is being shown.
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 5177cb4f8549..ffa9507ebc2a 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -39,9 +39,9 @@ import android.util.Log;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
@@ -977,7 +977,7 @@ public class Environment {
}
private static boolean hasInterestingFiles(File dir) {
- final LinkedList<File> explore = new LinkedList<>();
+ final ArrayDeque<File> explore = new ArrayDeque<>();
explore.add(dir);
while (!explore.isEmpty()) {
dir = explore.pop();
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index c68fb8544569..1dedc2666582 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -247,6 +247,7 @@ public final class Parcel {
private ArrayMap<Class, Object> mClassCookies;
private RuntimeException mStack;
+ private boolean mRecycled = false;
/** @hide */
@TestApi
@@ -528,6 +529,7 @@ public final class Parcel {
if (res == null) {
res = new Parcel(0);
} else {
+ res.mRecycled = false;
if (DEBUG_RECYCLE) {
res.mStack = new RuntimeException();
}
@@ -556,7 +558,15 @@ public final class Parcel {
* the object after this call.
*/
public final void recycle() {
- if (DEBUG_RECYCLE) mStack = null;
+ if (mRecycled) {
+ Log.w(TAG, "Recycle called on unowned Parcel. (recycle twice?)", mStack);
+ }
+ mRecycled = true;
+
+ // We try to reset the entire object here, but in order to be
+ // able to print a stack when a Parcel is recycled twice, that
+ // is cleared in obtain instead.
+
mClassCookies = null;
freeBuffer();
@@ -5112,6 +5122,7 @@ public final class Parcel {
if (res == null) {
res = new Parcel(obj);
} else {
+ res.mRecycled = false;
if (DEBUG_RECYCLE) {
res.mStack = new RuntimeException();
}
@@ -5160,7 +5171,8 @@ public final class Parcel {
@Override
protected void finalize() throws Throwable {
if (DEBUG_RECYCLE) {
- if (mStack != null) {
+ // we could always have this log on, but it's spammy
+ if (!mRecycled) {
Log.w(TAG, "Client did not call Parcel.recycle()", mStack);
}
}
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index 22ddbccbaaae..e02c45ddf5a8 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -53,8 +53,7 @@
"name": "FrameworksServicesTests",
"options": [
{ "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
- { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" },
- { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" }
+ { "include-filter": "com.android.server.power.stats.BatteryStatsTests" }
]
},
{
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index bf2898137967..3cb5c60259eb 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -82,13 +82,6 @@ public class ZygoteProcess {
private static final String LOG_TAG = "ZygoteProcess";
/**
- * The default value for enabling the unspecialized app process (USAP) pool. This value will
- * not be used if the devices has a DeviceConfig profile pushed to it that contains a value for
- * this key.
- */
- private static final String USAP_POOL_ENABLED_DEFAULT = "false";
-
- /**
* The name of the socket used to communicate with the primary zygote.
*/
private final LocalSocketAddress mZygoteSocketAddress;
@@ -793,14 +786,8 @@ public class ZygoteProcess {
private boolean fetchUsapPoolEnabledProp() {
boolean origVal = mUsapPoolEnabled;
- final String propertyString = Zygote.getConfigurationProperty(
- ZygoteConfig.USAP_POOL_ENABLED, USAP_POOL_ENABLED_DEFAULT);
-
- if (!propertyString.isEmpty()) {
- mUsapPoolEnabled = Zygote.getConfigurationPropertyBoolean(
- ZygoteConfig.USAP_POOL_ENABLED,
- Boolean.parseBoolean(USAP_POOL_ENABLED_DEFAULT));
- }
+ mUsapPoolEnabled = ZygoteConfig.getBool(
+ ZygoteConfig.USAP_POOL_ENABLED, ZygoteConfig.USAP_POOL_ENABLED_DEFAULT);
boolean valueChanged = origVal != mUsapPoolEnabled;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 22672131a85b..a513d5eb961b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -17701,7 +17701,7 @@ public final class Settings {
public @interface SyncDisabledMode {}
/**
- * Sync is not not disabled.
+ * Sync is not disabled.
*
* @hide
*/
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
index 024660bde6fe..a59f026c4182 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletClientImpl.java
@@ -45,9 +45,9 @@ import android.util.Log;
import com.android.internal.widget.LockPatternUtils;
import java.io.IOException;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
@@ -85,7 +85,7 @@ public class QuickAccessWalletClientImpl implements QuickAccessWalletClient, Ser
mServiceInfo = QuickAccessWalletServiceInfo.tryCreate(context);
mHandler = new Handler(Looper.getMainLooper());
mLifecycleExecutor = (bgExecutor == null) ? Runnable::run : bgExecutor;
- mRequestQueue = new LinkedList<>();
+ mRequestQueue = new ArrayDeque<>();
mEventListeners = new HashMap<>(1);
}
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/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index bbfe18cbfa44..f1ce9c3aa04b 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -57,6 +57,17 @@ public class FeatureFlagUtils {
*/
public static final String SETTINGS_APP_LANGUAGE_SELECTION = "settings_app_language_selection";
+
+ /**
+ * Feature flag to allow/restrict intent redirection from/to clone profile.
+ * Default value is false,this is to ensure that framework is not impacted by intent redirection
+ * till we are ready to launch.
+ * From Android U onwards, this would be set to true and eventually removed.
+ * @hide
+ */
+ public static final String SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE =
+ "settings_allow_intent_redirection_for_clone_profile";
+
/**
* Support locale opt-out and opt-in switch for per app's language.
* @hide
@@ -119,6 +130,7 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
DEFAULT_FLAGS.put("settings_search_always_expand", "true");
DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "true");
+ DEFAULT_FLAGS.put(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE, "false");
DEFAULT_FLAGS.put(SETTINGS_APP_LOCALE_OPT_IN_ENABLED, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
@@ -132,6 +144,7 @@ public class FeatureFlagUtils {
static {
PERSISTENT_FLAGS = new HashSet<>();
PERSISTENT_FLAGS.add(SETTINGS_APP_LANGUAGE_SELECTION);
+ PERSISTENT_FLAGS.add(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE);
PERSISTENT_FLAGS.add(SETTINGS_APP_LOCALE_OPT_IN_ENABLED);
PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN);
PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS);
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 7f8f50b7768e..14691b3cc5d8 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -60,7 +60,7 @@ public class HandwritingInitiator {
*/
private final long mHandwritingTimeoutInMillis;
- private final State mState = new State();
+ private State mState;
private final HandwritingAreaTracker mHandwritingAreasTracker = new HandwritingAreaTracker();
/** The reference to the View that currently has the input connection. */
@@ -86,17 +86,27 @@ public class HandwritingInitiator {
/**
* Notify the HandwritingInitiator that a new MotionEvent has arrived.
- * This method is non-block, and the event passed to this method should be dispatched to the
- * View tree as usual. If HandwritingInitiator triggers the handwriting mode, an fabricated
- * ACTION_CANCEL event will be sent to the ViewRootImpl.
- * @param motionEvent the stylus MotionEvent.
+ *
+ * <p>The return value indicates whether the event has been fully handled by the
+ * HandwritingInitiator and should not be dispatched to the view tree. This will be true for
+ * ACTION_MOVE events from a stylus gesture after handwriting mode has been initiated, in order
+ * to suppress other actions such as scrolling.
+ *
+ * <p>If HandwritingInitiator triggers the handwriting mode, a fabricated ACTION_CANCEL event
+ * will be sent to the ViewRootImpl.
+ *
+ * @param motionEvent the stylus {@link MotionEvent}
+ * @return true if the event has been fully handled by the {@link HandwritingInitiator} and
+ * should not be dispatched to the {@link View} tree, or false if the event should be dispatched
+ * to the {@link View} tree as usual
*/
@VisibleForTesting
- public void onTouchEvent(@NonNull MotionEvent motionEvent) {
+ public boolean onTouchEvent(@NonNull MotionEvent motionEvent) {
final int maskedAction = motionEvent.getActionMasked();
switch (maskedAction) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
+ mState = null;
final int actionIndex = motionEvent.getActionIndex();
final int toolType = motionEvent.getToolType(actionIndex);
// TOOL_TYPE_ERASER is also from stylus. This indicates that the user is holding
@@ -104,42 +114,44 @@ public class HandwritingInitiator {
if (toolType != MotionEvent.TOOL_TYPE_STYLUS
&& toolType != MotionEvent.TOOL_TYPE_ERASER) {
// The motion event is not from a stylus event, ignore it.
- return;
+ return false;
+ }
+ if (!mImm.isStylusHandwritingAvailable()) {
+ return false;
}
- mState.mStylusPointerId = motionEvent.getPointerId(actionIndex);
- mState.mStylusDownTimeInMillis = motionEvent.getEventTime();
- mState.mStylusDownX = motionEvent.getX(actionIndex);
- mState.mStylusDownY = motionEvent.getY(actionIndex);
- mState.mStylusDownCandidateView = new WeakReference<>(
- findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY));
- mState.mShouldInitHandwriting = true;
- mState.mExceedHandwritingSlop = false;
+ mState = new State(motionEvent);
break;
case MotionEvent.ACTION_POINTER_UP:
final int pointerId = motionEvent.getPointerId(motionEvent.getActionIndex());
- if (pointerId != mState.mStylusPointerId) {
+ if (mState == null || pointerId != mState.mStylusPointerId) {
// ACTION_POINTER_UP is from another stylus pointer, ignore the event.
- return;
+ return false;
}
// Deliberately fall through.
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// If it's ACTION_CANCEL or ACTION_UP, all the pointers go up. There is no need to
// check whether the stylus we are tracking goes up.
- mState.mShouldInitHandwriting = false;
- break;
+ if (mState != null) {
+ mState.mShouldInitHandwriting = false;
+ }
+ return false;
case MotionEvent.ACTION_MOVE:
+ if (mState == null) {
+ return false;
+ }
+
// Either we've already tried to initiate handwriting, or the ongoing MotionEvent
// sequence is considered to be tap, long-click or other gestures.
if (!mState.mShouldInitHandwriting || mState.mExceedHandwritingSlop) {
- return;
+ return mState.mHasInitiatedHandwriting;
}
final long timeElapsed =
motionEvent.getEventTime() - mState.mStylusDownTimeInMillis;
if (timeElapsed > mHandwritingTimeoutInMillis) {
mState.mShouldInitHandwriting = false;
- return;
+ return mState.mHasInitiatedHandwriting;
}
final int pointerIndex = motionEvent.findPointerIndex(mState.mStylusPointerId);
@@ -147,13 +159,8 @@ public class HandwritingInitiator {
final float y = motionEvent.getY(pointerIndex);
if (largerThanTouchSlop(x, y, mState.mStylusDownX, mState.mStylusDownY)) {
mState.mExceedHandwritingSlop = true;
- View candidateView = mState.mStylusDownCandidateView.get();
- if (candidateView == null || !candidateView.isAttachedToWindow()) {
- // If there was no candidate view found in the stylus down event, or if that
- // candidate view is no longer attached, search again for a candidate view.
- candidateView = findBestCandidateView(mState.mStylusDownX,
- mState.mStylusDownY);
- }
+ View candidateView =
+ findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY);
if (candidateView != null) {
if (candidateView == getConnectedView()) {
startHandwriting(candidateView);
@@ -162,7 +169,9 @@ public class HandwritingInitiator {
}
}
}
+ return mState.mHasInitiatedHandwriting;
}
+ return false;
}
@Nullable
@@ -195,7 +204,7 @@ public class HandwritingInitiator {
} else {
mConnectedView = new WeakReference<>(view);
mConnectionCount = 1;
- if (mState.mShouldInitHandwriting) {
+ if (mState != null && mState.mShouldInitHandwriting) {
tryStartHandwriting();
}
}
@@ -259,6 +268,7 @@ public class HandwritingInitiator {
@VisibleForTesting
public void startHandwriting(@NonNull View view) {
mImm.startStylusHandwriting(view);
+ mState.mHasInitiatedHandwriting = true;
mState.mShouldInitHandwriting = false;
}
@@ -438,28 +448,38 @@ public class HandwritingInitiator {
* b) If the MotionEvent sequence is considered to be tap, long-click or other gestures.
* This boolean will be set to false, and it won't request to start handwriting.
*/
- private boolean mShouldInitHandwriting = false;
+ private boolean mShouldInitHandwriting;
+ /**
+ * Whether handwriting mode has already been initiated for the current MotionEvent sequence.
+ */
+ private boolean mHasInitiatedHandwriting;
/**
* Whether the current ongoing stylus MotionEvent sequence already exceeds the
* handwriting slop.
* It's used for the case where the stylus exceeds handwriting slop before the target View
* built InputConnection.
*/
- private boolean mExceedHandwritingSlop = false;
+ private boolean mExceedHandwritingSlop;
/** The pointer id of the stylus pointer that is being tracked. */
- private int mStylusPointerId = -1;
+ private final int mStylusPointerId;
/** The time stamp when the stylus pointer goes down. */
- private long mStylusDownTimeInMillis = -1;
+ private final long mStylusDownTimeInMillis;
/** The initial location where the stylus pointer goes down. */
- private float mStylusDownX = Float.NaN;
- private float mStylusDownY = Float.NaN;
- /**
- * The best candidate view to initialize handwriting mode based on the initial location
- * where the stylus pointer goes down, or null if the location was not within any candidate
- * view's handwriting area.
- */
- private WeakReference<View> mStylusDownCandidateView = new WeakReference<>(null);
+ private final float mStylusDownX;
+ private final float mStylusDownY;
+
+ private State(MotionEvent motionEvent) {
+ final int actionIndex = motionEvent.getActionIndex();
+ mStylusPointerId = motionEvent.getPointerId(actionIndex);
+ mStylusDownTimeInMillis = motionEvent.getEventTime();
+ mStylusDownX = motionEvent.getX(actionIndex);
+ mStylusDownY = motionEvent.getY(actionIndex);
+
+ mShouldInitHandwriting = true;
+ mHasInitiatedHandwriting = false;
+ mExceedHandwritingSlop = false;
+ }
}
/** The helper method to check if the given view is still active for handwriting. */
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/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index acdff4fb52fd..58a041aec894 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -749,6 +749,11 @@ interface IWindowManager
void setLayerTracingFlags(int flags);
/**
+ * Toggle active SurfaceFlinger transaction tracing.
+ */
+ void setActiveTransactionTracing(boolean active);
+
+ /**
* Forwards a scroll capture request to the appropriate window, if available.
*
* @param displayId the id of the display to target
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/TEST_MAPPING b/core/java/android/view/TEST_MAPPING
index 50d69f78688a..ecb98f9ce801 100644
--- a/core/java/android/view/TEST_MAPPING
+++ b/core/java/android/view/TEST_MAPPING
@@ -10,6 +10,9 @@
"include-annotation": "android.platform.test.annotations.Presubmit"
},
{
+ "exclude-annotation": "android.platform.test.annotations.LargeTest"
+ },
+ {
"exclude-annotation": "androidx.test.filters.FlakyTest"
},
{
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0b742e7f2eff..bc7da13b66db 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)) {
@@ -1378,14 +1387,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.
*/
@@ -1743,16 +1744,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;
}
@@ -1770,6 +1775,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);
@@ -2703,6 +2711,9 @@ public final class ViewRootImpl implements ViewParent,
mIsInTraversal = true;
mWillDrawSoon = true;
+ boolean cancelDraw = false;
+ boolean isSyncRequest = false;
+
boolean windowSizeMayChange = false;
WindowManager.LayoutParams lp = mWindowAttributes;
@@ -2972,6 +2983,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;
@@ -2980,6 +2993,7 @@ public final class ViewRootImpl implements ViewParent,
}
reportNextDraw();
mSyncBuffer = true;
+ isSyncRequest = true;
}
final boolean surfaceControlChanged =
@@ -3268,6 +3282,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
@@ -3486,7 +3513,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();
}
@@ -6554,11 +6583,13 @@ public final class ViewRootImpl implements ViewParent,
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
- mHandwritingInitiator.onTouchEvent(event);
+ boolean handled = mHandwritingInitiator.onTouchEvent(event);
mAttachInfo.mUnbufferedDispatchRequested = false;
mAttachInfo.mHandlingPointerEvent = true;
- boolean handled = mView.dispatchPointerEvent(event);
+ // If the event was fully handled by the handwriting initiator, then don't dispatch it
+ // to the view tree.
+ handled = handled || mView.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
maybeUpdateTooltip(event);
mAttachInfo.mHandlingPointerEvent = false;
@@ -8011,69 +8042,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);
@@ -8122,10 +8104,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/WindowManager.java b/core/java/android/view/WindowManager.java
index 792eedec388e..13ea7af7a623 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2598,6 +2598,7 @@ public interface WindowManager extends ViewManager {
PRIVATE_FLAG_SYSTEM_ERROR,
PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
+ PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
@@ -2659,6 +2660,10 @@ public interface WindowManager extends ViewManager {
equals = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
name = "FORCE_STATUS_BAR_VISIBLE"),
@ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
+ equals = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
+ name = "LAYOUT_SIZE_EXTENDED_BY_CUTOUT"),
+ @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
name = "FORCE_DECOR_VIEW_VISIBILITY"),
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 85a5762f7f3d..25445abefca2 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -83,6 +83,11 @@ public final class WindowManagerGlobal {
public static final int RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS = 1 << 3;
/**
+ * The window manager has told the window it cannot draw this frame and should retry again.
+ */
+ public static final int RELAYOUT_RES_CANCEL_AND_REDRAW = 1 << 4;
+
+ /**
* Flag for relayout: the client will be later giving
* internal insets; as a result, the window will not impact other window
* layouts until the insets are given.
diff --git a/core/java/android/view/WindowlessWindowLayout.java b/core/java/android/view/WindowlessWindowLayout.java
index 7cc50c579d0d..5bec5b6b6722 100644
--- a/core/java/android/view/WindowlessWindowLayout.java
+++ b/core/java/android/view/WindowlessWindowLayout.java
@@ -30,10 +30,10 @@ public class WindowlessWindowLayout extends WindowLayout {
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities,
- Rect attachedWindowFrame, float compatScale, ClientWindowFrames outFrames) {
- outFrames.frame.set(0, 0, attrs.width, attrs.height);
- outFrames.displayFrame.set(outFrames.frame);
- outFrames.parentFrame.set(outFrames.frame);
+ float compatScale, ClientWindowFrames frames) {
+ frames.frame.set(0, 0, attrs.width, attrs.height);
+ frames.displayFrame.set(frames.frame);
+ frames.parentFrame.set(frames.frame);
}
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index a212348a2315..94da2741f71a 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -149,7 +149,7 @@ public class WindowlessWindowManager implements IWindowSession {
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
.setFormat(attrs.format)
.setBLASTLayer()
@@ -181,6 +181,7 @@ public class WindowlessWindowManager implements IWindowSession {
synchronized (this) {
mStateForWindow.put(window.asBinder(), state);
}
+ outAttachedFrame.set(0, 0, -1, -1);
final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
WindowManagerGlobal.ADD_FLAG_USE_BLAST;
@@ -196,15 +197,15 @@ public class WindowlessWindowManager implements IWindowSession {
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibilities,
- outInputChannel, outInsetsState, outActiveControls);
+ outInputChannel, outInsetsState, outActiveControls, outAttachedFrame);
}
@Override
public int addToDisplayWithoutInputChannel(android.view.IWindow window,
android.view.WindowManager.LayoutParams attrs, int viewVisibility, int layerStackId,
- android.view.InsetsState insetsState) {
+ android.view.InsetsState insetsState, Rect outAttachedFrame) {
return 0;
}
@@ -337,21 +338,6 @@ public class WindowlessWindowManager implements IWindowSession {
}
@Override
- public int updateVisibility(IWindow window, WindowManager.LayoutParams inAttrs,
- int viewVisibility, MergedConfiguration outMergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
- // TODO(b/161810301): Finish the implementation.
- return 0;
- }
-
- @Override
- public void updateLayout(IWindow window, WindowManager.LayoutParams inAttrs, int flags,
- ClientWindowFrames clientWindowFrames, int requestedWidth, int requestedHeight) {
- // TODO(b/161810301): Finish the implementation.
- }
-
- @Override
public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) {
}
@@ -552,4 +538,9 @@ public class WindowlessWindowManager implements IWindowSession {
}
}
}
+
+ @Override
+ public boolean cancelDraw(IWindow window) {
+ return false;
+ }
}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 827c3ceb07b3..54ff11ccac9d 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -182,13 +182,13 @@ public interface InputMethod {
* @param inputConnection Optional specific input connection for
* communicating with the text box; if null, you should use the generic
* bound input connection.
- * @param info Information about the text box (typically, an EditText)
+ * @param editorInfo Information about the text box (typically, an EditText)
* that requests input.
*
* @see EditorInfo
*/
@MainThread
- public void startInput(InputConnection inputConnection, EditorInfo info);
+ public void startInput(InputConnection inputConnection, EditorInfo editorInfo);
/**
* This method is called when the state of this input method needs to be
@@ -201,13 +201,13 @@ public interface InputMethod {
* @param inputConnection Optional specific input connection for
* communicating with the text box; if null, you should use the generic
* bound input connection.
- * @param attribute The attribute of the text box (typically, a EditText)
+ * @param editorInfo The attribute of the text box (typically, a EditText)
* that requests input.
*
* @see EditorInfo
*/
@MainThread
- public void restartInput(InputConnection inputConnection, EditorInfo attribute);
+ public void restartInput(InputConnection inputConnection, EditorInfo editorInfo);
/**
* This method is called when {@code {@link #startInput(InputConnection, EditorInfo)} or
@@ -233,7 +233,7 @@ public interface InputMethod {
* long as your implementation of {@link InputMethod} relies on such
* IPCs
* @param navButtonFlags {@link InputMethodNavButtonFlags} in the initial state of this session.
- * @param imeDispatcher The {@link ImeOnBackInvokedDispatcher }} to be set on the
+ * @param imeDispatcher The {@link ImeOnBackInvokedDispatcher} to be set on the
* IME's {@link android.window.WindowOnBackInvokedDispatcher}, so that IME
* {@link android.window.OnBackInvokedCallback}s can be forwarded to
* the client requesting to start input.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 73d6241c21d1..50f3e0cfbcf3 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -446,7 +446,7 @@ public final class InputMethodManager {
* the attributes that were last retrieved from the served view and given
* to the input connection.
*/
- EditorInfo mCurrentTextBoxAttribute;
+ EditorInfo mCurrentEditorInfo;
/**
* The InputConnection that was last retrieved from the served view.
*/
@@ -541,7 +541,9 @@ public final class InputMethodManager {
/**
* The monitor mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}.
+ * @deprecated This is kept for {@link UnsupportedAppUsage}. Must not be used.
*/
+ @Deprecated
private int mRequestUpdateCursorAnchorInfoMonitorMode = REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;
/**
@@ -652,15 +654,13 @@ public final class InputMethodManager {
public boolean startInput(@StartInputReason int startInputReason, View focusedView,
@StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
int windowFlags) {
- final View servedView;
ImeTracing.getInstance().triggerClientDump(
"InputMethodManager.DelegateImpl#startInput", InputMethodManager.this,
null /* icProto */);
synchronized (mH) {
- mCurrentTextBoxAttribute = null;
+ mCurrentEditorInfo = null;
mCompletions = null;
mServedConnecting = true;
- servedView = getServedViewLocked();
}
return startInputInner(startInputReason,
focusedView != null ? focusedView.getWindowToken() : null, startInputFlags,
@@ -1131,9 +1131,7 @@ public final class InputMethodManager {
|| mServedInputConnection == null) {
return;
}
- final boolean isMonitoring = (mRequestUpdateCursorAnchorInfoMonitorMode
- & InputConnection.CURSOR_UPDATE_MONITOR) != 0;
- if (!isMonitoring) {
+ if (!mServedInputConnection.isCursorAnchorInfoMonitoring()) {
return;
}
// Since the host VirtualDisplay is moved, we need to issue
@@ -1600,7 +1598,7 @@ public final class InputMethodManager {
checkFocus();
synchronized (mH) {
- return hasServedByInputMethodLocked(view) && mCurrentTextBoxAttribute != null;
+ return hasServedByInputMethodLocked(view) && mCurrentEditorInfo != null;
}
}
@@ -1610,7 +1608,7 @@ public final class InputMethodManager {
public boolean isActive() {
checkFocus();
synchronized (mH) {
- return getServedViewLocked() != null && mCurrentTextBoxAttribute != null;
+ return getServedViewLocked() != null && mCurrentEditorInfo != null;
}
}
@@ -1694,7 +1692,7 @@ public final class InputMethodManager {
* to an input method
*/
void clearConnectionLocked() {
- mCurrentTextBoxAttribute = null;
+ mCurrentEditorInfo = null;
if (mServedInputConnection != null) {
mServedInputConnection.deactivate();
mServedInputConnection = null;
@@ -2192,7 +2190,7 @@ public final class InputMethodManager {
public boolean doInvalidateInput(@NonNull RemoteInputConnectionImpl inputConnection,
@NonNull TextSnapshot textSnapshot, int sessionId) {
synchronized (mH) {
- if (mServedInputConnection != inputConnection || mCurrentTextBoxAttribute == null) {
+ if (mServedInputConnection != inputConnection || mCurrentEditorInfo == null) {
// OK to ignore because the calling InputConnection is already abandoned.
return true;
}
@@ -2200,7 +2198,7 @@ public final class InputMethodManager {
// IME is not yet bound to the client. Need to fall back to the restartInput().
return false;
}
- final EditorInfo editorInfo = mCurrentTextBoxAttribute.createCopyInternal();
+ final EditorInfo editorInfo = mCurrentEditorInfo.createCopyInternal();
editorInfo.initialSelStart = mCursorSelStart = textSnapshot.getSelectionStart();
editorInfo.initialSelEnd = mCursorSelEnd = textSnapshot.getSelectionEnd();
mCursorCandStart = textSnapshot.getCompositionStart();
@@ -2339,7 +2337,7 @@ public final class InputMethodManager {
// This is not an error. Once IME binds (MSG_BIND), InputConnection is fully
// established. So we report this to interested recipients.
reportInputConnectionOpened(
- mServedInputConnection.getInputConnection(), mCurrentTextBoxAttribute,
+ mServedInputConnection.getInputConnection(), mCurrentEditorInfo,
mServedInputConnectionHandler, view);
}
return false;
@@ -2347,12 +2345,12 @@ public final class InputMethodManager {
// If we already have a text box, then this view is already
// connected so we want to restart it.
- if (mCurrentTextBoxAttribute == null) {
+ if (mCurrentEditorInfo == null) {
startInputFlags |= StartInputFlags.INITIAL_CONNECTION;
}
// Hook 'em up and let 'er rip.
- mCurrentTextBoxAttribute = tba.createCopyInternal();
+ mCurrentEditorInfo = tba.createCopyInternal();
mServedConnecting = false;
if (mServedInputConnection != null) {
@@ -2658,7 +2656,7 @@ public final class InputMethodManager {
checkFocus();
synchronized (mH) {
- if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
|| mCurrentInputMethodSession == null) {
return;
}
@@ -2675,19 +2673,14 @@ public final class InputMethodManager {
if (DEBUG) {
Log.v(TAG, "SELECTION CHANGE: " + mCurrentInputMethodSession);
}
- final int oldSelStart = mCursorSelStart;
- final int oldSelEnd = mCursorSelEnd;
- // Update internal values before sending updateSelection to the IME, because
- // if it changes the text within its onUpdateSelection handler in a way that
- // does not move the cursor we don't want to call it again with the same values.
+ mCurrentInputMethodSession.updateSelection(mCursorSelStart, mCursorSelEnd, selStart,
+ selEnd, candidatesStart, candidatesEnd);
+ forAccessibilitySessionsLocked(wrapper -> wrapper.updateSelection(mCursorSelStart,
+ mCursorSelEnd, selStart, selEnd, candidatesStart, candidatesEnd));
mCursorSelStart = selStart;
mCursorSelEnd = selEnd;
mCursorCandStart = candidatesStart;
mCursorCandEnd = candidatesEnd;
- mCurrentInputMethodSession.updateSelection(
- oldSelStart, oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd);
- forAccessibilitySessionsLocked(wrapper -> wrapper.updateSelection(oldSelStart,
- oldSelEnd, selStart, selEnd, candidatesStart, candidatesEnd));
}
}
}
@@ -2720,7 +2713,7 @@ public final class InputMethodManager {
final boolean focusChanged = servedView != nextServedView;
checkFocus();
synchronized (mH) {
- if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
|| mCurrentInputMethodSession == null) {
return;
}
@@ -2744,8 +2737,10 @@ public final class InputMethodManager {
* Return true if the current input method wants to be notified when cursor/anchor location
* is changed.
*
+ * @deprecated This method is kept for {@link UnsupportedAppUsage}. Must not be used.
* @hide
*/
+ @Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean isCursorAnchorInfoEnabled() {
synchronized (mH) {
@@ -2760,8 +2755,10 @@ public final class InputMethodManager {
/**
* Set the requested mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}.
*
+ * @deprecated This method is kept for {@link UnsupportedAppUsage}. Must not be used.
* @hide
*/
+ @Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void setUpdateCursorAnchorInfoMode(int flags) {
synchronized (mH) {
@@ -2770,17 +2767,6 @@ public final class InputMethodManager {
}
/**
- * Get the requested mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}.
- *
- * @hide
- */
- public int getUpdateCursorAnchorInfoMode() {
- synchronized (mH) {
- return mRequestUpdateCursorAnchorInfoMonitorMode;
- }
- }
-
- /**
* Report the current cursor location in its window.
*
* @deprecated Use {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} instead.
@@ -2796,7 +2782,7 @@ public final class InputMethodManager {
checkFocus();
synchronized (mH) {
- if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
|| mCurrentInputMethodSession == null) {
return;
}
@@ -2828,14 +2814,14 @@ public final class InputMethodManager {
checkFocus();
synchronized (mH) {
- if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
|| mCurrentInputMethodSession == null) {
return;
}
// If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
// not been changed from the previous call.
- final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode &
- CURSOR_UPDATE_IMMEDIATE) != 0;
+ final boolean isImmediate = mServedInputConnection != null
+ && mServedInputConnection.resetHasPendingImmediateCursorAnchorInfoUpdate();
if (!isImmediate && Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) {
// TODO: Consider always emitting this message once we have addressed redundant
// calls of this method from android.widget.Editor.
@@ -2854,8 +2840,6 @@ public final class InputMethodManager {
mCurrentInputMethodSession.updateCursorAnchorInfo(cursorAnchorInfo);
}
mCursorAnchorInfo = cursorAnchorInfo;
- // Clear immediate bit (if any).
- mRequestUpdateCursorAnchorInfoMonitorMode &= ~CURSOR_UPDATE_IMMEDIATE;
}
}
@@ -2880,7 +2864,7 @@ public final class InputMethodManager {
checkFocus();
synchronized (mH) {
- if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
|| mCurrentInputMethodSession == null) {
return;
}
@@ -3606,11 +3590,11 @@ public final class InputMethodManager {
p.println(" mServedView=" + getServedViewLocked());
p.println(" mNextServedView=" + getNextServedViewLocked());
p.println(" mServedConnecting=" + mServedConnecting);
- if (mCurrentTextBoxAttribute != null) {
- p.println(" mCurrentTextBoxAttribute:");
- mCurrentTextBoxAttribute.dump(p, " ", false /* dumpExtras */);
+ if (mCurrentEditorInfo != null) {
+ p.println(" mCurrentEditorInfo:");
+ mCurrentEditorInfo.dump(p, " ", false /* dumpExtras */);
} else {
- p.println(" mCurrentTextBoxAttribute: null");
+ p.println(" mCurrentEditorInfo: null");
}
p.println(" mServedInputConnection=" + mServedInputConnection);
p.println(" mServedInputConnectionHandler=" + mServedInputConnectionHandler);
@@ -3733,8 +3717,8 @@ public final class InputMethodManager {
if (mCurRootView != null) {
mCurRootView.dumpDebug(proto, VIEW_ROOT_IMPL);
}
- if (mCurrentTextBoxAttribute != null) {
- mCurrentTextBoxAttribute.dumpDebug(proto, EDITOR_INFO);
+ if (mCurrentEditorInfo != null) {
+ mCurrentEditorInfo.dumpDebug(proto, EDITOR_INFO);
}
if (mImeInsetsConsumer != null) {
mImeInsetsConsumer.dumpDebug(proto, IME_INSETS_SOURCE_CONSUMER);
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index bb4b1c868bb9..fb298c75c909 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -4599,20 +4599,22 @@ public class Editor {
return;
}
// Skip if the IME has not requested the cursor/anchor position.
- if (!imm.isCursorAnchorInfoEnabled()) {
+ final int knownCursorAnchorInfoModes =
+ InputConnection.CURSOR_UPDATE_IMMEDIATE | InputConnection.CURSOR_UPDATE_MONITOR;
+ if ((mInputMethodState.mUpdateCursorAnchorInfoMode & knownCursorAnchorInfoModes) == 0) {
return;
}
Layout layout = mTextView.getLayout();
if (layout == null) {
return;
}
- int mode = imm.getUpdateCursorAnchorInfoMode();
+ final int filter = mInputMethodState.mUpdateCursorAnchorInfoFilter;
boolean includeEditorBounds =
- (mode & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0;
+ (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0;
boolean includeCharacterBounds =
- (mode & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0;
+ (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0;
boolean includeInsertionMarker =
- (mode & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0;
+ (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0;
boolean includeAll =
(!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker);
@@ -4713,6 +4715,10 @@ public class Editor {
}
imm.updateCursorAnchorInfo(mTextView, builder.build());
+
+ // Drop the immediate flag if any.
+ mInputMethodState.mUpdateCursorAnchorInfoMode &=
+ ~InputConnection.CURSOR_UPDATE_IMMEDIATE;
}
}
@@ -7203,6 +7209,10 @@ public class Editor {
boolean mSelectionModeChanged;
boolean mContentChanged;
int mChangedStart, mChangedEnd, mChangedDelta;
+ @InputConnection.CursorUpdateMode
+ int mUpdateCursorAnchorInfoMode;
+ @InputConnection.CursorUpdateFilter
+ int mUpdateCursorAnchorInfoFilter;
}
/**
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index cd072b0cfb31..83d77172031e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9005,6 +9005,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
if (onCheckIsTextEditor() && isEnabled()) {
mEditor.createInputMethodStateIfNeeded();
+ mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = 0;
+ mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = 0;
+
outAttrs.inputType = getInputType();
if (mEditor.mInputContentType != null) {
outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
@@ -9061,6 +9064,33 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Called back by the system to handle {@link InputConnection#requestCursorUpdates(int, int)}.
+ *
+ * @param cursorUpdateMode modes defined in {@link InputConnection.CursorUpdateMode}.
+ * @param cursorUpdateFilter modes defined in {@link InputConnection.CursorUpdateFilter}.
+ *
+ * @hide
+ */
+ public void onRequestCursorUpdatesInternal(
+ @InputConnection.CursorUpdateMode int cursorUpdateMode,
+ @InputConnection.CursorUpdateFilter int cursorUpdateFilter) {
+ mEditor.mInputMethodState.mUpdateCursorAnchorInfoMode = cursorUpdateMode;
+ mEditor.mInputMethodState.mUpdateCursorAnchorInfoFilter = cursorUpdateFilter;
+ if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) == 0) {
+ return;
+ }
+ if (isInLayout()) {
+ // In this case, the view hierarchy is currently undergoing a layout pass.
+ // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout
+ // pass is finished.
+ } else {
+ // This will schedule a layout pass of the view tree, and the layout event
+ // eventually triggers IMM#updateCursorAnchorInfo.
+ requestLayout();
+ }
+ }
+
+ /**
* If this TextView contains editable content, extract a portion of it
* based on the information in <var>request</var> in to <var>outText</var>.
* @return Returns true if the text was successfully extracted, else 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/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
index 874e3f4ae26a..50afb3ee0a03 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityShortcutChooserActivity.java
@@ -28,6 +28,7 @@ import static com.android.internal.accessibility.util.AccessibilityUtils.isUserS
import android.annotation.Nullable;
import android.app.Activity;
import android.app.AlertDialog;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.os.Bundle;
@@ -102,16 +103,7 @@ public class AccessibilityShortcutChooserActivity extends Activity {
final AccessibilityTarget target = mTargets.get(position);
if ((target instanceof AccessibilityServiceTarget) && !target.isShortcutEnabled()) {
- mPermissionDialog = new AlertDialog.Builder(this)
- .setView(createEnableDialogContentView(this,
- (AccessibilityServiceTarget) target,
- v -> {
- mPermissionDialog.dismiss();
- mTargetAdapter.notifyDataSetChanged();
- },
- v -> mPermissionDialog.dismiss()))
- .create();
- mPermissionDialog.show();
+ showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target, mTargetAdapter);
return;
}
@@ -119,6 +111,24 @@ public class AccessibilityShortcutChooserActivity extends Activity {
mTargetAdapter.notifyDataSetChanged();
}
+ private void showPermissionDialogIfNeeded(Context context,
+ AccessibilityServiceTarget serviceTarget, ShortcutTargetAdapter targetAdapter) {
+ if (mPermissionDialog != null) {
+ return;
+ }
+
+ mPermissionDialog = new AlertDialog.Builder(context)
+ .setView(createEnableDialogContentView(context, serviceTarget,
+ v -> {
+ mPermissionDialog.dismiss();
+ targetAdapter.notifyDataSetChanged();
+ },
+ v -> mPermissionDialog.dismiss()))
+ .setOnDismissListener(dialog -> mPermissionDialog = null)
+ .create();
+ mPermissionDialog.show();
+ }
+
private void onDoneButtonClicked() {
mTargets.clear();
mTargets.addAll(getTargets(this, mShortcutType));
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index f8b268fbc1f2..be7501fea7d0 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -215,13 +215,15 @@ public final class EditableInputConnection extends BaseInputConnection
public boolean requestCursorUpdates(int cursorUpdateMode) {
if (DEBUG) Log.v(TAG, "requestUpdateCursorAnchorInfo " + cursorUpdateMode);
- // It is possible that any other bit is used as a valid flag in a future release.
- // We should reject the entire request in such a case.
- final int knownFlagMask = InputConnection.CURSOR_UPDATE_IMMEDIATE
- | InputConnection.CURSOR_UPDATE_MONITOR
- | InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS
+ final int knownModeFlags = InputConnection.CURSOR_UPDATE_IMMEDIATE
+ | InputConnection.CURSOR_UPDATE_MONITOR;
+ final int knownFilterFlags = InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS
| InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER
| InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS;
+
+ // It is possible that any other bit is used as a valid flag in a future release.
+ // We should reject the entire request in such a case.
+ final int knownFlagMask = knownModeFlags | knownFilterFlags;
final int unknownFlags = cursorUpdateMode & ~knownFlagMask;
if (unknownFlags != 0) {
if (DEBUG) {
@@ -237,21 +239,10 @@ public final class EditableInputConnection extends BaseInputConnection
// CursorAnchorInfo is temporarily unavailable.
return false;
}
- mIMM.setUpdateCursorAnchorInfoMode(cursorUpdateMode);
- if ((cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0) {
- if (mTextView == null) {
- // In this case, FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is silently ignored.
- // TODO: Return some notification code for the input method that indicates
- // FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE is ignored.
- } else if (mTextView.isInLayout()) {
- // In this case, the view hierarchy is currently undergoing a layout pass.
- // IMM#updateCursorAnchorInfo is supposed to be called soon after the layout
- // pass is finished.
- } else {
- // This will schedule a layout pass of the view tree, and the layout event
- // eventually triggers IMM#updateCursorAnchorInfo.
- mTextView.requestLayout();
- }
+ mIMM.setUpdateCursorAnchorInfoMode(cursorUpdateMode); // for UnsupportedAppUsage
+ if (mTextView != null) {
+ mTextView.onRequestCursorUpdatesInternal(cursorUpdateMode & knownModeFlags,
+ cursorUpdateMode & knownFilterFlags);
}
return true;
}
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index 49eafd017687..b8196157ffd9 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -46,7 +46,7 @@ oneway interface IInputMethod {
void unbindInput();
void startInput(in IBinder startInputToken, in IRemoteInputConnection inputConnection,
- in EditorInfo attribute, boolean restarting, int navigationBarFlags,
+ in EditorInfo editorInfo, boolean restarting, int navigationBarFlags,
in ImeOnBackInvokedDispatcher imeDispatcher);
void onNavButtonFlagsChanged(int navButtonFlags);
diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
index cb18ccc9788a..2bef10f1aee5 100644
--- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java
@@ -176,6 +176,10 @@ public final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub
private final AtomicInteger mCurrentSessionId = new AtomicInteger(0);
private final AtomicBoolean mHasPendingInvalidation = new AtomicBoolean();
+ private final AtomicBoolean mIsCursorAnchorInfoMonitoring = new AtomicBoolean(false);
+ private final AtomicBoolean mHasPendingImmediateCursorAnchorInfoUpdate =
+ new AtomicBoolean(false);
+
public RemoteInputConnectionImpl(@NonNull Looper looper,
@NonNull InputConnection inputConnection,
@NonNull InputMethodManager inputMethodManager, @Nullable View servedView) {
@@ -223,6 +227,33 @@ public final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub
}
/**
+ * Gets and resets {@link #mHasPendingImmediateCursorAnchorInfoUpdate}.
+ *
+ * <p>Calling this method resets {@link #mHasPendingImmediateCursorAnchorInfoUpdate}. This
+ * means that the second call of this method returns {@code false} unless the IME requests
+ * {@link android.view.inputmethod.CursorAnchorInfo} again with
+ * {@link InputConnection#CURSOR_UPDATE_IMMEDIATE} flag.</p>
+ *
+ * @return {@code true} if there is any pending request for
+ * {@link android.view.inputmethod.CursorAnchorInfo} with
+ * {@link InputConnection#CURSOR_UPDATE_IMMEDIATE} flag.
+ */
+ @AnyThread
+ public boolean resetHasPendingImmediateCursorAnchorInfoUpdate() {
+ return mHasPendingImmediateCursorAnchorInfoUpdate.getAndSet(false);
+ }
+
+ /**
+ * @return {@code true} if there is any active request for
+ * {@link android.view.inputmethod.CursorAnchorInfo} with
+ * {@link InputConnection#CURSOR_UPDATE_MONITOR} flag.
+ */
+ @AnyThread
+ public boolean isCursorAnchorInfoMonitoring() {
+ return mIsCursorAnchorInfoMonitoring.get();
+ }
+
+ /**
* Schedule a task to execute
* {@link InputMethodManager#doInvalidateInput(RemoteInputConnectionImpl, TextSnapshot, int)}
* on the associated Handler if not yet scheduled.
@@ -998,11 +1029,20 @@ public final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub
// requestCursorUpdates() is not currently supported across displays.
return false;
}
+ final boolean hasImmediate =
+ (cursorUpdateMode & InputConnection.CURSOR_UPDATE_IMMEDIATE) != 0;
+ final boolean hasMonitoring =
+ (cursorUpdateMode & InputConnection.CURSOR_UPDATE_MONITOR) != 0;
+ boolean result = false;
try {
- return ic.requestCursorUpdates(cursorUpdateMode, cursorUpdateFilter);
+ result = ic.requestCursorUpdates(cursorUpdateMode, cursorUpdateFilter);
+ return result;
} catch (AbstractMethodError ignored) {
// TODO(b/199934664): See if we can remove this by providing a default impl.
return false;
+ } finally {
+ mHasPendingImmediateCursorAnchorInfoUpdate.set(result && hasImmediate);
+ mIsCursorAnchorInfoMonitoring.set(result && hasMonitoring);
}
}
diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING
index 5a5165ddd7e2..60b160ad18c2 100644
--- a/core/java/com/android/internal/os/TEST_MAPPING
+++ b/core/java/com/android/internal/os/TEST_MAPPING
@@ -39,8 +39,7 @@
"name": "FrameworksServicesTests",
"options": [
{ "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
- { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" },
- { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" }
+ { "include-filter": "com.android.server.power.stats.BatteryStatsTests" }
]
},
{
@@ -75,13 +74,11 @@
"name": "FrameworksCoreTests",
"options": [
{
- "include-filter": "com.android.internal.os.BstatsCpuTimesValidationTest"
+ "include-filter": "com.android.server.power.stats.BstatsCpuTimesValidationTest"
}
],
"file_patterns": [
- "BatteryStatsImpl\\.java",
- "KernelCpuUidFreqTimeReader\\.java",
- "KernelSingleUidTimeReader\\.java"
+ "Kernel[^/]*\\.java"
]
}
]
diff --git a/core/java/com/android/internal/os/ZygoteConfig.java b/core/java/com/android/internal/os/ZygoteConfig.java
index 6ebcae182b11..e5dc874d1f90 100644
--- a/core/java/com/android/internal/os/ZygoteConfig.java
+++ b/core/java/com/android/internal/os/ZygoteConfig.java
@@ -16,6 +16,9 @@
package com.android.internal.os;
+import android.os.SystemProperties;
+import android.provider.DeviceConfig;
+
/**
* Flag names for configuring the zygote.
*
@@ -26,15 +29,87 @@ public class ZygoteConfig {
/** If {@code true}, enables the unspecialized app process (USAP) pool feature */
public static final String USAP_POOL_ENABLED = "usap_pool_enabled";
+ /**
+ * The default value for enabling the unspecialized app process (USAP) pool. This value will
+ * not be used if the devices has a DeviceConfig profile pushed to it that contains a value for
+ * this key or if the System Property dalvik.vm.usap_pool_enabled is set.
+ */
+ public static final boolean USAP_POOL_ENABLED_DEFAULT = false;
+
+
+
/** The threshold used to determine if the pool should be refilled */
public static final String USAP_POOL_REFILL_THRESHOLD = "usap_refill_threshold";
+ public static final int USAP_POOL_REFILL_THRESHOLD_DEFAULT = 1;
+
+
+
/** The maximum number of processes to keep in the USAP pool */
public static final String USAP_POOL_SIZE_MAX = "usap_pool_size_max";
+ public static final int USAP_POOL_SIZE_MAX_DEFAULT = 3;
+
+ /**
+ * The maximim value that will be accepted from the USAP_POOL_SIZE_MAX device property.
+ * is a mirror of USAP_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp.
+ */
+ public static final int USAP_POOL_SIZE_MAX_LIMIT = 100;
+
+
+
/** The minimum number of processes to keep in the USAP pool */
public static final String USAP_POOL_SIZE_MIN = "usap_pool_size_min";
+ public static final int USAP_POOL_SIZE_MIN_DEFAULT = 1;
+
+ /**
+ * The minimum value that will be accepted from the USAP_POOL_SIZE_MIN device property.
+ */
+ public static final int USAP_POOL_SIZE_MIN_LIMIT = 1;
+
+
+
/** The number of milliseconds to delay before refilling the USAP pool */
public static final String USAP_POOL_REFILL_DELAY_MS = "usap_pool_refill_delay_ms";
+
+ public static final int USAP_POOL_REFILL_DELAY_MS_DEFAULT = 3000;
+
+ public static final String PROPERTY_PREFIX_DEVICE_CONFIG = "persist.device_config";
+ public static final String PROPERTY_PREFIX_SYSTEM = "dalvik.vm.";
+
+ private static String getDeviceConfig(String name) {
+ return SystemProperties.get(
+ String.join(
+ ".",
+ PROPERTY_PREFIX_DEVICE_CONFIG,
+ DeviceConfig.NAMESPACE_RUNTIME_NATIVE,
+ name));
+ }
+
+ /**
+ * Get a property value from SystemProperties and convert it to an integer value.
+ */
+ public static int getInt(String name, int defaultValue) {
+ final String propString = getDeviceConfig(name);
+
+ if (!propString.isEmpty()) {
+ return Integer.parseInt(propString);
+ } else {
+ return SystemProperties.getInt(PROPERTY_PREFIX_SYSTEM + name, defaultValue);
+ }
+ }
+
+ /**
+ * Get a property value from SystemProperties and convert it to a Boolean value.
+ */
+ public static boolean getBool(String name, boolean defaultValue) {
+ final String propString = getDeviceConfig(name);
+
+ if (!propString.isEmpty()) {
+ return Boolean.parseBoolean(propString);
+ } else {
+ return SystemProperties.getBoolean(PROPERTY_PREFIX_SYSTEM + name, defaultValue);
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index 4d2266b2eba5..f8598f2f471a 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -49,26 +49,6 @@ class ZygoteServer {
// TODO (chriswailes): Change this so it is set with Zygote or ZygoteSecondary as appropriate
public static final String TAG = "ZygoteServer";
- /**
- * The maximim value that will be accepted from the USAP_POOL_SIZE_MAX device property.
- * is a mirror of USAP_POOL_MAX_LIMIT found in com_android_internal_os_Zygote.cpp.
- */
- private static final int USAP_POOL_SIZE_MAX_LIMIT = 100;
-
- /**
- * The minimum value that will be accepted from the USAP_POOL_SIZE_MIN device property.
- */
- private static final int USAP_POOL_SIZE_MIN_LIMIT = 1;
-
- /** The default value used for the USAP_POOL_SIZE_MAX device property */
- private static final String USAP_POOL_SIZE_MAX_DEFAULT = "10";
-
- /** The default value used for the USAP_POOL_SIZE_MIN device property */
- private static final String USAP_POOL_SIZE_MIN_DEFAULT = "1";
-
- /** The default value used for the USAP_REFILL_DELAY_MS device property */
- private static final String USAP_POOL_REFILL_DELAY_MS_DEFAULT = "3000";
-
/** The "not a timestamp" value for the refill delay timestamp mechanism. */
private static final int INVALID_TIMESTAMP = -1;
@@ -264,46 +244,35 @@ class ZygoteServer {
private void fetchUsapPoolPolicyProps() {
if (mUsapPoolSupported) {
- final String usapPoolSizeMaxPropString = Zygote.getConfigurationProperty(
- ZygoteConfig.USAP_POOL_SIZE_MAX, USAP_POOL_SIZE_MAX_DEFAULT);
-
- if (!usapPoolSizeMaxPropString.isEmpty()) {
- mUsapPoolSizeMax = Integer.min(Integer.parseInt(
- usapPoolSizeMaxPropString), USAP_POOL_SIZE_MAX_LIMIT);
- }
-
- final String usapPoolSizeMinPropString = Zygote.getConfigurationProperty(
- ZygoteConfig.USAP_POOL_SIZE_MIN, USAP_POOL_SIZE_MIN_DEFAULT);
-
- if (!usapPoolSizeMinPropString.isEmpty()) {
- mUsapPoolSizeMin = Integer.max(
- Integer.parseInt(usapPoolSizeMinPropString), USAP_POOL_SIZE_MIN_LIMIT);
- }
-
- final String usapPoolRefillThresholdPropString = Zygote.getConfigurationProperty(
+ mUsapPoolSizeMax = Integer.min(
+ ZygoteConfig.getInt(
+ ZygoteConfig.USAP_POOL_SIZE_MAX,
+ ZygoteConfig.USAP_POOL_SIZE_MAX_DEFAULT),
+ ZygoteConfig.USAP_POOL_SIZE_MAX_LIMIT);
+
+ mUsapPoolSizeMin = Integer.max(
+ ZygoteConfig.getInt(
+ ZygoteConfig.USAP_POOL_SIZE_MIN,
+ ZygoteConfig.USAP_POOL_SIZE_MIN_DEFAULT),
+ ZygoteConfig.USAP_POOL_SIZE_MIN_LIMIT);
+
+ mUsapPoolRefillThreshold = Integer.min(
+ ZygoteConfig.getInt(
ZygoteConfig.USAP_POOL_REFILL_THRESHOLD,
- Integer.toString(mUsapPoolSizeMax / 2));
+ ZygoteConfig.USAP_POOL_REFILL_THRESHOLD_DEFAULT),
+ mUsapPoolSizeMax);
- if (!usapPoolRefillThresholdPropString.isEmpty()) {
- mUsapPoolRefillThreshold = Integer.min(
- Integer.parseInt(usapPoolRefillThresholdPropString),
- mUsapPoolSizeMax);
- }
-
- final String usapPoolRefillDelayMsPropString = Zygote.getConfigurationProperty(
- ZygoteConfig.USAP_POOL_REFILL_DELAY_MS, USAP_POOL_REFILL_DELAY_MS_DEFAULT);
-
- if (!usapPoolRefillDelayMsPropString.isEmpty()) {
- mUsapPoolRefillDelayMs = Integer.parseInt(usapPoolRefillDelayMsPropString);
- }
+ mUsapPoolRefillDelayMs = ZygoteConfig.getInt(
+ ZygoteConfig.USAP_POOL_REFILL_DELAY_MS,
+ ZygoteConfig.USAP_POOL_REFILL_DELAY_MS_DEFAULT);
// Validity check
if (mUsapPoolSizeMin >= mUsapPoolSizeMax) {
Log.w(TAG, "The max size of the USAP pool must be greater than the minimum size."
+ " Restoring default values.");
- mUsapPoolSizeMax = Integer.parseInt(USAP_POOL_SIZE_MAX_DEFAULT);
- mUsapPoolSizeMin = Integer.parseInt(USAP_POOL_SIZE_MIN_DEFAULT);
+ mUsapPoolSizeMax = ZygoteConfig.USAP_POOL_SIZE_MAX_DEFAULT;
+ mUsapPoolSizeMin = ZygoteConfig.USAP_POOL_SIZE_MIN_DEFAULT;
mUsapPoolRefillThreshold = mUsapPoolSizeMax / 2;
}
}
diff --git a/core/java/com/android/internal/power/TEST_MAPPING b/core/java/com/android/internal/power/TEST_MAPPING
index 96f31bcbe5b2..c6cab183d970 100644
--- a/core/java/com/android/internal/power/TEST_MAPPING
+++ b/core/java/com/android/internal/power/TEST_MAPPING
@@ -11,8 +11,7 @@
"name": "FrameworksServicesTests",
"options": [
{ "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
- { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" },
- { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" }
+ { "include-filter": "com.android.server.power.stats.BatteryStatsTests" }
]
}
]
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 9b19e021cc4f..508e4450d0f9 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -49,14 +49,14 @@ interface IInputMethodManager {
boolean hideSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
in ResultReceiver resultReceiver, int reason);
// If windowToken is null, this just does startInput(). Otherwise this reports that a window
- // has gained focus, and if 'attribute' is non-null then also does startInput.
+ // has gained focus, and if 'editorInfo' is non-null then also does startInput.
// @NonNull
InputBindResult startInputOrWindowGainedFocus(
/* @StartInputReason */ int startInputReason,
in IInputMethodClient client, in IBinder windowToken,
/* @StartInputFlags */ int startInputFlags,
/* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
- int windowFlags, in EditorInfo attribute, in IRemoteInputConnection inputConnection,
+ int windowFlags, in EditorInfo editorInfo, in IRemoteInputConnection inputConnection,
in IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, in ImeOnBackInvokedDispatcher imeDispatcher);
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 61536e8960e2..409ab01bf74b 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -94,6 +94,11 @@ public class ActionMenuItemView extends TextView
updateTextButtonVisibility();
}
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return android.widget.Button.class.getName();
+ }
+
/**
* Whether action menu items should obey the "withText" showAsAction flag. This may be set to
* false for situations where space is extremely limited. -->
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 34fd478baeda..5cb0de324106 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -94,6 +94,10 @@
#include "nativebridge/native_bridge.h"
+#if defined(__BIONIC__)
+extern "C" void android_reset_stack_guards();
+#endif
+
namespace {
// TODO (chriswailes): Add a function to initialize native Zygote data.
@@ -412,6 +416,7 @@ static void sendSigChildStatus(const pid_t pid, const uid_t uid, const int statu
}
// This signal handler is for zygote mode, since the zygote must reap its children
+NO_STACK_PROTECTOR
static void SigChldHandler(int /*signal_number*/, siginfo_t* info, void* /*ucontext*/) {
pid_t pid;
int status;
@@ -2042,6 +2047,7 @@ static std::set<int>* gPreloadFds = nullptr;
static bool gPreloadFdsExtracted = false;
// Utility routine to fork a process from the zygote.
+NO_STACK_PROTECTOR
pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server,
const std::vector<int>& fds_to_close,
const std::vector<int>& fds_to_ignore,
@@ -2098,6 +2104,11 @@ pid_t zygote::ForkCommon(JNIEnv* env, bool is_system_server,
setpriority(PRIO_PROCESS, 0, PROCESS_PRIORITY_MIN);
}
+#if defined(__BIONIC__)
+ // Reset the stack guard for the new process.
+ android_reset_stack_guards();
+#endif
+
// The child process.
PreApplicationInit();
@@ -2130,6 +2141,7 @@ static void com_android_internal_os_Zygote_nativePreApplicationInit(JNIEnv*, jcl
PreApplicationInit();
}
+NO_STACK_PROTECTOR
static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint runtime_flags,
jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name,
@@ -2184,6 +2196,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
return pid;
}
+NO_STACK_PROTECTOR
static jint com_android_internal_os_Zygote_nativeForkSystemServer(
JNIEnv* env, jclass, uid_t uid, gid_t gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities,
@@ -2255,6 +2268,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer(
* @param is_priority_fork Controls the nice level assigned to the newly created process
* @return child pid in the parent, 0 in the child
*/
+NO_STACK_PROTECTOR
static jint com_android_internal_os_Zygote_nativeForkApp(JNIEnv* env,
jclass,
jint read_pipe_fd,
@@ -2269,6 +2283,7 @@ static jint com_android_internal_os_Zygote_nativeForkApp(JNIEnv* env,
args_known == JNI_TRUE, is_priority_fork == JNI_TRUE, true);
}
+NO_STACK_PROTECTOR
int zygote::forkApp(JNIEnv* env,
int read_pipe_fd,
int write_pipe_fd,
diff --git a/core/jni/com_android_internal_os_Zygote.h b/core/jni/com_android_internal_os_Zygote.h
index b87396cbd5f5..15f53e0814e3 100644
--- a/core/jni/com_android_internal_os_Zygote.h
+++ b/core/jni/com_android_internal_os_Zygote.h
@@ -20,6 +20,14 @@
#define LOG_TAG "Zygote"
#define ATRACE_TAG ATRACE_TAG_DALVIK
+/*
+ * All functions that lead to ForkCommon must be marked with the
+ * no_stack_protector attributed. Because ForkCommon changes the stack
+ * protector cookie, all of the guard checks on the frames above ForkCommon
+ * would fail when they are popped.
+ */
+#define NO_STACK_PROTECTOR __attribute__((no_stack_protector))
+
#include <jni.h>
#include <vector>
#include <android-base/stringprintf.h>
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index add645dee718..2b5b8f7a108e 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -377,6 +377,7 @@ void com_android_internal_os_ZygoteCommandBuffer_nativeReadFullyAndReset(JNIEnv*
// We only process fork commands if the peer uid matches expected_uid.
// For every fork command after the first, we check that the requested uid is at
// least minUid.
+NO_STACK_PROTECTOR
jboolean com_android_internal_os_ZygoteCommandBuffer_nativeForkRepeatedly(
JNIEnv* env,
jclass,
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 dac13ff62e3b..2876161628c8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2905,7 +2905,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
@@ -5144,11 +5144,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>
@@ -5657,7 +5657,7 @@
<bool name="config_enableSafetyCenter">true</bool>
<!-- Config for whether Safety Protection is enabled. -->
- <bool name="config_safetyProtectionEnabled">false</bool>
+ <bool name="config_safetyProtectionEnabled">true</bool>
<!-- Flag indicating if help links for Settings app should be enabled. -->
<bool name="config_settingsHelpLinksEnabled">false</bool>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index edebc7a8b604..11c245b4c85c 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -162,6 +162,8 @@
</staging-public-group>
<staging-public-group type="bool" first-id="0x01be0000">
+ <!-- @hide @SystemApi -->
+ <public name="config_safetyProtectionEnabled" />
</staging-public-group>
<staging-public-group type="fraction" first-id="0x01bd0000">
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 3e4b1cc87ef8..e15458680e66 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -161,6 +161,9 @@
<!-- ChooserActivityTest permissions-->
<uses-permission android:name="android.permission.SET_CLIP_SOURCE" />
+ <!-- AccessibilityShortcutChooserActivityTest permissions -->
+ <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" />
+
<application
android:theme="@style/Theme"
android:supportsRtl="true"
@@ -1414,6 +1417,7 @@
<activity android:name="com.android.internal.app.ChooserWrapperActivity"/>
<activity android:name="com.android.internal.app.ResolverWrapperActivity"/>
<activity android:name="com.android.internal.app.IntentForwarderActivityTest$IntentForwarderWrapperActivity"/>
+ <activity android:name="com.android.internal.accessibility.AccessibilityShortcutChooserActivityTest$TestAccessibilityShortcutChooserActivity"/>
<receiver android:name="android.app.activity.AbortReceiver"
android:exported="true">
@@ -1680,6 +1684,17 @@
android:resizeableActivity="true"
android:exported="true">
</activity>
+
+ <service android:name="android.view.stylus.HandwritingImeService"
+ android:label="Handwriting IME"
+ android:permission="android.permission.BIND_INPUT_METHOD"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.view.InputMethod"/>
+ </intent-filter>
+ <meta-data android:name="android.view.im"
+ android:resource="@xml/ime_meta_handwriting"/>
+ </service>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/core/tests/coretests/res/xml/ime_meta_handwriting.xml b/core/tests/coretests/res/xml/ime_meta_handwriting.xml
new file mode 100644
index 000000000000..24c0c255c2f3
--- /dev/null
+++ b/core/tests/coretests/res/xml/ime_meta_handwriting.xml
@@ -0,0 +1,21 @@
+<?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.
+ -->
+
+<input-method
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity"
+ android:supportsStylusHandwriting="true"/>
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingImeService.java b/core/tests/coretests/src/android/view/stylus/HandwritingImeService.java
new file mode 100644
index 000000000000..98cf6b25152d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingImeService.java
@@ -0,0 +1,32 @@
+/*
+ * 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.view.stylus;
+
+import android.content.ComponentName;
+import android.inputmethodservice.InputMethodService;
+
+public class HandwritingImeService extends InputMethodService {
+ private static final String PACKAGE_NAME = "com.android.frameworks.coretests";
+
+ private static ComponentName getComponentName() {
+ return new ComponentName(PACKAGE_NAME, HandwritingImeService.class.getName());
+ }
+
+ static String getImeId() {
+ return getComponentName().flattenToShortString();
+ }
+}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 647e410d8c28..8d3ee2a15dce 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -43,10 +43,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.concurrent.TimeUnit;
+
/**
* Tests for {@link HandwritingInitiator}
*
@@ -60,7 +65,7 @@ public class HandwritingInitiatorTest {
private static final long TIMEOUT = ViewConfiguration.getLongPressTimeout();
private static final int HW_BOUNDS_OFFSETS_LEFT_PX = 10;
private static final int HW_BOUNDS_OFFSETS_TOP_PX = 20;
- private static final int HW_BOUNDS_OFFSETS_RIGHT_PX = 30;
+ private static final int HW_BOUNDS_OFFSETS_RIGHT_PX = 30;
private static final int HW_BOUNDS_OFFSETS_BOTTOM_PX = 40;
private int mHandwritingSlop = 4;
@@ -71,9 +76,17 @@ public class HandwritingInitiatorTest {
private Context mContext;
@Before
- public void setup() {
- final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mContext = mInstrumentation.getTargetContext();
+ public void setup() throws Exception {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = instrumentation.getTargetContext();
+
+ String imeId = HandwritingImeService.getImeId();
+ instrumentation.getUiAutomation().executeShellCommand("ime enable " + imeId);
+ instrumentation.getUiAutomation().executeShellCommand("ime set " + imeId);
+ PollingCheck.check("Check that stylus handwriting is available",
+ TimeUnit.SECONDS.toMillis(10),
+ () -> mContext.getSystemService(InputMethodManager.class)
+ .isStylusHandwritingAvailable());
final ViewConfiguration viewConfiguration = ViewConfiguration.get(mContext);
mHandwritingSlop = viewConfiguration.getScaledHandwritingSlop();
@@ -90,22 +103,32 @@ public class HandwritingInitiatorTest {
mHandwritingInitiator.updateHandwritingAreasForView(mTestView);
}
+ @After
+ public void tearDown() throws Exception {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .executeShellCommand("ime reset");
+ }
+
@Test
public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() {
mHandwritingInitiator.onInputConnectionCreated(mTestView);
final int x1 = (sHwArea.left + sHwArea.right) / 2;
final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
- mHandwritingInitiator.onTouchEvent(stylusEvent1);
+ boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1);
final int x2 = x1 + mHandwritingSlop * 2;
final int y2 = y1;
MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
- mHandwritingInitiator.onTouchEvent(stylusEvent2);
+ boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2);
// Stylus movement within HandwritingArea should trigger IMM.startHandwriting once.
verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
+ assertThat(onTouchEventResult1).isFalse();
+ // After IMM.startHandwriting is triggered, onTouchEvent should return true for ACTION_MOVE
+ // events so that the events are not dispatched to the view tree.
+ assertThat(onTouchEventResult2).isTrue();
}
@Test
@@ -114,24 +137,38 @@ public class HandwritingInitiatorTest {
final int x1 = (sHwArea.left + sHwArea.right) / 2;
final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
- mHandwritingInitiator.onTouchEvent(stylusEvent1);
+ boolean onTouchEventResult1 = mHandwritingInitiator.onTouchEvent(stylusEvent1);
- final int x2 = x1 + mHandwritingSlop * 2;
+ final int x2 = x1 + mHandwritingSlop / 2;
final int y2 = y1;
MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
- mHandwritingInitiator.onTouchEvent(stylusEvent2);
-
+ boolean onTouchEventResult2 = mHandwritingInitiator.onTouchEvent(stylusEvent2);
final int x3 = x2 + mHandwritingSlop * 2;
- final int y3 = y2;
+ final int y3 = y1;
MotionEvent stylusEvent3 = createStylusEvent(ACTION_MOVE, x3, y3, 0);
- mHandwritingInitiator.onTouchEvent(stylusEvent3);
+ boolean onTouchEventResult3 = mHandwritingInitiator.onTouchEvent(stylusEvent3);
+
+ final int x4 = x3 + mHandwritingSlop * 2;
+ final int y4 = y1;
+ MotionEvent stylusEvent4 = createStylusEvent(ACTION_MOVE, x4, y4, 0);
+ boolean onTouchEventResult4 = mHandwritingInitiator.onTouchEvent(stylusEvent4);
- MotionEvent stylusEvent4 = createStylusEvent(ACTION_UP, x2, y2, 0);
- mHandwritingInitiator.onTouchEvent(stylusEvent4);
+ MotionEvent stylusEvent5 = createStylusEvent(ACTION_UP, x4, y4, 0);
+ boolean onTouchEventResult5 = mHandwritingInitiator.onTouchEvent(stylusEvent5);
// It only calls startHandwriting once for each ACTION_DOWN.
verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
+ assertThat(onTouchEventResult1).isFalse();
+ // stylusEvent2 does not trigger IMM.startHandwriting since the touch slop distance has not
+ // been exceeded. onTouchEvent should return false so that the event is dispatched to the
+ // view tree.
+ assertThat(onTouchEventResult2).isFalse();
+ // After IMM.startHandwriting is triggered by stylusEvent3, onTouchEvent should return true
+ // for ACTION_MOVE events so that the events are not dispatched to the view tree.
+ assertThat(onTouchEventResult3).isTrue();
+ assertThat(onTouchEventResult4).isTrue();
+ assertThat(onTouchEventResult5).isFalse();
}
@Test
@@ -189,6 +226,32 @@ public class HandwritingInitiatorTest {
}
@Test
+ public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() throws Exception {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .executeShellCommand("ime reset");
+ PollingCheck.check("Check that stylus handwriting is unavailable",
+ TimeUnit.SECONDS.toMillis(10),
+ () -> !mContext.getSystemService(InputMethodManager.class)
+ .isStylusHandwritingAvailable());
+
+ mHandwritingInitiator.onInputConnectionCreated(mTestView);
+ final int x1 = (sHwArea.left + sHwArea.right) / 2;
+ final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
+ MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+ mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+ final int x2 = x1 + mHandwritingSlop * 2;
+ final int y2 = y1;
+
+ MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+ mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+ // Stylus movement within HandwritingArea should not trigger IMM.startHandwriting since
+ // the current IME doesn't support handwriting.
+ verify(mHandwritingInitiator, never()).startHandwriting(mTestView);
+ }
+
+ @Test
public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() {
mHandwritingInitiator.onInputConnectionCreated(mTestView);
final int x1 = 200;
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
new file mode 100644
index 000000000000..728757929cfd
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -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.internal.accessibility;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.action.ViewActions.doubleClick;
+import static androidx.test.espresso.action.ViewActions.scrollTo;
+import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.RootMatchers.isDialog;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withText;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+
+/**
+ * Tests for {@link AccessibilityShortcutChooserActivity}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityShortcutChooserActivityTest {
+ private static final String TEST_LABEL = "TEST_LABEL";
+ private static final Context sContext =
+ InstrumentationRegistry.getInstrumentation().getContext();
+ private ActivityScenario<TestAccessibilityShortcutChooserActivity> mScenario;
+ private static IAccessibilityManager sAccessibilityManagerService;
+
+ @Mock
+ private AccessibilityServiceInfo mAccessibilityServiceInfo;
+
+ @Mock
+ private ResolveInfo mResolveInfo;
+
+ @Mock
+ private ServiceInfo mServiceInfo;
+
+ @Mock
+ private ApplicationInfo mApplicationInfo;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ sAccessibilityManagerService = mock(IAccessibilityManager.class);
+
+ when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
+ mResolveInfo.serviceInfo = mServiceInfo;
+ mServiceInfo.applicationInfo = mApplicationInfo;
+ when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL);
+ when(mAccessibilityServiceInfo.getComponentName()).thenReturn(
+ new ComponentName("package", "class"));
+ when(sAccessibilityManagerService.getInstalledAccessibilityServiceList(
+ anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo));
+
+ mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+ }
+
+ @Test
+ public void doubleClickServiceTargetAndClickDenyButton_permissionDialogDoesNotExist() {
+ mScenario.moveToState(Lifecycle.State.CREATED);
+ mScenario.moveToState(Lifecycle.State.STARTED);
+ mScenario.moveToState(Lifecycle.State.RESUMED);
+ onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
+ isDialog()).check(matches(isDisplayed()));
+ onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
+
+ onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick());
+ onView(withId(R.id.accessibility_permission_enable_deny_button)).perform(scrollTo(),
+ click());
+
+ onView(withId(R.id.accessibility_permissionDialog_title)).inRoot(isDialog()).check(
+ doesNotExist());
+ mScenario.moveToState(Lifecycle.State.DESTROYED);
+ }
+
+ /**
+ * Used for testing.
+ */
+ public static class TestAccessibilityShortcutChooserActivity extends
+ AccessibilityShortcutChooserActivity {
+ private AccessibilityManager mAccessibilityManager;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ mAccessibilityManager = new AccessibilityManager(sContext, new Handler(getMainLooper()),
+ sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (Context.ACCESSIBILITY_SERVICE.equals(name)) {
+ return mAccessibilityManager;
+ }
+
+ return super.getSystemService(name);
+ }
+ }
+}
diff --git a/data/keyboards/Vendor_0957_Product_0006.idc b/data/keyboards/Vendor_0957_Product_0006.idc
new file mode 100644
index 000000000000..842307893448
--- /dev/null
+++ b/data/keyboards/Vendor_0957_Product_0006.idc
@@ -0,0 +1,25 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Input Device Configuration file for Google Reference RCU Remote.
+#
+#
+
+# Basic Parameters
+# Depending on the FLASH configurations, RCUs may have PID 0006 instead
+# of 0001.
+keyboard.layout = Vendor_0957_Product_0001
+keyboard.doNotWakeByDefault = 1
+audio.mic = 1
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/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 89d262b17b59..f061f8bc178a 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
@@ -187,8 +187,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
@@ -259,33 +259,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 {
@@ -354,18 +355,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/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 28427a808d90..c80c14f353d0 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) {
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..a43b6043908b 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
@@ -237,6 +237,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..05e5b8e66a00 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) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
index 61e11e877b90..61e92f355dc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
@@ -107,7 +107,7 @@ public class LegacyTransitions {
}
@Override
- public void onAnimationCancelled() throws RemoteException {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
mCancelled = true;
mApps = mWallpapers = mNonApps = null;
checkApply();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 2514e3be2f78..e7d641e9c66e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -51,7 +50,6 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
-@FlakyTest(bugId = 234848637)
class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
protected val taplInstrumentation = LauncherInstrumentation()
/**
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/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index 87ae45bedd67..3527eeead1d5 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -227,7 +227,6 @@ status_t CursorWindow::writeToParcel(Parcel* parcel) {
if (!dest) goto fail;
memcpy(static_cast<uint8_t*>(dest),
static_cast<uint8_t*>(mData), mAllocOffset);
- memset(static_cast<uint8_t*>(dest) + mAllocOffset, 0, 4);
memcpy(static_cast<uint8_t*>(dest) + compactedSize - slotsSize,
static_cast<uint8_t*>(mData) + mSlotsOffset, slotsSize);
}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index fd0c8948c321..f59650203889 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -2394,8 +2394,8 @@ public class LocationManager {
* @return true if the listener was successfully added
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*
- * @deprecated use {@link #registerGnssStatusCallback(GnssStatus.Callback)} instead. No longer
- * supported in apps targeting S and above.
+ * @deprecated Use {@link #registerGnssStatusCallback(GnssStatus.Callback, Handler)} or {@link
+ * #registerGnssStatusCallback(Executor, GnssStatus.Callback)} instead.
*/
@Deprecated
@RequiresPermission(ACCESS_FINE_LOCATION)
@@ -2505,7 +2505,8 @@ public class LocationManager {
/**
* No-op method to keep backward-compatibility.
*
- * @deprecated Use {@link #addNmeaListener} instead.
+ * @deprecated Use {@link #addNmeaListener(OnNmeaMessageListener, Handler)} or {@link
+ * #addNmeaListener(Executor, OnNmeaMessageListener)} instead.
*/
@Deprecated
@RequiresPermission(ACCESS_FINE_LOCATION)
@@ -2667,6 +2668,13 @@ public class LocationManager {
*
* <p>Not all GNSS chipsets support measurements updates, see {@link #getGnssCapabilities()}.
*
+ * <p class="caution">On Android R devices that have not yet upgraded to Android R QPR1, using
+ * this API will cause unavoidable crashes in the client application when GNSS measurements
+ * are received. If a client needs to receive GNSS measurements on Android R devices that have
+ * not been upgraded to QPR1, clients are instead encouraged to use
+ * <a href="https://developer.android.com/reference/androidx/core/location/LocationManagerCompat#registerGnssMeasurementsCallback(android.location.LocationManager,java.util.concurrent.Executor,android.location.GnssMeasurementsEvent.Callback)">LocationManagerCompat.registerGnssMeasurementsCallback()</a>
+ * from the compat libraries instead to avoid this crash.
+ *
* @param executor the executor that the callback runs on
* @param callback the callback to register
* @return {@code true} always
diff --git a/location/java/android/location/package.html b/location/java/android/location/package.html
index 20c5c54d6921..9e048d6f5f07 100644
--- a/location/java/android/location/package.html
+++ b/location/java/android/location/package.html
@@ -1,17 +1,13 @@
<html>
<body>
-
-<p>Contains the framework API classes that define Android location-based and
- related services.</p>
-<p class="warning">
-<strong>This API is not the recommended method for accessing Android location.</strong><br>
-The
-<a href="https://developers.google.com/android/reference/com/google/android/gms/location/package-summary">Google Location Services API</a>,
-part of Google Play services, is the preferred way to add location-awareness to
-your app. It offers a simpler API, higher accuracy, low-power geofencing, and
-more. If you are currently using the android.location API, you are strongly
-encouraged to switch to the Google Location Services API as soon as
-possible.
+<p>Contains framework API classes for accessing a variety of location related services.</p>
+<p class="note"><strong>Note:</strong> The
+<a href="https://developers.google.com/android/reference/com/google/android/gms/location/package-summary">Google Location Services APIs</a>,
+part of Google Play services, is the preferred way to access location services for apps. These APIs
+are both simpler to use and offer more capabilities as compared to the traditional Android location
+APIs found here. They also offer new services, such as low-power geofencing, activity recognition,
+and more. Clients of the traditional Android location APIs are encouraged to switch to the Google
+Location Services APIs wherever possible.
<br><br>
To learn more about the Google Location Services API, see the
<a href="{@docRoot}google/play-services/location.html">Location API overview</a>.
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 78fd944dc22b..6922637350d9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4004,7 +4004,7 @@ public class AudioManager {
* Timeout duration in ms when waiting on an external focus policy for the result for a
* focus request
*/
- private static final int EXT_FOCUS_POLICY_TIMEOUT_MS = 200;
+ private static final int EXT_FOCUS_POLICY_TIMEOUT_MS = 250;
private static final String FOCUS_CLIENT_ID_STRING = "android_audio_focus_client_id";
@@ -4284,8 +4284,17 @@ public class AudioManager {
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+ if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
+ // default path with no external focus policy
+ return status;
+ }
+
+ BlockingFocusResultReceiver focusReceiver;
+ synchronized (mFocusRequestsLock) {
+ focusReceiver = addClientIdToFocusReceiverLocked(clientFakeId);
+ }
- return handleExternalAudioPolicyWaitIfNeeded(clientFakeId, status);
+ return handleExternalAudioPolicyWaitIfNeeded(clientFakeId, focusReceiver);
}
/**
@@ -4368,7 +4377,9 @@ public class AudioManager {
}
final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
+ BlockingFocusResultReceiver focusReceiver;
synchronized (mFocusRequestsLock) {
+
try {
// TODO status contains result and generation counter for ext policy
status = service.requestAudioFocus(afr.getAudioAttributes(),
@@ -4383,29 +4394,30 @@ public class AudioManager {
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+ if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
+ // default path with no external focus policy
+ return status;
+ }
+ focusReceiver = addClientIdToFocusReceiverLocked(clientId);
}
- return handleExternalAudioPolicyWaitIfNeeded(clientId, status);
+ return handleExternalAudioPolicyWaitIfNeeded(clientId, focusReceiver);
}
- private @FocusRequestResult int handleExternalAudioPolicyWaitIfNeeded(String clientId,
- @FocusRequestResult int results) {
- if (results != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
- // default path with no external focus policy
- return results;
- }
-
+ @GuardedBy("mFocusRequestsLock")
+ private BlockingFocusResultReceiver addClientIdToFocusReceiverLocked(String clientId) {
BlockingFocusResultReceiver focusReceiver;
-
- synchronized (mFocusRequestsLock) {
- if (mFocusRequestsAwaitingResult == null) {
- mFocusRequestsAwaitingResult =
- new HashMap<String, BlockingFocusResultReceiver>(1);
- }
- focusReceiver = new BlockingFocusResultReceiver(clientId);
- mFocusRequestsAwaitingResult.put(clientId, focusReceiver);
+ if (mFocusRequestsAwaitingResult == null) {
+ mFocusRequestsAwaitingResult =
+ new HashMap<String, BlockingFocusResultReceiver>(1);
}
+ focusReceiver = new BlockingFocusResultReceiver(clientId);
+ mFocusRequestsAwaitingResult.put(clientId, focusReceiver);
+ return focusReceiver;
+ }
+ private @FocusRequestResult int handleExternalAudioPolicyWaitIfNeeded(String clientId,
+ BlockingFocusResultReceiver focusReceiver) {
focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);
if (DEBUG && !focusReceiver.receivedResult()) {
Log.e(TAG, "handleExternalAudioPolicyWaitIfNeeded"
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 2a04ebb1efec..762dea1393a0 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -2867,7 +2867,7 @@ public final class MediaDrm implements AutoCloseable {
= "drm.mediadrm.get_device_unique_id.error.list";
/**
- * Key to extraact the count of {@link KeyStatus#STATUS_EXPIRED} events
+ * Key to extract the count of {@link KeyStatus#STATUS_EXPIRED} events
* that occured. The count is extracted from the
* {@link PersistableBundle} returned from a {@link #getMetrics} call.
* The count is a Long value ({@link android.os.BaseBundle#getLong}).
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/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/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 20fac93df0af..db14fdf5930b 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -474,7 +474,13 @@ class TextInterpolator(layout: Layout) {
val out = mutableListOf<List<PositionedGlyphs>>()
for (lineNo in 0 until layout.lineCount) { // Shape all lines.
val lineStart = layout.getLineStart(lineNo)
- val count = layout.getLineEnd(lineNo) - lineStart
+ var count = layout.getLineEnd(lineNo) - lineStart
+ // Do not render the last character in the line if it's a newline and unprintable
+ val last = lineStart + count - 1
+ if (last > lineStart && last < layout.text.length && layout.text[last] == '\n') {
+ count--
+ }
+
val runs = mutableListOf<PositionedGlyphs>()
TextShaper.shapeText(
layout.text,
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 0c1916074e0c..cafdc8676173 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -13,9 +13,9 @@
*/
package com.android.systemui.plugins
+import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.view.View
-import com.android.internal.colorextraction.ColorExtractor
import com.android.systemui.plugins.annotations.ProvidesInterface
import java.io.PrintWriter
import java.util.Locale
@@ -57,7 +57,15 @@ interface Clock {
val events: ClockEvents
/** Triggers for various animations */
- val animation: ClockAnimation
+ val animations: ClockAnimations
+
+ /** Initializes various rendering parameters. If never called, provides reasonable defaults. */
+ fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+ events.onColorPaletteChanged(resources)
+ animations.doze(dozeFraction)
+ animations.fold(foldFraction)
+ events.onTimeTick()
+ }
/** Optional method for dumping debug information */
fun dump(pw: PrintWriter) { }
@@ -80,15 +88,12 @@ interface ClockEvents {
/** Call whenever font settings change */
fun onFontSettingChanged() { }
- /** Call whenever the color pallete should update */
- fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) { }
+ /** Call whenever the color palette should update */
+ fun onColorPaletteChanged(resources: Resources) { }
}
/** Methods which trigger various clock animations */
-interface ClockAnimation {
- /** Initializes the doze & fold animation positions. Defaults to neither folded nor dozing. */
- fun initialize(dozeFraction: Float, foldFraction: Float) { }
-
+interface ClockAnimations {
/** Runs an enter animation (if any) */
fun enter() { }
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/font/clock.xml b/packages/SystemUI/res-keyguard/font/clock.xml
deleted file mode 100644
index 0137dc39921f..000000000000
--- a/packages/SystemUI/res-keyguard/font/clock.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 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.
-*/
--->
-
-<!--
-** AOD/LockScreen Clock font.
-** Should include all numeric glyphs in all supported locales.
-** Recommended: font with variable width to support AOD => LS animations
--->
-<!-- TODO: Remove when clock migration complete -->
-<font-family xmlns:android="http://schemas.android.com/apk/res/android">
- <font android:typeface="monospace"/>
-</font-family> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 6a38507b2ad7..8b8ebf00e190 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -31,42 +31,14 @@
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:paddingStart="@dimen/clock_padding_start">
- <com.android.systemui.shared.clocks.AnimatableClockView
- android:id="@+id/animatable_clock_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:gravity="start"
- android:textSize="@dimen/clock_text_size"
- android:fontFamily="@font/clock"
- android:elegantTextHeight="false"
- android:singleLine="true"
- android:fontFeatureSettings="pnum"
- chargeAnimationDelay="350"
- dozeWeight="200"
- lockScreenWeight="400"
- />
</FrameLayout>
<FrameLayout
android:id="@+id/lockscreen_clock_view_large"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_below="@id/keyguard_slice_view"
+ android:paddingTop="@dimen/keyguard_large_clock_top_padding"
android:visibility="gone">
- <com.android.systemui.shared.clocks.AnimatableClockView
- android:id="@+id/animatable_clock_view_large"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:gravity="center_horizontal"
- android:textSize="@dimen/large_clock_text_size"
- android:fontFamily="@font/clock"
- android:typeface="monospace"
- android:elegantTextHeight="false"
- chargeAnimationDelay="200"
- dozeWeight="200"
- lockScreenWeight="400"
- />
</FrameLayout>
<!-- Not quite optimal but needed to translate these items as a group. The
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..5f4e310f975c 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -19,11 +19,11 @@
android:id="@+id/time_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:fontFamily="@font/clock"
+ android:fontFamily="@*android:string/config_clockFontFamily"
android:includeFontPadding="false"
android:textColor="@android:color/white"
- android:format12Hour="h:mm"
- android:format24Hour="kk:mm"
+ android:format12Hour="@string/dream_time_complication_12_hr_time_format"
+ android:format24Hour="@string/dream_time_complication_24_hr_time_format"
android:shadowColor="@color/keyguard_shadow_color"
android:shadowRadius="?attr/shadowRadius"
android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/>
diff --git a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
index 43b16618d615..a313833e2a66 100644
--- a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
+++ b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
@@ -57,7 +57,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- style="?attr/hybridNotificationTextStyle"
+ android:paddingEnd="4dp"
+ style="@*android:style/Widget.DeviceDefault.Notification.Text"
/>
<TextView
@@ -65,6 +66,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
- style="?attr/hybridNotificationTextStyle"
+ android:paddingEnd="4dp"
+ style="@*android:style/Widget.DeviceDefault.Notification.Text"
/>
</com.android.systemui.statusbar.notification.row.HybridConversationNotificationView>
diff --git a/packages/SystemUI/res/layout/hybrid_notification.xml b/packages/SystemUI/res/layout/hybrid_notification.xml
index e8d77511e53c..9ea7be50adec 100644
--- a/packages/SystemUI/res/layout/hybrid_notification.xml
+++ b/packages/SystemUI/res/layout/hybrid_notification.xml
@@ -20,19 +20,22 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom|start"
- style="?attr/hybridNotificationStyle">
+ android:paddingStart="@*android:dimen/notification_content_margin_start"
+ android:paddingEnd="12dp">
<TextView
android:id="@+id/notification_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- style="?attr/hybridNotificationTitleStyle"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:paddingEnd="4dp"
/>
<TextView
android:id="@+id/notification_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
- style="?attr/hybridNotificationTextStyle"
+ android:paddingEnd="4dp"
+ style="@*android:style/Widget.DeviceDefault.Notification.Text"
/>
</com.android.systemui.statusbar.notification.row.HybridNotificationView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
index 4d24140abbf4..d88680669fe0 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -31,6 +31,8 @@
android:padding="@dimen/media_ttt_chip_outer_padding"
android:background="@drawable/media_ttt_chip_background"
android:layout_marginTop="20dp"
+ android:layout_marginStart="@dimen/notification_side_paddings"
+ android:layout_marginEnd="@dimen/notification_side_paddings"
android:clipToPadding="false"
android:gravity="center_vertical"
android:alpha="0.0"
@@ -46,8 +48,9 @@
<TextView
android:id="@+id/text"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
+ android:layout_weight="1"
android:textSize="@dimen/media_ttt_text_size"
android:textColor="?android:attr/textColorPrimary"
android:alpha="0.0"
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index e6af6f46ae69..94fe20955ce6 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -15,9 +15,6 @@
-->
<resources>
- <!-- Minimum margin between clock and top of screen or ambient indication -->
- <dimen name="keyguard_clock_top_margin">26dp</dimen>
-
<!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
<dimen name="large_clock_text_size">200dp</dimen>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 70a72ad23d7e..9a71995383ac 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -108,12 +108,6 @@
<attr name="android:layout" />
</declare-styleable>
- <declare-styleable name="HybridNotificationTheme">
- <attr name="hybridNotificationStyle" format="reference" />
- <attr name="hybridNotificationTitleStyle" format="reference" />
- <attr name="hybridNotificationTextStyle" format="reference" />
- </declare-styleable>
-
<declare-styleable name="PluginInflateContainer">
<attr name="viewType" format="string" />
</declare-styleable>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3b8505015f3e..1210b79d3ff5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -661,13 +661,7 @@
<!-- When large clock is showing, offset the smartspace by this amount -->
<dimen name="keyguard_smartspace_top_offset">12dp</dimen>
<!-- With the large clock, move up slightly from the center -->
- <dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
-
- <!-- TODO: Remove during migration -->
- <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
- <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
- <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
- <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
+ <dimen name="keyguard_large_clock_top_padding">100dp</dimen>
<dimen name="notification_scrim_corner_radius">32dp</dimen>
@@ -890,11 +884,6 @@
burn-in on AOD. -->
<dimen name="burn_in_prevention_offset_y_clock">42dp</dimen>
- <!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
- <!-- TODO: Remove when clock migration complete -->
- <dimen name="large_clock_text_size">150dp</dimen>
- <dimen name="clock_text_size">86dp</dimen>
-
<!-- The maximum offset in either direction that icons move to prevent burn-in on AOD. -->
<dimen name="default_burn_in_prevention_offset">15dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d88428f972cb..343ec4f67964 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2581,4 +2581,12 @@
<!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is unknown -->
<string name="bt_le_audio_broadcast_dialog_unknown_name">Unknown</string>
+ <!-- Date format for the Dream Date Complication [CHAR LIMIT=NONE] -->
+ <string name="dream_date_complication_date_format">EEE, MMM d</string>
+
+ <!-- Time format for the Dream Time Complication for 12-hour time format [CHAR LIMIT=NONE] -->
+ <string name="dream_time_complication_12_hr_time_format">h:mm</string>
+
+ <!-- Time format for the Dream Time Complication for 24-hour time format [CHAR LIMIT=NONE] -->
+ <string name="dream_time_complication_24_hr_time_format">kk:mm</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 4ca6e3ab3dc1..758c16d15bcf 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..2c3ff2c75c72
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -0,0 +1,51 @@
+package com.android.systemui.testing.screenshot
+
+import android.app.Activity
+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 [view] 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),
+ view: (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(view(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))
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/shared/res/layout/clock_default_large.xml
index 8510a0a8b550..0139d50dcfba 100644
--- a/packages/SystemUI/shared/res/layout/clock_default_large.xml
+++ b/packages/SystemUI/shared/res/layout/clock_default_large.xml
@@ -18,7 +18,6 @@
-->
<com.android.systemui.shared.clocks.AnimatableClockView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/animatable_clock_view_large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/shared/res/layout/clock_default_small.xml
index ec0e427e6a4d..390ff5e3ff78 100644
--- a/packages/SystemUI/shared/res/layout/clock_default_small.xml
+++ b/packages/SystemUI/shared/res/layout/clock_default_small.xml
@@ -18,7 +18,6 @@
-->
<com.android.systemui.shared.clocks.AnimatableClockView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/animatable_clock_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
@@ -26,6 +25,7 @@
android:textSize="@dimen/small_clock_text_size"
android:fontFamily="@*android:string/config_clockFontFamily"
android:elegantTextHeight="false"
+ android:ellipsize="none"
android:singleLine="true"
android:fontFeatureSettings="pnum"
chargeAnimationDelay="350"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 2739d59dbf00..8f1959e884cf 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -20,12 +20,15 @@ import android.annotation.ColorInt
import android.annotation.FloatRange
import android.annotation.IntRange
import android.annotation.SuppressLint
+import android.app.compat.ChangeIdStateCache.invalidate
import android.content.Context
import android.graphics.Canvas
import android.text.TextUtils
import android.text.format.DateFormat
import android.util.AttributeSet
import android.widget.TextView
+import com.android.internal.R.attr.contentDescription
+import com.android.internal.R.attr.format
import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.TextAnimator
@@ -75,6 +78,12 @@ class AnimatableClockView @JvmOverloads constructor(
val lockScreenWeight: Int
get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal
+ /**
+ * The number of pixels below the baseline. For fonts that support languages such as
+ * Burmese, this space can be significant and should be accounted for when computing layout.
+ */
+ val bottom get() = paint?.fontMetrics?.bottom ?: 0f
+
init {
val animatableClockViewAttributes = context.obtainStyledAttributes(
attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes
@@ -133,6 +142,15 @@ class AnimatableClockView @JvmOverloads constructor(
// relayout if the text didn't actually change.
if (!TextUtils.equals(text, formattedText)) {
text = formattedText
+
+ // Because the TextLayout may mutate under the hood as a result of the new text, we
+ // notify the TextAnimator that it may have changed and request a measure/layout. A
+ // crash will occur on the next invocation of setTextStyle if the layout is mutated
+ // without being notified TextInterpolator being notified.
+ if (layout != null) {
+ textAnimator?.updateLayout(layout)
+ }
+ requestLayout()
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index a4c03b0b57c8..06247c6c9523 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -35,8 +35,6 @@ import javax.inject.Inject
private val TAG = ClockRegistry::class.simpleName
private val DEBUG = true
-typealias ClockChangeListener = () -> Unit
-
/** ClockRegistry aggregates providers and plugins */
open class ClockRegistry(
val context: Context,
@@ -51,12 +49,19 @@ open class ClockRegistry(
defaultClockProvider: DefaultClockProvider
) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { }
+ // Usually this would be a typealias, but a SAM provides better java interop
+ fun interface ClockChangeListener {
+ fun onClockChanged()
+ }
+
+ var isEnabled: Boolean = false
+
private val gson = Gson()
private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
private val clockChangeListeners = mutableListOf<ClockChangeListener>()
private val settingObserver = object : ContentObserver(handler) {
override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
- clockChangeListeners.forEach { it() }
+ clockChangeListeners.forEach { it.onClockChanged() }
}
private val pluginListener = object : PluginListener<ClockProviderPlugin> {
@@ -73,7 +78,7 @@ open class ClockRegistry(
context.contentResolver,
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
)
- return gson.fromJson(json, ClockSetting::class.java).clockId
+ return gson.fromJson(json, ClockSetting::class.java)?.clockId ?: DEFAULT_CLOCK_ID
}
set(value) {
val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
@@ -85,6 +90,12 @@ open class ClockRegistry(
init {
connectClocks(defaultClockProvider)
+ if (!availableClocks.containsKey(DEFAULT_CLOCK_ID)) {
+ throw IllegalArgumentException(
+ "$defaultClockProvider did not register clock at $DEFAULT_CLOCK_ID"
+ )
+ }
+
pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
context.contentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
@@ -100,8 +111,11 @@ open class ClockRegistry(
val id = clock.clockId
val current = availableClocks[id]
if (current != null) {
- Log.e(TAG, "Clock Id conflict: $id is registered by both " +
- "${provider::class.simpleName} and ${current.provider::class.simpleName}")
+ Log.e(
+ TAG,
+ "Clock Id conflict: $id is registered by both " +
+ "${provider::class.simpleName} and ${current.provider::class.simpleName}"
+ )
return
}
@@ -110,7 +124,7 @@ open class ClockRegistry(
if (DEBUG) {
Log.i(TAG, "Current clock ($currentId) was connected")
}
- clockChangeListeners.forEach { it() }
+ clockChangeListeners.forEach { it.onClockChanged() }
}
}
}
@@ -122,12 +136,17 @@ open class ClockRegistry(
if (currentId == clock.clockId) {
Log.w(TAG, "Current clock ($currentId) was disconnected")
- clockChangeListeners.forEach { it() }
+ clockChangeListeners.forEach { it.onClockChanged() }
}
}
}
- fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata }
+ fun getClocks(): List<ClockMetadata> {
+ if (!isEnabled) {
+ return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
+ }
+ return availableClocks.map { (_, clock) -> clock.metadata }
+ }
fun getClockThumbnail(clockId: ClockId): Drawable? =
availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
@@ -142,7 +161,7 @@ open class ClockRegistry(
fun createCurrentClock(): Clock {
val clockId = currentClockId
- if (!clockId.isNullOrEmpty()) {
+ if (isEnabled && clockId.isNotEmpty()) {
val clock = createClock(clockId)
if (clock != null) {
return clock
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index 5d8da5985768..1d8abe3fdf42 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -19,10 +19,9 @@ import android.graphics.drawable.Drawable
import android.icu.text.NumberFormat
import android.util.TypedValue
import android.view.LayoutInflater
-import com.android.internal.colorextraction.ColorExtractor
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.Clock
-import com.android.systemui.plugins.ClockAnimation
+import com.android.systemui.plugins.ClockAnimations
import com.android.systemui.plugins.ClockEvents
import com.android.systemui.plugins.ClockId
import com.android.systemui.plugins.ClockMetadata
@@ -102,10 +101,13 @@ class DefaultClock(
TypedValue.COMPLEX_UNIT_PX,
resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
)
+ recomputePadding()
}
- override fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) =
- clocks.forEach { it.setColors(DOZE_COLOR, palette.mainColor) }
+ override fun onColorPaletteChanged(resources: Resources) {
+ val color = resources.getColor(android.R.color.system_accent1_100)
+ clocks.forEach { it.setColors(DOZE_COLOR, color) }
+ }
override fun onLocaleChanged(locale: Locale) {
val nf = NumberFormat.getInstance(locale)
@@ -119,8 +121,17 @@ class DefaultClock(
}
}
- override val animation = object : ClockAnimation {
- override fun initialize(dozeFraction: Float, foldFraction: Float) {
+ override var animations = DefaultClockAnimations(0f, 0f)
+ private set
+
+ inner class DefaultClockAnimations(
+ dozeFraction: Float,
+ foldFraction: Float
+ ) : ClockAnimations {
+ private var foldState = AnimationState(0f)
+ private var dozeState = AnimationState(0f)
+
+ init {
dozeState = AnimationState(dozeFraction)
foldState = AnimationState(foldFraction)
@@ -132,14 +143,13 @@ class DefaultClock(
}
override fun enter() {
- if (dozeState.isActive) {
+ if (!dozeState.isActive) {
clocks.forEach { it.animateAppearOnLockscreen() }
}
}
override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
- private var foldState = AnimationState(0f)
override fun fold(fraction: Float) {
val (hasChanged, hasJumped) = foldState.update(fraction)
if (hasChanged) {
@@ -147,7 +157,6 @@ class DefaultClock(
}
}
- private var dozeState = AnimationState(0f)
override fun doze(fraction: Float) {
val (hasChanged, hasJumped) = dozeState.update(fraction)
if (hasChanged) {
@@ -172,6 +181,19 @@ class DefaultClock(
init {
events.onLocaleChanged(Locale.getDefault())
+ clocks.forEach { it.setColors(DOZE_COLOR, DOZE_COLOR) }
+ }
+
+ override fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) {
+ recomputePadding()
+ animations = DefaultClockAnimations(dozeFraction, foldFraction)
+ events.onColorPaletteChanged(resources)
+ events.onTimeTick()
+ }
+
+ private fun recomputePadding() {
+ val topPadding = -1 * (largeClock.bottom.toInt() - 180)
+ largeClock.setPadding(0, topPadding, 0, 0)
}
override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) }
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..e629f749ee96 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,
@@ -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/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index c69ff7ee1cd8..e0b11d83bf75 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -182,18 +182,6 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
mStatusBarStateController.removeCallback(mStatusBarStateListener);
}
- /**
- * @return the number of pixels below the baseline. For fonts that support languages such as
- * Burmese, this space can be significant.
- */
- public float getBottom() {
- if (mView.getPaint() != null && mView.getPaint().getFontMetrics() != null) {
- return mView.getPaint().getFontMetrics().bottom;
- }
-
- return 0f;
- }
-
/** Animate the clock appearance */
public void animateAppear() {
if (!mIsDozing) mView.animateAppearOnLockscreen();
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
new file mode 100644
index 000000000000..efd7bcf10cd2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.res.Resources
+import android.text.format.DateFormat
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
+import com.android.systemui.statusbar.policy.ConfigurationController
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+import javax.inject.Inject
+
+/**
+ * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
+ * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
+ */
+class ClockEventController @Inject constructor(
+ private val statusBarStateController: StatusBarStateController,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val batteryController: BatteryController,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val configurationController: ConfigurationController,
+ @Main private val resources: Resources,
+ private val context: Context
+) {
+ var clock: Clock? = null
+ set(value) {
+ field = value
+ if (value != null) {
+ value.initialize(resources, dozeAmount, 0f)
+ }
+ }
+
+ private var isDozing = false
+ private set
+
+ private var isCharging = false
+ private var dozeAmount = 0f
+ private var isKeyguardShowing = false
+
+ private val configListener = object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ clock?.events?.onColorPaletteChanged(resources)
+ }
+ }
+
+ private val batteryCallback = object : BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+ if (isKeyguardShowing && !isCharging && charging) {
+ clock?.animations?.charge()
+ }
+ isCharging = charging
+ }
+ }
+
+ private val localeBroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ clock?.events?.onLocaleChanged(Locale.getDefault())
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ clock?.animations?.doze(linear)
+
+ isDozing = linear > dozeAmount
+ dozeAmount = linear
+ }
+ }
+
+ private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
+ override fun onKeyguardVisibilityChanged(showing: Boolean) {
+ isKeyguardShowing = showing
+ if (!isKeyguardShowing) {
+ clock?.animations?.doze(if (isDozing) 1f else 0f)
+ }
+ }
+
+ override fun onTimeFormatChanged(timeFormat: String) {
+ clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+
+ override fun onTimeZoneChanged(timeZone: TimeZone) {
+ clock?.events?.onTimeZoneChanged(timeZone)
+ }
+
+ override fun onUserSwitchComplete(userId: Int) {
+ clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+ }
+
+ init {
+ isDozing = statusBarStateController.isDozing
+ }
+
+ fun registerListeners() {
+ dozeAmount = statusBarStateController.dozeAmount
+ isDozing = statusBarStateController.isDozing || dozeAmount != 0f
+
+ broadcastDispatcher.registerReceiver(
+ localeBroadcastReceiver,
+ IntentFilter(Intent.ACTION_LOCALE_CHANGED)
+ )
+ configurationController.addCallback(configListener)
+ batteryController.addCallback(batteryCallback)
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ statusBarStateController.addCallback(statusBarStateListener)
+ }
+
+ fun unregisterListeners() {
+ broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
+ configurationController.removeCallback(configListener)
+ batteryController.removeCallback(batteryCallback)
+ keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
+ statusBarStateController.removeCallback(statusBarStateListener)
+ }
+
+ /**
+ * Dump information for debugging
+ */
+ fun dump(pw: PrintWriter) {
+ pw.println(this)
+ clock?.dump(pw)
+ }
+
+ companion object {
+ private val TAG = ClockEventController::class.simpleName
+ private const val FORMAT_NUMBER = 1234567890
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 206b8bee323c..e1fabdef3651 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -5,10 +5,8 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
import android.util.AttributeSet;
-import android.util.TypedValue;
+import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -17,19 +15,14 @@ import android.widget.RelativeLayout;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.dagger.KeyguardStatusViewScope;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.plugins.Clock;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
-import java.util.TimeZone;
-
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
@@ -50,17 +43,10 @@ public class KeyguardClockSwitch extends RelativeLayout {
public static final int SMALL = 1;
/**
- * Optional/alternative clock injected via plugin.
- */
- private ClockPlugin mClockPlugin;
-
- /**
* Frame for small/large clocks
*/
- private FrameLayout mClockFrame;
+ private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
- private AnimatableClockView mClockView;
- private AnimatableClockView mLargeClockView;
private View mStatusArea;
private int mSmartspaceTopOffset;
@@ -80,12 +66,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
@VisibleForTesting AnimatorSet mClockOutAnim = null;
private ObjectAnimator mStatusAreaAnim = null;
- /**
- * If the Keyguard Slice has a header (big center-aligned text.)
- */
- private boolean mSupportsDarkText;
- private int[] mColorPalette;
-
private int mClockSwitchYAmount;
@VisibleForTesting boolean mChildrenAreLaidOut = false;
@@ -97,97 +77,38 @@ public class KeyguardClockSwitch extends RelativeLayout {
* Apply dp changes on font/scale change
*/
public void onDensityOrFontScaleChanged() {
- mLargeClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources()
- .getDimensionPixelSize(R.dimen.large_clock_text_size));
- mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources()
- .getDimensionPixelSize(R.dimen.clock_text_size));
-
mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_clock_switch_y_shift);
-
mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
R.dimen.keyguard_smartspace_top_offset);
}
- /**
- * Returns if this view is presenting a custom clock, or the default implementation.
- */
- public boolean hasCustomClock() {
- return mClockPlugin != null;
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mClockFrame = findViewById(R.id.lockscreen_clock_view);
- mClockView = findViewById(R.id.animatable_clock_view);
+ mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
- mLargeClockView = findViewById(R.id.animatable_clock_view_large);
mStatusArea = findViewById(R.id.keyguard_status_area);
onDensityOrFontScaleChanged();
}
- void setClockPlugin(ClockPlugin plugin, int statusBarState) {
+ void setClock(Clock clock, int statusBarState) {
// Disconnect from existing plugin.
- if (mClockPlugin != null) {
- View smallClockView = mClockPlugin.getView();
- if (smallClockView != null && smallClockView.getParent() == mClockFrame) {
- mClockFrame.removeView(smallClockView);
- }
- View bigClockView = mClockPlugin.getBigClockView();
- if (bigClockView != null && bigClockView.getParent() == mLargeClockFrame) {
- mLargeClockFrame.removeView(bigClockView);
- }
- mClockPlugin.onDestroyView();
- mClockPlugin = null;
- }
- if (plugin == null) {
- mClockView.setVisibility(View.VISIBLE);
- mLargeClockView.setVisibility(View.VISIBLE);
- return;
- }
- // Attach small and big clock views to hierarchy.
- View smallClockView = plugin.getView();
- if (smallClockView != null) {
- mClockFrame.addView(smallClockView, -1,
- new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- mClockView.setVisibility(View.GONE);
- }
- View bigClockView = plugin.getBigClockView();
- if (bigClockView != null) {
- mLargeClockFrame.addView(bigClockView);
- mLargeClockView.setVisibility(View.GONE);
- }
-
- // Initialize plugin parameters.
- mClockPlugin = plugin;
- mClockPlugin.setStyle(getPaint().getStyle());
- mClockPlugin.setTextColor(getCurrentTextColor());
- mClockPlugin.setDarkAmount(mDarkAmount);
- if (mColorPalette != null) {
- mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
- }
- }
+ mSmallClockFrame.removeAllViews();
+ mLargeClockFrame.removeAllViews();
- /**
- * It will also update plugin setStyle if plugin is connected.
- */
- public void setStyle(Style style) {
- if (mClockPlugin != null) {
- mClockPlugin.setStyle(style);
+ if (clock == null) {
+ Log.e(TAG, "No clock being shown");
+ return;
}
- }
- /**
- * It will also update plugin setTextColor if plugin is connected.
- */
- public void setTextColor(int color) {
- if (mClockPlugin != null) {
- mClockPlugin.setTextColor(color);
- }
+ // Attach small and big clock views to hierarchy.
+ mSmallClockFrame.addView(clock.getSmallClock(), -1,
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ mLargeClockFrame.addView(clock.getLargeClock());
}
private void updateClockViews(boolean useLargeClock, boolean animate) {
@@ -203,14 +124,14 @@ public class KeyguardClockSwitch extends RelativeLayout {
int direction = 1;
float statusAreaYTranslation;
if (useLargeClock) {
- out = mClockFrame;
+ out = mSmallClockFrame;
in = mLargeClockFrame;
if (indexOfChild(in) == -1) addView(in);
direction = -1;
- statusAreaYTranslation = mClockFrame.getTop() - mStatusArea.getTop()
+ statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop()
+ mSmartspaceTopOffset;
} else {
- in = mClockFrame;
+ in = mSmallClockFrame;
out = mLargeClockFrame;
statusAreaYTranslation = 0f;
@@ -269,18 +190,6 @@ public class KeyguardClockSwitch extends RelativeLayout {
}
/**
- * Set the amount (ratio) that the device has transitioned to doze.
- *
- * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
- */
- public void setDarkAmount(float darkAmount) {
- mDarkAmount = darkAmount;
- if (mClockPlugin != null) {
- mClockPlugin.setDarkAmount(darkAmount);
- }
- }
-
- /**
* Display the desired clock and hide the other one
*
* @return true if desired clock appeared and false if it was already visible
@@ -311,64 +220,11 @@ public class KeyguardClockSwitch extends RelativeLayout {
mChildrenAreLaidOut = true;
}
- public Paint getPaint() {
- return mClockView.getPaint();
- }
-
- public int getCurrentTextColor() {
- return mClockView.getCurrentTextColor();
- }
-
- public float getTextSize() {
- return mClockView.getTextSize();
- }
-
- /**
- * Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
- */
- public void refresh() {
- if (mClockPlugin != null) {
- mClockPlugin.onTimeTick();
- }
- }
-
- /**
- * Notifies that the time zone has changed.
- */
- public void onTimeZoneChanged(TimeZone timeZone) {
- if (mClockPlugin != null) {
- mClockPlugin.onTimeZoneChanged(timeZone);
- }
- }
-
- /**
- * Notifies that the time format has changed.
- *
- * @param timeFormat "12" for 12-hour format, "24" for 24-hour format
- */
- public void onTimeFormatChanged(String timeFormat) {
- if (mClockPlugin != null) {
- mClockPlugin.onTimeFormatChanged(timeFormat);
- }
- }
-
- void updateColors(ColorExtractor.GradientColors colors) {
- mSupportsDarkText = colors.supportsDarkText();
- mColorPalette = colors.getColorPalette();
- if (mClockPlugin != null) {
- mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
- }
- }
-
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardClockSwitch:");
- pw.println(" mClockPlugin: " + mClockPlugin);
- pw.println(" mClockFrame: " + mClockFrame);
+ pw.println(" mClockFrame: " + mSmallClockFrame);
pw.println(" mLargeClockFrame: " + mLargeClockFrame);
pw.println(" mStatusArea: " + mStatusArea);
- pw.println(" mDarkAmount: " + mDarkAmount);
- pw.println(" mSupportsDarkText: " + mSupportsDarkText);
- pw.println(" mColorPalette: " + Arrays.toString(mColorPalette));
pw.println(" mDisplayedClockSize: " + mDisplayedClockSize);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 6c32a4910c56..eeab538932c0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -22,8 +22,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
-import android.app.WallpaperManager;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.UserHandle;
import android.provider.Settings;
@@ -32,34 +30,30 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
-import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.clock.ClockManager;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.plugins.Clock;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.util.Locale;
-import java.util.TimeZone;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -69,48 +63,24 @@ import javax.inject.Inject;
*/
public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch>
implements Dumpable {
- private static final boolean CUSTOM_CLOCKS_ENABLED = true;
-
private final StatusBarStateController mStatusBarStateController;
- private final SysuiColorExtractor mColorExtractor;
- private final ClockManager mClockManager;
+ private final ClockRegistry mClockRegistry;
private final KeyguardSliceViewController mKeyguardSliceViewController;
private final NotificationIconAreaController mNotificationIconAreaController;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final BatteryController mBatteryController;
private final LockscreenSmartspaceController mSmartspaceController;
- private final Resources mResources;
private final SecureSettings mSecureSettings;
private final DumpManager mDumpManager;
+ private final ClockEventController mClockEventController;
- /**
- * Clock for both small and large sizes
- */
- private AnimatableClockController mClockViewController;
- private FrameLayout mClockFrame; // top aligned clock
- private AnimatableClockController mLargeClockViewController;
+ /** Clock frames for both small and large sizes */
+ private FrameLayout mSmallClockFrame; // top aligned clock
private FrameLayout mLargeClockFrame; // centered clock
@KeyguardClockSwitch.ClockSize
private int mCurrentClockSize = SMALL;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
private int mKeyguardClockTopMargin = 0;
-
- /**
- * Listener for changes to the color palette.
- *
- * The color palette changes when the wallpaper is changed.
- */
- private final ColorExtractor.OnColorsChangedListener mColorsListener =
- (extractor, which) -> {
- if ((which & WallpaperManager.FLAG_LOCK) != 0) {
- mView.updateColors(getGradientColors());
- }
- };
-
- private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
+ private final ClockRegistry.ClockChangeListener mClockChangedListener;
private ViewGroup mStatusArea;
// If set will replace keyguard_slice_view
@@ -119,9 +89,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private boolean mOnlyClock = false;
- private Executor mUiExecutor;
+ private final Executor mUiExecutor;
private boolean mCanShowDoubleLineClock = true;
- private ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
+ private final ContentObserver mDoubleLineClockObserver = new ContentObserver(null) {
@Override
public void onChange(boolean change) {
updateDoubleLineClock();
@@ -142,34 +112,32 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
public KeyguardClockSwitchController(
KeyguardClockSwitch keyguardClockSwitch,
StatusBarStateController statusBarStateController,
- SysuiColorExtractor colorExtractor,
- ClockManager clockManager,
+ ClockRegistry clockRegistry,
KeyguardSliceViewController keyguardSliceViewController,
NotificationIconAreaController notificationIconAreaController,
- BroadcastDispatcher broadcastDispatcher,
- BatteryController batteryController,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
LockscreenSmartspaceController smartspaceController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
SecureSettings secureSettings,
@Main Executor uiExecutor,
- @Main Resources resources,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ ClockEventController clockEventController,
+ FeatureFlags featureFlags) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
- mColorExtractor = colorExtractor;
- mClockManager = clockManager;
+ mClockRegistry = clockRegistry;
mKeyguardSliceViewController = keyguardSliceViewController;
mNotificationIconAreaController = notificationIconAreaController;
- mBroadcastDispatcher = broadcastDispatcher;
- mBatteryController = batteryController;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mSmartspaceController = smartspaceController;
- mResources = resources;
mSecureSettings = secureSettings;
mUiExecutor = uiExecutor;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mDumpManager = dumpManager;
+ mClockEventController = clockEventController;
+
+ mClockRegistry.setEnabled(featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS));
+ mClockChangedListener = () -> {
+ setClock(mClockRegistry.createCurrentClock());
+ };
}
/**
@@ -186,40 +154,18 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
public void onInit() {
mKeyguardSliceViewController.init();
- mClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
+ mSmallClockFrame = mView.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mView.findViewById(R.id.lockscreen_clock_view_large);
- mClockViewController =
- new AnimatableClockController(
- mView.findViewById(R.id.animatable_clock_view),
- mStatusBarStateController,
- mBroadcastDispatcher,
- mBatteryController,
- mKeyguardUpdateMonitor,
- mResources);
- mClockViewController.init();
-
- mLargeClockViewController =
- new AnimatableClockController(
- mView.findViewById(R.id.animatable_clock_view_large),
- mStatusBarStateController,
- mBroadcastDispatcher,
- mBatteryController,
- mKeyguardUpdateMonitor,
- mResources);
- mLargeClockViewController.init();
-
mDumpManager.unregisterDumpable(getClass().toString()); // unregister previous clocks
mDumpManager.registerDumpable(getClass().toString(), this);
}
@Override
protected void onViewAttached() {
- if (CUSTOM_CLOCKS_ENABLED) {
- mClockManager.addOnClockChangedListener(mClockChangedListener);
- }
- mColorExtractor.addOnColorsChangedListener(mColorsListener);
- mView.updateColors(getGradientColors());
+ mClockRegistry.registerClockChangeListener(mClockChangedListener);
+ setClock(mClockRegistry.createCurrentClock());
+ mClockEventController.registerListeners();
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
@@ -242,7 +188,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
ksv.setVisibility(View.GONE);
addSmartspaceView(ksvIndex);
- updateClockLayout();
}
mSecureSettings.registerContentObserverForUser(
@@ -264,11 +209,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
@Override
protected void onViewDetached() {
- if (CUSTOM_CLOCKS_ENABLED) {
- mClockManager.removeOnClockChangedListener(mClockChangedListener);
- }
- mColorExtractor.removeOnColorsChangedListener(mColorsListener);
- mView.setClockPlugin(null, mStatusBarStateController.getState());
+ mClockRegistry.unregisterClockChangeListener(mClockChangedListener);
+ mClockEventController.unregisterListeners();
+ setClock(null);
mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver);
@@ -307,18 +250,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mView.onDensityOrFontScaleChanged();
mKeyguardClockTopMargin =
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
-
- updateClockLayout();
- }
-
- private void updateClockLayout() {
- int largeClockTopMargin = getContext().getResources().getDimensionPixelSize(
- R.dimen.keyguard_large_clock_top_margin)
- - (int) mLargeClockViewController.getBottom();
- RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
- MATCH_PARENT);
- lp.topMargin = largeClockTopMargin;
- mLargeClockFrame.setLayoutParams(lp);
}
/**
@@ -334,44 +265,31 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
boolean appeared = mView.switchToClock(clockSize, animate);
if (animate && appeared && clockSize == LARGE) {
- mLargeClockViewController.animateAppear();
+ getClock().getAnimations().enter();
}
}
- public void animateFoldToAod() {
- if (mClockViewController != null) {
- mClockViewController.animateFoldAppear();
- mLargeClockViewController.animateFoldAppear();
- }
- }
-
- /**
- * If we're presenting a custom clock of just the default one.
- */
- public boolean hasCustomClock() {
- return mView.hasCustomClock();
- }
-
/**
- * Get the clock text size.
+ * Animates the clock view between folded and unfolded states
*/
- public float getClockTextSize() {
- return mView.getTextSize();
+ public void animateFoldToAod(float foldFraction) {
+ Clock clock = getClock();
+ if (clock != null) {
+ clock.getAnimations().fold(foldFraction);
+ }
}
/**
* Refresh clock. Called in response to TIME_TICK broadcasts.
*/
void refresh() {
- if (mClockViewController != null) {
- mClockViewController.refreshTime();
- mLargeClockViewController.refreshTime();
- }
if (mSmartspaceController != null) {
mSmartspaceController.requestSmartspaceUpdate();
}
-
- mView.refresh();
+ Clock clock = getClock();
+ if (clock != null) {
+ clock.getEvents().onTimeTick();
+ }
}
/**
@@ -383,7 +301,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
void updatePosition(int x, float scale, AnimationProperties props, boolean animate) {
x = getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? -x : x;
- PropertyAnimator.setProperty(mClockFrame, AnimatableProperty.TRANSLATION_X,
+ PropertyAnimator.setProperty(mSmallClockFrame, AnimatableProperty.TRANSLATION_X,
x, props, animate);
PropertyAnimator.setProperty(mLargeClockFrame, AnimatableProperty.SCALE_X,
scale, props, animate);
@@ -396,25 +314,39 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
}
- void updateTimeZone(TimeZone timeZone) {
- mView.onTimeZoneChanged(timeZone);
- }
-
/**
* Get y-bottom position of the currently visible clock on the keyguard.
* We can't directly getBottom() because clock changes positions in AOD for burn-in
*/
int getClockBottom(int statusBarHeaderHeight) {
+ Clock clock = getClock();
+ if (clock == null) {
+ return 0;
+ }
+
if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
- View clock = mLargeClockFrame.findViewById(
- com.android.systemui.R.id.animatable_clock_view_large);
int frameHeight = mLargeClockFrame.getHeight();
- int clockHeight = clock.getHeight();
+ int clockHeight = clock.getLargeClock().getHeight();
return frameHeight / 2 + clockHeight / 2;
} else {
- return mClockFrame.findViewById(
- com.android.systemui.R.id.animatable_clock_view).getHeight()
- + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ int clockHeight = clock.getSmallClock().getHeight();
+ return clockHeight + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ }
+ }
+
+ /**
+ * Get the height of the currently visible clock on the keyguard.
+ */
+ int getClockHeight() {
+ Clock clock = getClock();
+ if (clock == null) {
+ return 0;
+ }
+
+ if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
+ return clock.getLargeClock().getHeight();
+ } else {
+ return clock.getSmallClock().getHeight();
}
}
@@ -429,12 +361,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mNotificationIconAreaController.setupAodIcons(nic);
}
- private void setClockPlugin(ClockPlugin plugin) {
- mView.setClockPlugin(plugin, mStatusBarStateController.getState());
+ private void setClock(Clock clock) {
+ mClockEventController.setClock(clock);
+ mView.setClock(clock, mStatusBarStateController.getState());
}
- private ColorExtractor.GradientColors getGradientColors() {
- return mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
+ private Clock getClock() {
+ return mClockEventController.getClock();
}
private int getCurrentLayoutDirection() {
@@ -467,8 +400,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE));
pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock);
- mClockViewController.dump(pw);
- mLargeClockViewController.dump(pw);
+ Clock clock = getClock();
+ if (clock != null) {
+ clock.dump(pw);
+ }
}
}
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/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index cb3172dabdb1..83e23bd52f19 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -17,14 +17,11 @@
package com.android.keyguard;
import android.content.Context;
-import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.GridLayout;
-import androidx.core.graphics.ColorUtils;
-
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -46,7 +43,6 @@ public class KeyguardStatusView extends GridLayout {
private View mMediaHostContainer;
private float mDarkAmount = 0;
- private int mTextColor;
public KeyguardStatusView(Context context) {
this(context, null, 0);
@@ -71,7 +67,6 @@ public class KeyguardStatusView extends GridLayout {
}
mKeyguardSlice = findViewById(R.id.keyguard_slice_view);
- mTextColor = mClockView.getCurrentTextColor();
mMediaHostContainer = findViewById(R.id.status_view_media_container);
@@ -83,15 +78,12 @@ public class KeyguardStatusView extends GridLayout {
return;
}
mDarkAmount = darkAmount;
- mClockView.setDarkAmount(darkAmount);
CrossFadeHelper.fadeOut(mMediaHostContainer, darkAmount);
updateDark();
}
void updateDark() {
- final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
mKeyguardSlice.setDarkAmount(mDarkAmount);
- mClockView.setTextColor(blendedTextColor);
}
/** Sets a translationY value on every child view except for the media view. */
@@ -113,7 +105,6 @@ public class KeyguardStatusView extends GridLayout {
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardStatusView:");
pw.println(" mDarkAmount: " + mDarkAmount);
- pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
if (mClockView != null) {
mClockView.dump(pw, args);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 014d08288158..c715a4eaef2b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -30,8 +30,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
-import java.util.TimeZone;
-
import javax.inject.Inject;
/**
@@ -96,13 +94,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
/**
- * The amount we're in doze.
- */
- public void setDarkAmount(float darkAmount) {
- mView.setDarkAmount(darkAmount);
- }
-
- /**
* Set which clock should be displayed on the keyguard. The other one will be automatically
* hidden.
*/
@@ -114,16 +105,10 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
* Performs fold to aod animation of the clocks (changes font weight from bold to thin).
* This animation is played when AOD is enabled and foldable device is fully folded, it is
* displayed on the outer screen
+ * @param foldFraction current fraction of fold animation complete
*/
- public void animateFoldToAod() {
- mKeyguardClockSwitchController.animateFoldToAod();
- }
-
- /**
- * If we're presenting a custom clock of just the default one.
- */
- public boolean hasCustomClock() {
- return mKeyguardClockSwitchController.hasCustomClock();
+ public void animateFoldToAod(float foldFraction) {
+ mKeyguardClockSwitchController.animateFoldToAod(foldFraction);
}
/**
@@ -143,24 +128,11 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
/**
- * Set pivot x.
- */
- public void setPivotX(float pivot) {
- mView.setPivotX(pivot);
- }
-
- /**
- * Set pivot y.
- */
- public void setPivotY(float pivot) {
- mView.setPivotY(pivot);
- }
-
- /**
- * Get the clock text size.
+ * Update the pivot position based on the parent view
*/
- public float getClockTextSize() {
- return mKeyguardClockSwitchController.getClockTextSize();
+ public void updatePivot(float parentWidth, float parentHeight) {
+ mView.setPivotX(parentWidth / 2f);
+ mView.setPivotY(mKeyguardClockSwitchController.getClockHeight() / 2f);
}
/**
@@ -240,11 +212,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
@Override
- public void onTimeZoneChanged(TimeZone timeZone) {
- mKeyguardClockSwitchController.updateTimeZone(timeZone);
- }
-
- @Override
public void onKeyguardVisibilityChanged(boolean showing) {
if (showing) {
if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
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/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 061a05dcfb72..8de8e020a359 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -61,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;
@@ -379,6 +380,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);
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 66b6a5078462..56bb53b567be 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
@@ -81,12 +84,15 @@ public class Flags {
public static final ResourceBooleanFlag FACE_SCANNING_ANIM =
new ResourceBooleanFlag(205, R.bool.config_enableFaceScanningAnimation);
+
/**
* Whether the KeyguardBottomArea(View|Controller) should use the modern architecture or the old
* one.
*/
public static final BooleanFlag MODERN_BOTTOM_AREA = new BooleanFlag(206, false);
+ public static final BooleanFlag LOCKSCREEN_CUSTOM_CLOCKS = new BooleanFlag(207, false);
+
/***************************************/
// 300 - power menu
public static final BooleanFlag POWER_MENU_LITE =
@@ -157,7 +163,7 @@ public class Flags {
/***************************************/
// 900 - media
- public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
+ public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true);
public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);
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 72dd36fec349..4ee89854987c 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);
}
@@ -2497,10 +2497,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"));
@@ -3161,9 +3169,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);
}
}
@@ -3204,8 +3212,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/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 30ba476abce2..88a1b17e37fe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -1096,7 +1096,7 @@ class MediaDataManager(
fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaEntries.remove(key)
- if (useMediaResumption && removed?.resumeAction != null && removed?.isLocalSession()) {
+ if (useMediaResumption && removed?.resumeAction != null && removed.isLocalSession()) {
Log.d(TAG, "Not removing $key because resumable")
// Move to resume key (aka package name) if that key doesn't already exist.
val resumeAction = getResumeMediaAction(removed.resumeAction!!)
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 2c5d1b95654e..e6116c16ec4a 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 19b9757bb772..bd6a5fc8661b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -83,13 +83,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;
import javax.inject.Provider;
@@ -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<>();
@@ -220,6 +214,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();
@@ -235,7 +231,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private boolean mIsNavBarShownTransiently;
private boolean mIsBackGestureAllowed;
private boolean mGestureBlockingActivityRunning;
- private boolean mIsInPipMode;
private boolean mIsNewBackAffordanceEnabled;
private InputMonitor mInputMonitor;
@@ -304,6 +299,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
}
};
+ private final Consumer<Boolean> mOnIsInPipStateChangedListener =
+ (isInPip) -> mIsInPip = isInPip;
EdgeBackGestureHandler(
Context context,
@@ -318,6 +315,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
ViewConfiguration viewConfiguration,
WindowManager windowManager,
IWindowManager windowManagerService,
+ Optional<Pip> pipOptional,
FalsingManager falsingManager,
LatencyTracker latencyTracker,
Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
@@ -335,6 +333,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mViewConfiguration = viewConfiguration;
mWindowManager = windowManager;
mWindowManagerService = windowManagerService;
+ mPipOptional = pipOptional;
mFalsingManager = falsingManager;
mLatencyTracker = latencyTracker;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
@@ -495,6 +494,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(
@@ -512,6 +512,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(
@@ -681,7 +683,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;
}
@@ -934,7 +936,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);
@@ -1003,6 +1005,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 Provider<BackGestureTfClassifierProvider>
@@ -1021,6 +1024,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
ViewConfiguration viewConfiguration,
WindowManager windowManager,
IWindowManager windowManagerService,
+ Optional<Pip> pipOptional,
FalsingManager falsingManager,
LatencyTracker latencyTracker,
Provider<BackGestureTfClassifierProvider>
@@ -1037,6 +1041,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mViewConfiguration = viewConfiguration;
mWindowManager = windowManager;
mWindowManagerService = windowManagerService;
+ mPipOptional = pipOptional;
mFalsingManager = falsingManager;
mLatencyTracker = latencyTracker;
mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
@@ -1058,6 +1063,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mViewConfiguration,
mWindowManager,
mWindowManagerService,
+ mPipOptional,
mFalsingManager,
mLatencyTracker,
mBackGestureTfClassifierProviderProvider,
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/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 19eb168fddc0..89a15f65e98f 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 1ffa6f41d8a3..decc02c98b61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -65,7 +65,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();
@@ -319,7 +319,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/connectivity/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
index 9d8667a3ccb0..f2014e9deb72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalController.java
@@ -110,7 +110,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
@Override
public void onMobileStatusChanged(boolean updateTelephony,
MobileStatus mobileStatus) {
- if (Log.isLoggable(mTag, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(mTag, "onMobileStatusChanged="
+ " updateTelephony=" + updateTelephony
+ " mobileStatus=" + mobileStatus.toString());
@@ -717,7 +717,7 @@ public class MobileSignalController extends SignalController<MobileState, Mobile
* This will call listeners if necessary.
*/
private void updateTelephony() {
- if (Log.isLoggable(mTag, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(mTag, "updateTelephonySignalStrength: hasService="
+ mCurrentState.isInService()
+ " ss=" + mCurrentState.signalStrength
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/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 72aa89aa382d..6a01df61705d 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, PipelineDumpable {
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, PipelineDumpable {
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, PipelineDumpable {
updateDismissInterceptors(entry);
if (isDismissIntercepted(entry)) {
- mLogger.logNotifDismissedIntercepted(entry.getKey());
+ mLogger.logNotifDismissedIntercepted(entry);
continue;
}
@@ -299,7 +304,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
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, PipelineDumpable {
// 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, PipelineDumpable {
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, PipelineDumpable {
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, PipelineDumpable {
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, PipelineDumpable {
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, PipelineDumpable {
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, PipelineDumpable {
}
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, PipelineDumpable {
}
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, PipelineDumpable {
}
}
} 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, PipelineDumpable {
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, PipelineDumpable {
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, PipelineDumpable {
entries,
true,
"\t\t"));
+
+ pw.println("\n\tmNotificationsWithoutRankings: " + mNotificationsWithoutRankings.size());
+ for (String key : mNotificationsWithoutRankings) {
+ pw.println("\t * : " + key);
+ }
}
@Override
@@ -924,17 +949,17 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
// 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/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 083759c1d757..26d2ee349f50 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, PipelineDumpable {
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()
@@ -1084,7 +1080,7 @@ public class ShadeListBuilder implements Dumpable, PipelineDumpable {
if (!Objects.equals(curr, prev)) {
mLogger.logEntryAttachStateChanged(
mIterationCount,
- entry.getKey(),
+ entry,
prev.getParent(),
curr.getParent());
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/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index 9e8b35af1bce..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
@@ -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/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/collection/provider/SectionHeaderVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
index 2148d3bb336a..82c7aae08f6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
@@ -17,8 +17,8 @@
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
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
index 50e7d1ce4ba0..7b9483022fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
@@ -43,4 +43,4 @@ class SectionStyleProvider @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 34d25cf9c3be..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
@@ -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/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/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index 3616f8faee1e..81cf14646465 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -57,7 +57,7 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> {
@NonNull StageCallback callback) {
RowContentBindParams params = getStageParams(entry);
- mLogger.logStageParams(entry.getKey(), params.toString());
+ mLogger.logStageParams(entry, params);
// Resolve content to bind/unbind.
@InflationFlag int inflationFlags = params.getContentViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
index 29cce3375c8a..f9923b2254d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -19,17 +19,19 @@ package com.android.systemui.statusbar.notification.row
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class RowContentBindStageLogger @Inject constructor(
@NotificationLog private val buffer: LogBuffer
) {
- fun logStageParams(notifKey: String, stageParams: String) {
+ fun logStageParams(entry: NotificationEntry, stageParams: RowContentBindParams) {
buffer.log(TAG, INFO, {
- str1 = notifKey
- str2 = stageParams
+ str1 = entry.logKey
+ str2 = stageParams.toString()
}, {
- "Invalidated notif $str1 with params: \n$str2"
+ "Invalidated notif $str1 with params: $str2"
})
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 213f00b8e03d..2fd02d9f1cd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -748,19 +748,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
- private void logHunSkippedForUnexpectedState(String key, boolean expected, boolean actual) {
+ private void logHunSkippedForUnexpectedState(ExpandableNotificationRow enr,
+ boolean expected, boolean actual) {
if (mLogger == null) return;
- mLogger.hunSkippedForUnexpectedState(key, expected, actual);
+ mLogger.hunSkippedForUnexpectedState(enr.getEntry(), expected, actual);
}
- private void logHunAnimationSkipped(String key, String reason) {
+ private void logHunAnimationSkipped(ExpandableNotificationRow enr, String reason) {
if (mLogger == null) return;
- mLogger.hunAnimationSkipped(key, reason);
+ mLogger.hunAnimationSkipped(enr.getEntry(), reason);
}
- private void logHunAnimationEventAdded(String key, int type) {
+ private void logHunAnimationEventAdded(ExpandableNotificationRow enr, int type) {
if (mLogger == null) return;
- mLogger.hunAnimationEventAdded(key, type);
+ mLogger.hunAnimationEventAdded(enr.getEntry(), type);
}
private void onDrawDebug(Canvas canvas) {
@@ -3174,7 +3175,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (isHeadsUp != row.isHeadsUp()) {
// For cases where we have a heads up showing and appearing again we shouldn't
// do the animations at all.
- logHunSkippedForUnexpectedState(key, isHeadsUp, row.isHeadsUp());
+ logHunSkippedForUnexpectedState(row, isHeadsUp, row.isHeadsUp());
continue;
}
int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
@@ -3192,7 +3193,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (row.isChildInGroup()) {
// We can otherwise get stuck in there if it was just isolated
row.setHeadsUpAnimatingAway(false);
- logHunAnimationSkipped(key, "row is child in group");
+ logHunAnimationSkipped(row, "row is child in group");
continue;
}
} else {
@@ -3200,7 +3201,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (viewState == null) {
// A view state was never generated for this view, so we don't need to animate
// this. This may happen with notification children.
- logHunAnimationSkipped(key, "row has no viewState");
+ logHunAnimationSkipped(row, "row has no viewState");
continue;
}
if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
@@ -3224,7 +3225,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
+ " onBottom=" + onBottom
+ " row=" + row.getEntry().getKey());
}
- logHunAnimationEventAdded(key, type);
+ logHunAnimationEventAdded(row, type);
}
mHeadsUpChangeAnimations.clear();
mAddedHeadsUpChildren.clear();
@@ -4360,8 +4361,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/**
* Update colors of "dismiss" and "empty shade" views.
- *
- * @param lightTheme True if light theme should be used.
*/
@ShadeViewRefactor(RefactorComponent.DECORATOR)
void updateDecorViews() {
@@ -4777,8 +4776,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (SPEW) {
Log.v(TAG, "generateHeadsUpAnimation: previous hun appear animation cancelled");
}
- logHunAnimationSkipped(row.getEntry().getKey(),
- "previous hun appear animation cancelled");
+ logHunAnimationSkipped(row, "previous hun appear animation cancelled");
return;
}
mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 04bf62104f66..5f79c0e3913a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -3,21 +3,27 @@ package com.android.systemui.statusbar.notification.stack
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.*
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER
import javax.inject.Inject
class NotificationStackScrollLogger @Inject constructor(
@NotificationHeadsUpLog private val buffer: LogBuffer
) {
- fun hunAnimationSkipped(key: String, reason: String) {
+ fun hunAnimationSkipped(entry: NotificationEntry, reason: String) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
str2 = reason
}, {
"heads up animation skipped: key: $str1 reason: $str2"
})
}
- fun hunAnimationEventAdded(key: String, type: Int) {
+ fun hunAnimationEventAdded(entry: NotificationEntry, type: Int) {
val reason: String
reason = if (type == ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
"HEADS_UP_DISAPPEAR"
@@ -33,16 +39,16 @@ class NotificationStackScrollLogger @Inject constructor(
type.toString()
}
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
str2 = reason
}, {
"heads up animation added: $str1 with type $str2"
})
}
- fun hunSkippedForUnexpectedState(key: String, expected: Boolean, actual: Boolean) {
+ fun hunSkippedForUnexpectedState(entry: NotificationEntry, expected: Boolean, actual: Boolean) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
bool1 = expected
bool2 = actual
}, {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index 77377af9ddfb..cb4a0884fea4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -3,6 +3,7 @@ package com.android.systemui.statusbar.notification.stack
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class StackStateLogger @Inject constructor(
@@ -10,7 +11,7 @@ class StackStateLogger @Inject constructor(
) {
fun logHUNViewDisappearing(key: String) {
buffer.log(TAG, LogLevel.INFO, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up view disappearing $str1 "
})
@@ -18,7 +19,7 @@ class StackStateLogger @Inject constructor(
fun logHUNViewAppearing(key: String) {
buffer.log(TAG, LogLevel.INFO, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up notification view appearing $str1 "
})
@@ -26,7 +27,7 @@ class StackStateLogger @Inject constructor(
fun logHUNViewDisappearingWithRemoveEvent(key: String) {
buffer.log(TAG, LogLevel.ERROR, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE"
})
@@ -34,7 +35,7 @@ class StackStateLogger @Inject constructor(
fun logHUNViewAppearingWithAddEvent(key: String) {
buffer.log(TAG, LogLevel.ERROR, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up view disappearing $str1 for ANIMATION_TYPE_ADD"
})
@@ -42,7 +43,7 @@ class StackStateLogger @Inject constructor(
fun disappearAnimationEnded(key: String) {
buffer.log(TAG, LogLevel.INFO, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up notification disappear animation ended $str1 "
})
@@ -50,7 +51,7 @@ class StackStateLogger @Inject constructor(
fun appearAnimationEnded(key: String) {
buffer.log(TAG, LogLevel.INFO, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up notification appear animation ended $str1 "
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 414bc84fcd85..b43e9df79241 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -28,17 +28,13 @@ import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNL
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
@@ -46,13 +42,8 @@ import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.provider.MediaStore;
-import android.service.media.CameraPrewarmService;
import android.service.quickaccesswallet.GetWalletCardsError;
import android.service.quickaccesswallet.GetWalletCardsResponse;
import android.service.quickaccesswallet.QuickAccessWalletClient;
@@ -172,20 +163,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private KeyguardAffordanceHelper mAffordanceHelper;
private FalsingManager mFalsingManager;
private boolean mUserSetupComplete;
- private boolean mPrewarmBound;
- private Messenger mPrewarmMessenger;
- private final ServiceConnection mPrewarmConnection = new ServiceConnection() {
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mPrewarmMessenger = new Messenger(service);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mPrewarmMessenger = null;
- }
- };
private boolean mLeftIsVoiceAssist;
private Drawable mLeftAssistIcon;
@@ -602,46 +579,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
}
- public void bindCameraPrewarmService() {
- Intent intent = getCameraIntent();
- ActivityInfo targetInfo = mActivityIntentHelper.getTargetActivityInfo(intent,
- KeyguardUpdateMonitor.getCurrentUser(), true /* onlyDirectBootAware */);
- if (targetInfo != null && targetInfo.metaData != null) {
- String clazz = targetInfo.metaData.getString(
- MediaStore.META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE);
- if (clazz != null) {
- Intent serviceIntent = new Intent();
- serviceIntent.setClassName(targetInfo.packageName, clazz);
- serviceIntent.setAction(CameraPrewarmService.ACTION_PREWARM);
- try {
- if (getContext().bindServiceAsUser(serviceIntent, mPrewarmConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
- new UserHandle(UserHandle.USER_CURRENT))) {
- mPrewarmBound = true;
- }
- } catch (SecurityException e) {
- Log.w(TAG, "Unable to bind to prewarm service package=" + targetInfo.packageName
- + " class=" + clazz, e);
- }
- }
- }
- }
-
- public void unbindCameraPrewarmService(boolean launched) {
- if (mPrewarmBound) {
- if (mPrewarmMessenger != null && launched) {
- try {
- mPrewarmMessenger.send(Message.obtain(null /* handler */,
- CameraPrewarmService.MSG_CAMERA_FIRED));
- } catch (RemoteException e) {
- Log.w(TAG, "Error sending camera fired message", e);
- }
- }
- mContext.unbindService(mPrewarmConnection);
- mPrewarmBound = false;
- }
- }
-
public void launchCamera(String source) {
final Intent intent = getCameraIntent();
intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
@@ -651,8 +588,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
AsyncTask.execute(new Runnable() {
@Override
public void run() {
- int result = ActivityManager.START_CANCELED;
-
// Normally an activity will set it's requested rotation
// animation on its window. However when launching an activity
// causes the orientation to change this is too late. In these cases
@@ -666,7 +601,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
o.setRotationAnimationHint(
WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
try {
- result = ActivityTaskManager.getService().startActivityAsUser(
+ ActivityTaskManager.getService().startActivityAsUser(
null, getContext().getBasePackageName(),
getContext().getAttributionTag(), intent,
intent.resolveTypeIfNeeded(getContext().getContentResolver()),
@@ -675,25 +610,12 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
} catch (RemoteException e) {
Log.w(TAG, "Unable to start camera activity", e);
}
- final boolean launched = isSuccessfulLaunch(result);
- post(new Runnable() {
- @Override
- public void run() {
- unbindCameraPrewarmService(launched);
- }
- });
}
});
} else {
// We need to delay starting the activity because ResolverActivity finishes itself if
// launched behind lockscreen.
- mActivityStarter.startActivity(intent, false /* dismissShade */,
- new ActivityStarter.Callback() {
- @Override
- public void onActivityStarted(int resultCode) {
- unbindCameraPrewarmService(isSuccessfulLaunch(resultCode));
- }
- });
+ mActivityStarter.startActivity(intent, false /* dismissShade */);
}
}
@@ -705,12 +627,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
dozeTimeTick();
}
- private static boolean isSuccessfulLaunch(int result) {
- return result == ActivityManager.START_SUCCESS
- || result == ActivityManager.START_DELIVERED_TO_TOP
- || result == ActivityManager.START_TASK_TO_FRONT;
- }
-
public void launchLeftAffordance() {
if (mLeftIsVoiceAssist) {
launchVoiceAssist();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 967b807f6f29..b435055d1b16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -276,11 +276,6 @@ public class NotificationPanelViewController extends PanelViewController {
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final NotificationIconAreaController mNotificationIconAreaController;
- // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
- // changed.
- private static final int CAP_HEIGHT = 1456;
- private static final int FONT_HEIGHT = 2163;
-
/**
* Maximum time before which we will expand the panel even for slow motions when getting a
* touch passed over from launcher.
@@ -1123,13 +1118,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- /**
- * Returns if there's a custom clock being presented.
- */
- public boolean hasCustomClock() {
- return mKeyguardStatusViewController.hasCustomClock();
- }
-
private void setCentralSurfaces(CentralSurfaces centralSurfaces) {
// TODO: this can be injected.
mCentralSurfaces = centralSurfaces;
@@ -1673,7 +1661,6 @@ public class NotificationPanelViewController extends PanelViewController {
setQsExpansionEnabled();
}
- @Override
public void resetViews(boolean animate) {
mIsLaunchTransitionFinished = false;
mBlockTouches = false;
@@ -4009,9 +3996,10 @@ public class NotificationPanelViewController extends PanelViewController {
endAction.run();
}
})
+ .setUpdateListener(anim -> {
+ mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
+ })
.start();
-
- mKeyguardStatusViewController.animateFoldToAod();
}
/**
@@ -4593,13 +4581,6 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void onSwipingStarted(boolean rightIcon) {
mFalsingCollector.onAffordanceSwipingStarted(rightIcon);
- boolean
- camera =
- mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
- : rightIcon;
- if (camera) {
- mKeyguardBottomArea.bindCameraPrewarmService();
- }
mView.requestDisallowInterceptTouchEvent(true);
mOnlyAffordanceInThisMotion = true;
mQsTracking = false;
@@ -4608,7 +4589,6 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void onSwipingAborted() {
mFalsingCollector.onAffordanceSwipingAborted();
- mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
}
@Override
@@ -4873,7 +4853,6 @@ public class NotificationPanelViewController extends PanelViewController {
public void onDozeAmountChanged(float linearAmount, float amount) {
mInterpolatedDarkAmount = amount;
mLinearDarkAmount = linearAmount;
- mKeyguardStatusViewController.setDarkAmount(mInterpolatedDarkAmount);
mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
positionClockAndNotifications();
}
@@ -4982,11 +4961,8 @@ public class NotificationPanelViewController extends PanelViewController {
updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount());
setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
- // Update Clock Pivot
- mKeyguardStatusViewController.setPivotX(mView.getWidth() / 2);
- mKeyguardStatusViewController.setPivotY(
- (FONT_HEIGHT - CAP_HEIGHT) / 2048f
- * mKeyguardStatusViewController.getClockTextSize());
+ // Update Clock Pivot (used by anti-burnin transformations)
+ mKeyguardStatusViewController.updatePivot(mView.getWidth(), mView.getHeight());
// Calculate quick setting heights.
int oldMaxHeight = mQsMaxExpansionHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 9f0ecb9d4096..ed12b00cc644 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -1163,8 +1163,6 @@ public abstract class PanelViewController {
mTouchDisabled ? "T" : "f"));
}
- public abstract void resetViews(boolean animate);
-
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 6e331bc13294..fa701e704c21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -89,6 +89,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;
@@ -97,13 +108,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
@@ -322,7 +330,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();
@@ -404,11 +415,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/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 670ba96ca1ff..25892d9db080 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -24,7 +24,6 @@ import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedul
import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT;
import android.animation.Animator;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.Fragment;
@@ -395,14 +394,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
state |= DISABLE_CLOCK;
}
-
- // The shelf will be hidden when dozing with a custom clock, we must show notification
- // icons in this occasion.
- if (mStatusBarStateController.isDozing()
- && mNotificationPanelViewController.hasCustomClock()) {
- state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO;
- }
-
if (mOngoingCallController.hasOngoingCall()) {
state &= ~DISABLE_ONGOING_CALL_CHIP;
} else {
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/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index e5d5ed48cc9f..699414c27059 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -145,7 +145,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);
@@ -186,7 +186,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/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
new file mode 100644
index 000000000000..4f3995252b54
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.keyguard
+
+import android.content.BroadcastReceiver
+import android.testing.AndroidTestingRunner
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockAnimations
+import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import java.util.TimeZone
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ClockEventControllerTest : SysuiTestCase() {
+
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var batteryController: BatteryController
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var animations: ClockAnimations
+ @Mock private lateinit var events: ClockEvents
+ @Mock private lateinit var clock: Clock
+
+ private lateinit var clockEventController: ClockEventController
+
+ @Before
+ fun setUp() {
+ whenever(clock.smallClock).thenReturn(TextView(context))
+ whenever(clock.largeClock).thenReturn(TextView(context))
+ whenever(clock.events).thenReturn(events)
+ whenever(clock.animations).thenReturn(animations)
+
+ clockEventController = ClockEventController(
+ statusBarStateController,
+ broadcastDispatcher,
+ batteryController,
+ keyguardUpdateMonitor,
+ configurationController,
+ context.resources,
+ context
+ )
+ }
+
+ @Test
+ fun clockSet_validateInitialization() {
+ clockEventController.clock = clock
+
+ verify(clock).initialize(any(), anyFloat(), anyFloat())
+ }
+
+ @Test
+ fun clockUnset_validateState() {
+ clockEventController.clock = clock
+ clockEventController.clock = null
+
+ assertEquals(clockEventController.clock, null)
+ }
+
+ @Test
+ fun themeChanged_verifyClockPaletteUpdated() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
+ verify(configurationController).addCallback(capture(captor))
+ captor.value.onThemeChanged()
+
+ verify(events).onColorPaletteChanged(any())
+ }
+
+ @Test
+ fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+
+ verify(animations).charge()
+ }
+
+ @Test
+ fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+
+ verify(animations, times(1)).charge()
+ }
+
+ @Test
+ fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(false)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, true)
+
+ verify(animations, never()).charge()
+ }
+
+ @Test
+ fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
+ verify(batteryController).addCallback(capture(batteryCaptor))
+ val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
+ keyguardCaptor.value.onKeyguardVisibilityChanged(true)
+ batteryCaptor.value.onBatteryLevelChanged(10, false, false)
+
+ verify(animations, never()).charge()
+ }
+
+ @Test
+ fun localeCallback_verifyClockNotified() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<BroadcastReceiver>()
+ verify(broadcastDispatcher).registerReceiver(
+ capture(captor), any(), eq(null), eq(null), anyInt(), eq(null)
+ )
+ captor.value.onReceive(context, mock())
+
+ verify(events).onLocaleChanged(any())
+ }
+
+ @Test
+ fun keyguardCallback_visibilityChanged_clockDozeCalled() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(captor))
+
+ captor.value.onKeyguardVisibilityChanged(true)
+ verify(animations, never()).doze(0f)
+
+ captor.value.onKeyguardVisibilityChanged(false)
+ verify(animations, times(1)).doze(0f)
+ }
+
+ @Test
+ fun keyguardCallback_timeFormat_clockNotified() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(captor))
+ captor.value.onTimeFormatChanged("12h")
+
+ verify(events).onTimeFormatChanged(false)
+ }
+
+ @Test
+ fun keyguardCallback_timezoneChanged_clockNotified() {
+ val mockTimeZone = mock<TimeZone>()
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(captor))
+ captor.value.onTimeZoneChanged(mockTimeZone)
+
+ verify(events).onTimeZoneChanged(mockTimeZone)
+ }
+
+ @Test
+ fun keyguardCallback_userSwitched_clockNotified() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
+ verify(keyguardUpdateMonitor).registerCallback(capture(captor))
+ captor.value.onUserSwitchComplete(10)
+
+ verify(events).onTimeFormatChanged(false)
+ }
+
+ @Test
+ fun keyguardCallback_verifyKeyguardChanged() {
+ clockEventController.clock = clock
+ clockEventController.registerListeners()
+
+ val captor = argumentCaptor<StatusBarStateController.StateListener>()
+ verify(statusBarStateController).addCallback(capture(captor))
+ captor.value.onDozeAmountChanged(0.4f, 0.6f)
+
+ verify(animations).doze(0.4f)
+ }
+
+ @Test
+ fun unregisterListeners_validate() {
+ clockEventController.unregisterListeners()
+ verify(broadcastDispatcher).unregisterReceiver(any())
+ verify(configurationController).removeCallback(any())
+ verify(batteryController).removeCallback(any())
+ verify(keyguardUpdateMonitor).removeCallback(any())
+ verify(statusBarStateController).removeCallback(any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 8b9a1e022d26..635ee9ea1a2f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -19,7 +19,6 @@ package com.android.keyguard;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -41,23 +40,19 @@ import android.widget.RelativeLayout;
import androidx.test.filters.SmallTest;
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.keyguard.clock.ClockManager;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.plugins.Clock;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.shared.clocks.ClockRegistry;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
@@ -79,22 +74,12 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
- private SysuiColorExtractor mColorExtractor;
- @Mock
- private ClockManager mClockManager;
+ private ClockRegistry mClockRegistry;
@Mock
KeyguardSliceViewController mKeyguardSliceViewController;
@Mock
NotificationIconAreaController mNotificationIconAreaController;
@Mock
- BroadcastDispatcher mBroadcastDispatcher;
- @Mock
- BatteryController mBatteryController;
- @Mock
- KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- KeyguardBypassController mBypassController;
- @Mock
LockscreenSmartspaceController mSmartspaceController;
@Mock
@@ -102,11 +87,11 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
@Mock
KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock
- private ClockPlugin mClockPlugin;
- @Mock
- ColorExtractor.GradientColors mGradientColors;
+ private Clock mClock;
@Mock
DumpManager mDumpManager;
+ @Mock
+ ClockEventController mClockEventController;
@Mock
private NotificationIconContainer mNotificationIcons;
@@ -118,6 +103,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
private FrameLayout mLargeClockFrame;
@Mock
private SecureSettings mSecureSettings;
+ @Mock
+ private FeatureFlags mFeatureFlags;
private final View mFakeSmartspaceView = new View(mContext);
@@ -136,8 +123,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mView.getContext()).thenReturn(getContext());
when(mView.getResources()).thenReturn(mResources);
- when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
- when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView);
when(mView.findViewById(R.id.lockscreen_clock_view_large)).thenReturn(mLargeClockFrame);
when(mClockView.getContext()).thenReturn(getContext());
when(mLargeClockView.getContext()).thenReturn(getContext());
@@ -148,23 +133,20 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
mController = new KeyguardClockSwitchController(
mView,
mStatusBarStateController,
- mColorExtractor,
- mClockManager,
+ mClockRegistry,
mKeyguardSliceViewController,
mNotificationIconAreaController,
- mBroadcastDispatcher,
- mBatteryController,
- mKeyguardUpdateMonitor,
mSmartspaceController,
mKeyguardUnlockAnimationController,
mSecureSettings,
mExecutor,
- mResources,
- mDumpManager
+ mDumpManager,
+ mClockEventController,
+ mFeatureFlags
);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
+ when(mClockRegistry.createCurrentClock()).thenReturn(mClock);
mSliceView = new View(getContext());
when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
@@ -211,20 +193,20 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
verifyAttachment(times(1));
listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
- verify(mColorExtractor).removeOnColorsChangedListener(
- any(ColorExtractor.OnColorsChangedListener.class));
+ verify(mClockEventController).unregisterListeners();
}
@Test
public void testPluginPassesStatusBarState() {
- ArgumentCaptor<ClockManager.ClockChangedListener> listenerArgumentCaptor =
- ArgumentCaptor.forClass(ClockManager.ClockChangedListener.class);
+ ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
mController.init();
- verify(mClockManager).addOnClockChangedListener(listenerArgumentCaptor.capture());
+ verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
- listenerArgumentCaptor.getValue().onClockChanged(mClockPlugin);
- verify(mView).setClockPlugin(mClockPlugin, StatusBarState.SHADE);
+ listenerArgumentCaptor.getValue().onClockChanged();
+ verify(mView, times(2)).setClock(mClock, StatusBarState.SHADE);
+ verify(mClockEventController, times(2)).setClock(mClock);
}
@Test
@@ -281,10 +263,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
}
private void verifyAttachment(VerificationMode times) {
- verify(mClockManager, times).addOnClockChangedListener(
- any(ClockManager.ClockChangedListener.class));
- verify(mColorExtractor, times).addOnColorsChangedListener(
- any(ColorExtractor.OnColorsChangedListener.class));
- verify(mView, times).updateColors(mGradientColors);
+ verify(mClockRegistry, times).registerClockChangeListener(
+ any(ClockRegistry.ClockChangeListener.class));
+ verify(mClockEventController, times).registerListeners();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 6c6f0acd7085..a0295d09826f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
@@ -24,56 +23,61 @@ import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.TestCase.assertEquals;
+
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.graphics.Color;
-import android.graphics.Paint.Style;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
-import android.widget.TextClock;
+import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.ClockPlugin;
-import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.plugins.Clock;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
// Need to run on the main thread because KeyguardSliceView$Row init checks for
// the main thread before acquiring a wake lock. This class is constructed when
-// the keyguard_clcok_switch layout is inflated.
+// the keyguard_clock_switch layout is inflated.
@RunWithLooper(setAsMainLooper = true)
public class KeyguardClockSwitchTest extends SysuiTestCase {
- private FrameLayout mClockFrame;
+ @Mock
+ ViewGroup mMockKeyguardSliceView;
+
+ @Mock
+ Clock mClock;
+
+ private FrameLayout mSmallClockFrame;
private FrameLayout mLargeClockFrame;
- private TextClock mBigClock;
- private AnimatableClockView mClockView;
- private AnimatableClockView mLargeClockView;
- View mMockKeyguardSliceView;
KeyguardClockSwitch mKeyguardClockSwitch;
@Before
public void setUp() {
- mMockKeyguardSliceView = mock(KeyguardSliceView.class);
+ MockitoAnnotations.initMocks(this);
when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area))
.thenReturn(mMockKeyguardSliceView);
+ when(mClock.getSmallClock()).thenReturn(new TextView(getContext()));
+ when(mClock.getLargeClock()).thenReturn(new TextView(getContext()));
+
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
@@ -93,164 +97,68 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
});
mKeyguardClockSwitch =
(KeyguardClockSwitch) layoutInflater.inflate(R.layout.keyguard_clock_switch, null);
- mClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
- mClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view);
+ mSmallClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view);
mLargeClockFrame = mKeyguardClockSwitch.findViewById(R.id.lockscreen_clock_view_large);
- mLargeClockView = mKeyguardClockSwitch.findViewById(R.id.animatable_clock_view_large);
- mBigClock = new TextClock(getContext());
mKeyguardClockSwitch.mChildrenAreLaidOut = true;
- MockitoAnnotations.initMocks(this);
- }
-
- @Test
- public void onPluginConnected_showPluginClock() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getView()).thenReturn(pluginView);
-
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-
- assertThat(mClockView.getVisibility()).isEqualTo(GONE);
- assertThat(plugin.getView().getParent()).isEqualTo(mClockFrame);
}
@Test
- public void onPluginConnected_showPluginBigClock() {
- // GIVEN the plugin returns a view for the big clock
- ClockPlugin plugin = mock(ClockPlugin.class);
- when(plugin.getBigClockView()).thenReturn(mBigClock);
- // WHEN the plugin is connected
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- // THEN the big clock container is visible and it is the parent of the
- // big clock view.
- assertThat(mLargeClockView.getVisibility()).isEqualTo(View.GONE);
- assertThat(mBigClock.getParent()).isEqualTo(mLargeClockFrame);
+ public void noPluginConnected_showNothing() {
+ mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
+ assertEquals(mLargeClockFrame.getChildCount(), 0);
+ assertEquals(mSmallClockFrame.getChildCount(), 0);
}
@Test
- public void onPluginConnected_nullView() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
- }
-
- @Test
- public void onPluginConnected_showSecondPluginClock() {
- // GIVEN a plugin has already connected
- ClockPlugin plugin1 = mock(ClockPlugin.class);
- when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD);
- // WHEN a second plugin is connected
- ClockPlugin plugin2 = mock(ClockPlugin.class);
- when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD);
- // THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
- assertThat(plugin2.getView().getParent()).isEqualTo(mClockFrame);
- assertThat(plugin1.getView().getParent()).isNull();
- }
+ public void pluginConnectedThenDisconnected_showNothing() {
+ mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
+ assertEquals(mLargeClockFrame.getChildCount(), 1);
+ assertEquals(mSmallClockFrame.getChildCount(), 1);
- @Test
- public void onPluginConnected_darkAmountInitialized() {
- // GIVEN that the dark amount has already been set
- mKeyguardClockSwitch.setDarkAmount(0.5f);
- // WHEN a plugin is connected
- ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- // THEN dark amount should be initalized on the plugin.
- verify(plugin).setDarkAmount(0.5f);
+ mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
+ assertEquals(mLargeClockFrame.getChildCount(), 0);
+ assertEquals(mSmallClockFrame.getChildCount(), 0);
}
@Test
- public void onPluginDisconnected_showDefaultClock() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getView()).thenReturn(pluginView);
-
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- assertThat(mClockView.getVisibility()).isEqualTo(GONE);
+ public void onPluginConnected_showClock() {
+ mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
-
- assertThat(plugin.getView().getParent()).isNull();
+ assertEquals(mClock.getSmallClock().getParent(), mSmallClockFrame);
+ assertEquals(mClock.getLargeClock().getParent(), mLargeClockFrame);
}
@Test
- public void onPluginDisconnected_hidePluginBigClock() {
- // GIVEN the plugin returns a view for the big clock
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getBigClockView()).thenReturn(pluginView);
- // WHEN the plugin is connected and then disconnected
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- // THEN the big lock container is GONE and the big clock view doesn't have
- // a parent.
- assertThat(mLargeClockView.getVisibility()).isEqualTo(VISIBLE);
- assertThat(pluginView.getParent()).isNull();
- }
+ public void onPluginConnected_showSecondPluginClock() {
+ // GIVEN a plugin has already connected
+ Clock otherClock = mock(Clock.class);
+ when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
+ when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
+ mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
+ mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
- @Test
- public void onPluginDisconnected_nullView() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
+ // THEN only the view from the second plugin should be a child of KeyguardClockSwitch.
+ assertThat(otherClock.getSmallClock().getParent()).isEqualTo(mSmallClockFrame);
+ assertThat(otherClock.getLargeClock().getParent()).isEqualTo(mLargeClockFrame);
+ assertThat(mClock.getSmallClock().getParent()).isNull();
+ assertThat(mClock.getLargeClock().getParent()).isNull();
}
@Test
public void onPluginDisconnected_secondOfTwoDisconnected() {
// GIVEN two plugins are connected
- ClockPlugin plugin1 = mock(ClockPlugin.class);
- when(plugin1.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(plugin1, StatusBarState.KEYGUARD);
- ClockPlugin plugin2 = mock(ClockPlugin.class);
- when(plugin2.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(plugin2, StatusBarState.KEYGUARD);
+ Clock otherClock = mock(Clock.class);
+ when(otherClock.getSmallClock()).thenReturn(new TextView(getContext()));
+ when(otherClock.getLargeClock()).thenReturn(new TextView(getContext()));
+ mKeyguardClockSwitch.setClock(otherClock, StatusBarState.KEYGUARD);
+ mKeyguardClockSwitch.setClock(mClock, StatusBarState.KEYGUARD);
// WHEN the second plugin is disconnected
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- // THEN the default clock should be shown.
- assertThat(mClockView.getVisibility()).isEqualTo(VISIBLE);
- assertThat(plugin1.getView().getParent()).isNull();
- assertThat(plugin2.getView().getParent()).isNull();
- }
-
- @Test
- public void onPluginDisconnected_onDestroyView() {
- // GIVEN a plugin is connected
- ClockPlugin clockPlugin = mock(ClockPlugin.class);
- when(clockPlugin.getView()).thenReturn(new TextClock(getContext()));
- mKeyguardClockSwitch.setClockPlugin(clockPlugin, StatusBarState.KEYGUARD);
- // WHEN the plugin is disconnected
- mKeyguardClockSwitch.setClockPlugin(null, StatusBarState.KEYGUARD);
- // THEN onDestroyView is called on the plugin
- verify(clockPlugin).onDestroyView();
- }
-
- @Test
- public void setTextColor_pluginClockSetTextColor() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getView()).thenReturn(pluginView);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-
- mKeyguardClockSwitch.setTextColor(Color.WHITE);
-
- verify(plugin).setTextColor(Color.WHITE);
- }
-
-
- @Test
- public void setStyle_pluginClockSetStyle() {
- ClockPlugin plugin = mock(ClockPlugin.class);
- TextClock pluginView = new TextClock(getContext());
- when(plugin.getView()).thenReturn(pluginView);
- Style style = mock(Style.class);
- mKeyguardClockSwitch.setClockPlugin(plugin, StatusBarState.KEYGUARD);
-
- mKeyguardClockSwitch.setStyle(style);
-
- verify(plugin).setStyle(style);
+ mKeyguardClockSwitch.setClock(null, StatusBarState.KEYGUARD);
+ // THEN nothing should be shown
+ assertThat(otherClock.getSmallClock().getParent()).isNull();
+ assertThat(otherClock.getLargeClock().getParent()).isNull();
+ assertThat(mClock.getSmallClock().getParent()).isNull();
+ assertThat(mClock.getLargeClock().getParent()).isNull();
}
@Test
@@ -262,7 +170,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
- assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
}
@Test
@@ -271,7 +179,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(1);
assertThat(mLargeClockFrame.getVisibility()).isEqualTo(VISIBLE);
- assertThat(mClockFrame.getAlpha()).isEqualTo(0);
+ assertThat(mSmallClockFrame.getAlpha()).isEqualTo(0);
}
@Test
@@ -281,8 +189,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
mKeyguardClockSwitch.mClockInAnim.end();
mKeyguardClockSwitch.mClockOutAnim.end();
- assertThat(mClockFrame.getAlpha()).isEqualTo(1);
- assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+ assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
+ assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
@@ -292,8 +200,8 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
public void switchingToSmallClockNoAnimation_makesBigClockDisappear() {
mKeyguardClockSwitch.switchToClock(SMALL, false);
- assertThat(mClockFrame.getAlpha()).isEqualTo(1);
- assertThat(mClockFrame.getVisibility()).isEqualTo(VISIBLE);
+ assertThat(mSmallClockFrame.getAlpha()).isEqualTo(1);
+ assertThat(mSmallClockFrame.getVisibility()).isEqualTo(VISIBLE);
// only big clock is removed at switch
assertThat(mLargeClockFrame.getParent()).isNull();
assertThat(mLargeClockFrame.getAlpha()).isEqualTo(0);
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/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/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index d61989fc3128..131eac668af3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -110,6 +110,7 @@ class ClockRegistryTest : SysuiTestCase() {
get() = settingValue
set(value) { settingValue = value }
}
+ registry.isEnabled = true
verify(mockPluginManager)
.addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java))
@@ -129,13 +130,16 @@ class ClockRegistryTest : SysuiTestCase() {
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
val list = registry.getClocks()
- assertEquals(list, listOf(
- ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
- ClockMetadata("clock_1", "clock 1"),
- ClockMetadata("clock_2", "clock 2"),
- ClockMetadata("clock_3", "clock 3"),
- ClockMetadata("clock_4", "clock 4")
- ))
+ assertEquals(
+ list,
+ listOf(
+ ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2"),
+ ClockMetadata("clock_3", "clock 3"),
+ ClockMetadata("clock_4", "clock 4")
+ )
+ )
}
@Test
@@ -157,11 +161,14 @@ class ClockRegistryTest : SysuiTestCase() {
pluginListener.onPluginConnected(plugin1, mockContext)
pluginListener.onPluginConnected(plugin2, mockContext)
val list = registry.getClocks()
- assertEquals(list, listOf(
- ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
- ClockMetadata("clock_1", "clock 1"),
- ClockMetadata("clock_2", "clock 2")
- ))
+ assertEquals(
+ list,
+ listOf(
+ ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2")
+ )
+ )
assertEquals(registry.createExampleClock("clock_1"), mockClock)
assertEquals(registry.createExampleClock("clock_2"), mockClock)
@@ -221,7 +228,7 @@ class ClockRegistryTest : SysuiTestCase() {
pluginListener.onPluginConnected(plugin2, mockContext)
var changeCallCount = 0
- registry.registerClockChangeListener({ changeCallCount++ })
+ registry.registerClockChangeListener { changeCallCount++ }
pluginListener.onPluginDisconnected(plugin1)
assertEquals(0, changeCallCount)
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/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/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
deleted file mode 100644
index 6171e2f760d3..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ /dev/null
@@ -1,90 +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.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.collection.provider.SectionHeaderVisibilityProvider;
-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(
- mKeyguardNotificationVisibilityProvider,
- mSectionHeaderVisibilityProvider,
- mock(SharedCoordinatorLogger.class),
- mStatusBarStateController);
-
- 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/RowAppearanceCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
index 40859d0e6304..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
@@ -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)
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 ff601938d544..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
@@ -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/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index b94aac21acad..3d03c4750869 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -223,10 +223,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- public void disable_isDozingButNoCustomClock_clockAndSystemInfoVisible() {
+ public void disable_isDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(true);
- when(mNotificationPanelViewController.hasCustomClock()).thenReturn(false);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -235,10 +234,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- public void disable_customClockButNotDozing_clockAndSystemInfoVisible() {
+ public void disable_NotDozing_clockAndSystemInfoVisible() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
@@ -247,40 +245,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- public void disable_dozingAndCustomClock_clockAndSystemInfoHidden() {
- CollapsedStatusBarFragment fragment = resumeAndGetFragment();
- when(mStatusBarStateController.isDozing()).thenReturn(true);
- when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
-
- // Make sure they start out as visible
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
- assertEquals(View.VISIBLE, getClockView().getVisibility());
-
- fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
-
- assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
- assertEquals(View.GONE, getClockView().getVisibility());
- }
-
- @Test
- public void onDozingChanged_clockAndSystemInfoVisibilitiesUpdated() {
- CollapsedStatusBarFragment fragment = resumeAndGetFragment();
- when(mStatusBarStateController.isDozing()).thenReturn(true);
- when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
-
- // Make sure they start out as visible
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
- assertEquals(View.VISIBLE, getClockView().getVisibility());
-
- fragment.onDozingChanged(true);
-
- // When this callback is triggered, we want to make sure the clock and system info
- // visibilities are recalculated. Since dozing=true, they shouldn't be visible.
- assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
- assertEquals(View.GONE, getClockView().getVisibility());
- }
-
- @Test
public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
CollapsedStatusBarFragment fragment = resumeAndGetFragment();
when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
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/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index f39d6875cffc..e2b9a9ee7826 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 04468ed47640..ab9966f218c1 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -19,6 +19,7 @@ package com.android.server.companion;
import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
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;
@@ -49,6 +50,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;
@@ -93,6 +96,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;
@@ -110,6 +114,7 @@ import com.android.server.pm.UserManagerInternal;
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;
@@ -133,6 +138,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;
@@ -160,12 +168,40 @@ 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));
@@ -176,6 +212,9 @@ public class CompanionDeviceManagerService extends SystemService {
mUserPersistenceHandler = new PersistUserStateHandler();
mAssociationStore = new AssociationStoreImpl();
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
+
+ mOnPackageVisibilityChangeListener =
+ new OnPackageVisibilityChangeListener(mActivityManager);
}
@Override
@@ -217,7 +256,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
@@ -367,10 +432,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(
@@ -438,13 +511,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);
}
}
@@ -712,7 +789,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
@@ -772,7 +849,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");
}
@@ -832,8 +909,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);
@@ -845,6 +922,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;
}
@@ -925,39 +1007,187 @@ 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);
// Remove all the system data transfer requests for the association.
mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
- 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());
@@ -1175,4 +1405,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 4d42838fff50..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 #getStorageFileForUser(int)}
+ * 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/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 395cf1805b63..70745ba0b368 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -24,8 +24,6 @@ import static android.content.ComponentName.createRelative;
import static com.android.server.companion.Utils.prepareForIpc;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
@@ -41,23 +39,17 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.UserHandle;
+import android.permission.PermissionControllerManager;
import android.util.Slog;
-import android.util.Xml;
import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.PermissionsUtils;
-import com.android.server.companion.datatransfer.permbackup.BackupHelper;
import com.android.server.companion.proto.CompanionMessage;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
/**
* This processor builds user consent intent for a given SystemDataTransferRequest and processes the
@@ -83,6 +75,8 @@ public class SystemDataTransferProcessor {
private final AssociationStore mAssociationStore;
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
private final CompanionMessageProcessor mCompanionMessageProcessor;
+ private final PermissionControllerManager mPermissionControllerManager;
+ private final ExecutorService mExecutor;
public SystemDataTransferProcessor(CompanionDeviceManagerService service,
AssociationStore associationStore,
@@ -93,6 +87,8 @@ public class SystemDataTransferProcessor {
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
mCompanionMessageProcessor = companionMessageProcessor;
mCompanionMessageProcessor.setListener(this::onCompleteMessageReceived);
+ mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
+ mExecutor = Executors.newSingleThreadExecutor();
}
/**
@@ -180,23 +176,13 @@ public class SystemDataTransferProcessor {
// TODO: Establish a secure channel
- final long callingIdentityToken = Binder.clearCallingIdentity();
-
// Start permission sync
+ final long callingIdentityToken = Binder.clearCallingIdentity();
try {
- BackupHelper backupHelper = new BackupHelper(mContext, UserHandle.of(userId));
- XmlSerializer serializer = Xml.newSerializer();
- ByteArrayOutputStream backup = new ByteArrayOutputStream();
- serializer.setOutput(backup, UTF_8.name());
-
- backupHelper.writeState(serializer);
-
- serializer.flush();
-
- mCompanionMessageProcessor.paginateAndDispatchMessagesToApp(backup.toByteArray(),
- CompanionMessage.PERMISSION_SYNC, packageName, userId, associationId);
- } catch (IOException ioe) {
- Slog.e(LOG_TAG, "Error while writing permission state.");
+ mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
+ mExecutor,
+ backup -> mCompanionMessageProcessor.paginateAndDispatchMessagesToApp(backup,
+ CompanionMessage.PERMISSION_SYNC, packageName, userId, associationId));
} finally {
Binder.restoreCallingIdentity(callingIdentityToken);
}
@@ -219,21 +205,11 @@ public class SystemDataTransferProcessor {
private void processPermissionSyncMessage(CompanionMessageInfo messageInfo) {
Slog.i(LOG_TAG, "Applying permissions.");
// Start applying permissions
+ UserHandle user = mContext.getUser();
final long callingIdentityToken = Binder.clearCallingIdentity();
try {
- BackupHelper backupHelper = new BackupHelper(mContext, mContext.getUser());
- XmlPullParser parser = Xml.newPullParser();
- ByteArrayInputStream stream = new ByteArrayInputStream(
- messageInfo.getData());
- parser.setInput(stream, UTF_8.name());
-
- backupHelper.restoreState(parser);
- } catch (IOException e) {
- Slog.e(LOG_TAG, "IOException reading message: "
- + new String(messageInfo.getData()));
- } catch (XmlPullParserException e) {
- Slog.e(LOG_TAG, "Error parsing message: "
- + new String(messageInfo.getData()));
+ mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(
+ messageInfo.getData(), user);
} finally {
Slog.i(LOG_TAG, "Permissions applied.");
Binder.restoreCallingIdentity(callingIdentityToken);
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/BackupHelper.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/BackupHelper.java
deleted file mode 100644
index 5e3c4c7834fe..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/BackupHelper.java
+++ /dev/null
@@ -1,782 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.datatransfer.permbackup;
-
-import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-import static android.content.pm.PackageManager.GET_PERMISSIONS;
-import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
-
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.END_TAG;
-import static org.xmlpull.v1.XmlPullParser.START_TAG;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.Signature;
-import android.content.pm.SigningInfo;
-import android.os.Build;
-import android.os.UserHandle;
-import android.permission.PermissionManager;
-import android.permission.PermissionManager.SplitPermissionInfo;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.server.companion.datatransfer.permbackup.model.AppPermissionGroup;
-import com.android.server.companion.datatransfer.permbackup.model.AppPermissions;
-import com.android.server.companion.datatransfer.permbackup.model.Permission;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.security.cert.CertificateException;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Helper for creating and restoring permission backups.
- */
-public class BackupHelper {
- private static final String LOG_TAG = BackupHelper.class.getSimpleName();
-
- private static final String TAG_PERMISSION_BACKUP = "perm-grant-backup";
- private static final String ATTR_PLATFORM_VERSION = "version";
-
- private static final String TAG_ALL_GRANTS = "rt-grants";
-
- private static final String TAG_GRANT = "grant";
- private static final String ATTR_PACKAGE_NAME = "pkg";
- private static final String ATTR_HAS_MULTIPLE_SIGNERS = "multi-signers";
-
- private static final String TAG_SIGNATURE = "sig";
- private static final String ATTR_SIGNATURE_VALUE = "v";
-
- private static final String TAG_PERMISSION = "perm";
- private static final String ATTR_PERMISSION_NAME = "name";
- private static final String ATTR_IS_GRANTED = "g";
- private static final String ATTR_USER_SET = "set";
- private static final String ATTR_USER_FIXED = "fixed";
- private static final String ATTR_WAS_REVIEWED = "was-reviewed";
-
- /** Flags of permissions to <u>not</u> back up */
- private static final int SYSTEM_RUNTIME_GRANT_MASK = FLAG_PERMISSION_POLICY_FIXED
- | FLAG_PERMISSION_SYSTEM_FIXED;
-
- /** Make sure only one user can change the delayed permissions at a time */
- private static final Object sLock = new Object();
-
- private final Context mContext;
-
- /**
- * Create a new backup utils for a user.
- *
- * @param context A context to use
- * @param user The user that is backed up / restored
- */
- public BackupHelper(@NonNull Context context, @NonNull UserHandle user) {
- try {
- mContext = context.createPackageContextAsUser(context.getPackageName(), 0, user);
- } catch (PackageManager.NameNotFoundException doesNotHappen) {
- throw new IllegalStateException();
- }
- }
-
- /**
- * Forward parser and skip everything up to the end of the current tag.
- *
- * @param parser The parser to forward
- */
- private static void skipToEndOfTag(@NonNull XmlPullParser parser)
- throws IOException, XmlPullParserException {
- int numOpenTags = 1;
- while (numOpenTags > 0) {
- switch (parser.next()) {
- case START_TAG:
- numOpenTags++;
- break;
- case END_TAG:
- numOpenTags--;
- break;
- default:
- // ignore
- }
- }
- }
-
- /**
- * Forward parser to a given direct sub-tag.
- *
- * @param parser The parser to forward
- * @param tag The tag to search for
- */
- private void skipToTag(@NonNull XmlPullParser parser, @NonNull String tag)
- throws IOException, XmlPullParserException {
- int type;
- do {
- type = parser.next();
-
- switch (type) {
- case START_TAG:
- if (!parser.getName().equals(tag)) {
- skipToEndOfTag(parser);
- }
-
- return;
- }
- } while (type != END_DOCUMENT);
- }
-
- /**
- * Read a XML file and return the packages stored in it.
- *
- * @param parser The file to read
- *
- * @return The packages in this file
- */
- private @NonNull ArrayList<BackupPackageState> parseFromXml(@NonNull XmlPullParser parser)
- throws IOException, XmlPullParserException {
- ArrayList<BackupPackageState> pkgStates = new ArrayList<>();
-
- skipToTag(parser, TAG_PERMISSION_BACKUP);
-
- int backupPlatformVersion;
- try {
- backupPlatformVersion = Integer.parseInt(
- parser.getAttributeValue(null, ATTR_PLATFORM_VERSION));
- } catch (NumberFormatException ignored) {
- // Platforms P and before did not store the platform version
- backupPlatformVersion = Build.VERSION_CODES.P;
- }
-
- skipToTag(parser, TAG_ALL_GRANTS);
-
- if (parser.getEventType() != START_TAG && !parser.getName().equals(TAG_ALL_GRANTS)) {
- throw new XmlPullParserException("Could not find " + TAG_PERMISSION_BACKUP + " > "
- + TAG_ALL_GRANTS);
- }
-
- // Read packages to restore from xml
- int type;
- do {
- type = parser.next();
-
- switch (type) {
- case START_TAG:
- switch (parser.getName()) {
- case TAG_GRANT:
- try {
- pkgStates.add(BackupPackageState.parseFromXml(parser, mContext,
- backupPlatformVersion));
- } catch (XmlPullParserException e) {
- Log.e(LOG_TAG, "Could not parse permissions ", e);
- skipToEndOfTag(parser);
- }
- break;
- default:
- // ignore tag
- Log.w(LOG_TAG, "Found unexpected tag " + parser.getName()
- + " during restore");
- skipToEndOfTag(parser);
- }
- }
- } while (type != END_DOCUMENT);
-
- return pkgStates;
- }
-
- /**
- * Try to restore the permission state from XML.
- *
- * @param parser The xml to read
- */
- public void restoreState(@NonNull XmlPullParser parser) throws IOException,
- XmlPullParserException {
- ArrayList<BackupPackageState> pkgStates = parseFromXml(parser);
-
- ArrayList<BackupPackageState> packagesToRestoreLater = new ArrayList<>();
- int numPkgStates = pkgStates.size();
- if (numPkgStates > 0) {
- // Try to restore packages
- for (int i = 0; i < numPkgStates; i++) {
- BackupPackageState pkgState = pkgStates.get(i);
-
- PackageInfo pkgInfo;
- try {
- pkgInfo = mContext.getPackageManager().getPackageInfo(pkgState.mPackageName,
- GET_PERMISSIONS | GET_SIGNING_CERTIFICATES);
- } catch (PackageManager.NameNotFoundException ignored) {
- packagesToRestoreLater.add(pkgState);
- continue;
- }
-
- pkgState.restore(mContext, pkgInfo);
- }
- }
-
-// synchronized (sLock) {
-// writeDelayedStorePkgsLocked(packagesToRestoreLater);
-// }
- }
-
- /**
- * Write a xml file for the given packages.
- *
- * @param serializer The file to write to
- * @param pkgs The packages to write
- */
- private static void writePkgsAsXml(@NonNull XmlSerializer serializer,
- @NonNull ArrayList<BackupPackageState> pkgs) throws IOException {
- serializer.startDocument(null, true);
-
- serializer.startTag(null, TAG_PERMISSION_BACKUP);
-
-// if (SDK_INT >= Build.VERSION_CODES.Q) {
- // STOPSHIP: Remove compatibility code once Q SDK level is declared
- serializer.attribute(null, ATTR_PLATFORM_VERSION,
- Integer.valueOf(Build.VERSION_CODES.Q).toString());
-// } else {
-// serializer.attribute(null, ATTR_PLATFORM_VERSION,
-// Integer.valueOf(SDK_INT).toString());
-// }
-
- serializer.startTag(null, TAG_ALL_GRANTS);
-
- int numPkgs = pkgs.size();
- for (int i = 0; i < numPkgs; i++) {
- BackupPackageState packageState = pkgs.get(i);
-
- if (packageState != null) {
- packageState.writeAsXml(serializer);
- }
- }
-
- serializer.endTag(null, TAG_ALL_GRANTS);
- serializer.endTag(null, TAG_PERMISSION_BACKUP);
-
- serializer.endDocument();
- }
-
- /**
- * Write the state of all packages as XML.
- *
- * @param serializer The xml to write to
- */
- public void writeState(@NonNull XmlSerializer serializer) throws IOException {
- List<PackageInfo> pkgs = mContext.getPackageManager().getInstalledPackages(
- GET_PERMISSIONS | GET_SIGNING_CERTIFICATES);
- ArrayList<BackupPackageState> backupPkgs = new ArrayList<>();
-
- int numPkgs = pkgs.size();
- for (int i = 0; i < numPkgs; i++) {
- BackupPackageState packageState = BackupPackageState.fromAppPermissions(mContext,
- pkgs.get(i));
-
- if (packageState != null) {
- backupPkgs.add(packageState);
- }
- }
-
- writePkgsAsXml(serializer, backupPkgs);
- }
-
- /**
- * State that needs to be backed up for a permission.
- */
- private static class BackupPermissionState {
- private final @NonNull String mPermissionName;
- private final boolean mIsGranted;
- private final boolean mIsUserSet;
- private final boolean mIsUserFixed;
- private final boolean mWasReviewed;
-
- private BackupPermissionState(@NonNull String permissionName, boolean isGranted,
- boolean isUserSet, boolean isUserFixed, boolean wasReviewed) {
- mPermissionName = permissionName;
- mIsGranted = isGranted;
- mIsUserSet = isUserSet;
- mIsUserFixed = isUserFixed;
- mWasReviewed = wasReviewed;
- }
-
- /**
- * Parse a package state from XML.
- *
- * @param parser The data to read
- * @param context a context to use
- * @param backupPlatformVersion The platform version the backup was created on
- *
- * @return The state
- */
- static @NonNull List<BackupPermissionState> parseFromXml(@NonNull XmlPullParser parser,
- @NonNull Context context, int backupPlatformVersion)
- throws XmlPullParserException {
- String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME);
- if (permName == null) {
- throw new XmlPullParserException("Found " + TAG_PERMISSION + " without "
- + ATTR_PERMISSION_NAME);
- }
-
- ArrayList<String> expandedPermissions = new ArrayList<>();
- expandedPermissions.add(permName);
-
- List<SplitPermissionInfo> splitPerms = context.getSystemService(
- PermissionManager.class).getSplitPermissions();
-
- // Expand the properties to permissions that were split between the platform version the
- // backup was taken and the current version.
- int numSplitPerms = splitPerms.size();
- for (int i = 0; i < numSplitPerms; i++) {
- SplitPermissionInfo splitPerm = splitPerms.get(i);
- if (backupPlatformVersion < splitPerm.getTargetSdk()
- && permName.equals(splitPerm.getSplitPermission())) {
- expandedPermissions.addAll(splitPerm.getNewPermissions());
- }
- }
-
- ArrayList<BackupPermissionState> parsedPermissions = new ArrayList<>(
- expandedPermissions.size());
- int numExpandedPerms = expandedPermissions.size();
- for (int i = 0; i < numExpandedPerms; i++) {
- parsedPermissions.add(new BackupPermissionState(expandedPermissions.get(i),
- "true".equals(parser.getAttributeValue(null, ATTR_IS_GRANTED)),
- "true".equals(parser.getAttributeValue(null, ATTR_USER_SET)),
- "true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED)),
- "true".equals(parser.getAttributeValue(null, ATTR_WAS_REVIEWED))));
- }
-
- return parsedPermissions;
- }
-
- /**
- * Is the permission granted, also considering the app-op.
- *
- * <p>This does not consider the review-required state of the permission.
- *
- * @param perm The permission that might be granted
- *
- * @return {@code true} iff the permission and app-op is granted
- */
- private static boolean isPermGrantedIncludingAppOp(@NonNull Permission perm) {
- return perm.isGranted() && (!perm.affectsAppOp() || perm.isAppOpAllowed());
- }
-
- /**
- * Get the state of a permission to back up.
- *
- * @param perm The permission to back up
- * @param appSupportsRuntimePermissions If the app supports runtimePermissions
- *
- * @return The state to back up or {@code null} if the permission does not need to be
- * backed up.
- */
- private static @Nullable BackupPermissionState fromPermission(@NonNull Permission perm,
- boolean appSupportsRuntimePermissions) {
- int grantFlags = perm.getFlags();
-
- if ((grantFlags & SYSTEM_RUNTIME_GRANT_MASK) != 0) {
- return null;
- }
-
- if (!perm.isUserSet() && perm.isGrantedByDefault()) {
- return null;
- }
-
- boolean permissionWasReviewed;
- boolean isNotInDefaultGrantState;
- if (appSupportsRuntimePermissions) {
- isNotInDefaultGrantState = isPermGrantedIncludingAppOp(perm);
- permissionWasReviewed = false;
- } else {
- isNotInDefaultGrantState = !isPermGrantedIncludingAppOp(perm);
- permissionWasReviewed = !perm.isReviewRequired();
- }
-
-// if (isNotInDefaultGrantState || perm.isUserSet() || perm.isUserFixed()
-// || permissionWasReviewed) {
-// return new BackupPermissionState(perm.getName(),
-// isPermGrantedIncludingAppOp(perm),
-// perm.isUserSet(), perm.isUserFixed(), permissionWasReviewed);
-// } else {
-// return null;
-// }
- if (perm.isUserSet() && isPermGrantedIncludingAppOp(perm)) {
- return new BackupPermissionState(perm.getName(), /* isGranted */ true,
- /* isUserSet */ true, perm.isUserFixed(), permissionWasReviewed);
- } else {
- return null;
- }
- }
-
- /**
- * Get the states of all permissions of a group to back up.
- *
- * @param group The group of the permissions to back up
- *
- * @return The state to back up. Empty list if no permissions in the group need to be backed
- * up
- */
- static @NonNull ArrayList<BackupPermissionState> fromPermissionGroup(
- @NonNull AppPermissionGroup group) {
- ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>();
- List<Permission> perms = group.getPermissions();
-
- boolean appSupportsRuntimePermissions =
- group.getApp().applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M;
-
- int numPerms = perms.size();
- for (int i = 0; i < numPerms; i++) {
- BackupPermissionState permState = fromPermission(perms.get(i),
- appSupportsRuntimePermissions);
- if (permState != null) {
- permissionsToRestore.add(permState);
- }
- }
-
- return permissionsToRestore;
- }
-
- /**
- * Write this state as XML.
- *
- * @param serializer The file to write to
- */
- void writeAsXml(@NonNull XmlSerializer serializer) throws IOException {
- serializer.startTag(null, TAG_PERMISSION);
-
- serializer.attribute(null, ATTR_PERMISSION_NAME, mPermissionName);
-
- if (mIsGranted) {
- serializer.attribute(null, ATTR_IS_GRANTED, "true");
- }
-
- if (mIsUserSet) {
- serializer.attribute(null, ATTR_USER_SET, "true");
- }
-
- if (mIsUserFixed) {
- serializer.attribute(null, ATTR_USER_FIXED, "true");
- }
-
- if (mWasReviewed) {
- serializer.attribute(null, ATTR_WAS_REVIEWED, "true");
- }
-
- serializer.endTag(null, TAG_PERMISSION);
- }
-
- /**
- * Restore this permission state.
- *
- * @param appPerms The {@link AppPermissions} to restore the state to
- * @param restoreBackgroundPerms if {@code true} only restore background permissions,
- * if {@code false} do not restore background permissions
- */
- void restore(@NonNull AppPermissions appPerms, boolean restoreBackgroundPerms) {
- AppPermissionGroup group = appPerms.getGroupForPermission(mPermissionName);
- if (group == null) {
- Log.w(LOG_TAG, "Could not find group for " + mPermissionName + " in "
- + appPerms.getPackageInfo().packageName);
- return;
- }
-
- if (restoreBackgroundPerms != group.isBackgroundGroup()) {
- return;
- }
-
- Permission perm = group.getPermission(mPermissionName);
- if (mWasReviewed) {
- perm.unsetReviewRequired();
- }
-
- // Don't grant or revoke fixed permission groups
- if (group.isSystemFixed() || group.isPolicyFixed()) {
- return;
- }
-
- if (!perm.isUserSet()) {
- if (mIsGranted) {
- group.grantRuntimePermissions(false, mIsUserFixed,
- new String[]{mPermissionName});
- } else {
- group.revokeRuntimePermissions(mIsUserFixed,
- new String[]{mPermissionName});
- }
-
- perm.setUserSet(mIsUserSet);
- }
- }
- }
-
- /**
- * State that needs to be backed up for a package.
- */
- private static class BackupPackageState {
- final @NonNull String mPackageName;
- final boolean mHasMultipleSigners;
- @NonNull Signature[] mSignatures;
- private final @NonNull ArrayList<BackupPermissionState> mPermissionsToRestore;
-
- private BackupPackageState(@NonNull String packageName, boolean hasMultipleSigners,
- @NonNull Signature[] signatures,
- @NonNull ArrayList<BackupPermissionState> permissionsToRestore) {
- mPackageName = packageName;
- mHasMultipleSigners = hasMultipleSigners;
- mSignatures = signatures;
- mPermissionsToRestore = permissionsToRestore;
- }
-
- /**
- * Parse a package state from XML.
- *
- * @param parser The data to read
- * @param context a context to use
- * @param backupPlatformVersion The platform version the backup was created on
- *
- * @return The state
- */
- static @NonNull BackupPackageState parseFromXml(@NonNull XmlPullParser parser,
- @NonNull Context context, int backupPlatformVersion)
- throws IOException, XmlPullParserException {
- String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
- if (packageName == null) {
- throw new XmlPullParserException("Found " + TAG_GRANT + " without "
- + ATTR_PACKAGE_NAME);
- }
-
- boolean hasMultipleSigners = Boolean.parseBoolean(
- parser.getAttributeValue(null, ATTR_HAS_MULTIPLE_SIGNERS));
- ArrayList<Signature> signatureList = new ArrayList<>();
-
- ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>();
-
- while (true) {
- switch (parser.next()) {
- case START_TAG:
- switch (parser.getName()) {
- case TAG_PERMISSION:
- try {
- permissionsToRestore.addAll(
- BackupPermissionState.parseFromXml(parser, context,
- backupPlatformVersion));
- } catch (XmlPullParserException e) {
- Log.e(LOG_TAG, "Could not parse permission for "
- + packageName, e);
- }
-
- skipToEndOfTag(parser);
- break;
- case TAG_SIGNATURE:
- signatureList.add(new Signature(
- parser.getAttributeValue(null, ATTR_SIGNATURE_VALUE)));
- skipToEndOfTag(parser);
- break;
- default:
- // ignore tag
- Log.w(LOG_TAG, "Found unexpected tag " + parser.getName()
- + " while restoring " + packageName);
- skipToEndOfTag(parser);
- }
-
- break;
- case END_TAG:
- Signature[] signatures = new Signature[signatureList.size()];
- for (int i = 0; i < signatureList.size(); i++) {
- signatures[i] = signatureList.get(i);
- }
- return new BackupPackageState(packageName, hasMultipleSigners, signatures,
- permissionsToRestore);
- case END_DOCUMENT:
- throw new XmlPullParserException("Could not parse state for "
- + packageName);
- }
- }
- }
-
- /**
- * Get the state of a package to back up.
- *
- * @param context A context to use
- * @param pkgInfo The package to back up.
- *
- * @return The state to back up or {@code null} if no permission of the package need to be
- * backed up.
- */
- static @Nullable BackupPackageState fromAppPermissions(@NonNull Context context,
- @NonNull PackageInfo pkgInfo) {
- AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, null);
-
- ArrayList<BackupPermissionState> permissionsToRestore = new ArrayList<>();
- List<AppPermissionGroup> groups = appPerms.getPermissionGroups();
-
- // Check if the package has signatures
- SigningInfo signingInfo = pkgInfo.signingInfo;
- Signature[] signatures;
- boolean hasMultipleSigners;
- if (signingInfo.hasMultipleSigners()) {
- hasMultipleSigners = true;
- signatures = signingInfo.getApkContentsSigners();
- } else {
- hasMultipleSigners = false;
- signatures = signingInfo.getSigningCertificateHistory();
- }
- if (signatures == null) {
- Slog.d(LOG_TAG, "Skipping " + pkgInfo.packageName + ", it's unsigned.");
- return null;
- }
-
- int numGroups = groups.size();
- for (int groupNum = 0; groupNum < numGroups; groupNum++) {
- AppPermissionGroup group = groups.get(groupNum);
-
- permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup(group));
-
- // Background permissions are in a subgroup that is not part of
- // {@link AppPermission#getPermissionGroups}. Hence add it explicitly here.
- if (group.getBackgroundPermissions() != null) {
- permissionsToRestore.addAll(BackupPermissionState.fromPermissionGroup(
- group.getBackgroundPermissions()));
- }
- }
-
- if (permissionsToRestore.size() == 0) {
- return null;
- }
-
- return new BackupPackageState(pkgInfo.packageName, hasMultipleSigners, signatures,
- permissionsToRestore);
- }
-
- /**
- * Write this state as XML.
- *
- * @param serializer The file to write to
- */
- void writeAsXml(@NonNull XmlSerializer serializer) throws IOException {
- if (mPermissionsToRestore.size() == 0) {
- return;
- }
-
- serializer.startTag(null, TAG_GRANT);
- serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName);
-
- // Add signing info
- serializer.attribute(null, ATTR_HAS_MULTIPLE_SIGNERS,
- String.valueOf(mHasMultipleSigners));
- for (Signature signature : mSignatures) {
- serializer.startTag(null, TAG_SIGNATURE);
- serializer.attribute(null, ATTR_SIGNATURE_VALUE, signature.toCharsString());
- serializer.endTag(null, TAG_SIGNATURE);
- }
-
- int numPerms = mPermissionsToRestore.size();
- for (int i = 0; i < numPerms; i++) {
- mPermissionsToRestore.get(i).writeAsXml(serializer);
- }
-
- serializer.endTag(null, TAG_GRANT);
- }
-
- /**
- * Restore this package state.
- *
- * @param context A context to use
- * @param pkgInfo The package to restore.
- */
- void restore(@NonNull Context context, @NonNull PackageInfo pkgInfo) {
- Slog.e(LOG_TAG, "Restoring permissions for package [" + mPackageName + "]");
-
- // Verify signature info
- try {
- if (mHasMultipleSigners && pkgInfo.signingInfo.hasMultipleSigners()) {
- // If both packages are signed by multi signers, check if two signature sets are
- // effectively matched.
- if (!Signature.areEffectiveMatch(mSignatures,
- pkgInfo.signingInfo.getApkContentsSigners())) {
- Slog.e(LOG_TAG, "Multi-signers signatures don't match for package ["
- + mPackageName + "], skipped.");
- return;
- }
- } else if (!mHasMultipleSigners && !pkgInfo.signingInfo.hasMultipleSigners()) {
- // If both packages are not signed by multi signers, check if two signature sets
- // have overlaps.
- Signature[] signatures = pkgInfo.signingInfo.getSigningCertificateHistory();
- if (signatures == null) {
- Slog.e(LOG_TAG, "The dest package is unsigned.");
- return;
- }
- boolean isMatched = false;
- for (int i = 0; i < mSignatures.length; i++) {
- for (int j = 0; j < signatures.length; j++) {
- isMatched = Signature.areEffectiveMatch(mSignatures[i], signatures[j]);
- }
- }
- if (!isMatched) {
- Slog.e(LOG_TAG, "Single signer signatures don't match for package ["
- + mPackageName + "], skipped.");
- return;
- }
- } else {
- Slog.e(LOG_TAG, "Number of signers don't match.");
- return;
- }
- } catch (CertificateException ce) {
- Slog.e(LOG_TAG, "Either the source or the dest package's bounced cert length "
- + "looks fishy, skipped package [" + pkgInfo.packageName + "]");
- }
-
- AppPermissions appPerms = new AppPermissions(context, pkgInfo, false, true, null);
-
- ArraySet<String> affectedPermissions = new ArraySet<>();
- // Restore background permissions after foreground permissions as for pre-M apps bg
- // granted and fg revoked cannot be expressed.
- int numPerms = mPermissionsToRestore.size();
- for (int i = 0; i < numPerms; i++) {
- mPermissionsToRestore.get(i).restore(appPerms, false);
- affectedPermissions.add(mPermissionsToRestore.get(i).mPermissionName);
- }
- for (int i = 0; i < numPerms; i++) {
- mPermissionsToRestore.get(i).restore(appPerms, true);
- }
-
- int numGroups = appPerms.getPermissionGroups().size();
- for (int i = 0; i < numGroups; i++) {
- AppPermissionGroup group = appPerms.getPermissionGroups().get(i);
-
- // Only denied groups can be user fixed
- if (group.areRuntimePermissionsGranted()) {
- group.setUserFixed(false);
- }
-
- AppPermissionGroup bgGroup = group.getBackgroundPermissions();
- if (bgGroup != null) {
- // Only denied groups can be user fixed
- if (bgGroup.areRuntimePermissionsGranted()) {
- bgGroup.setUserFixed(false);
- }
- }
- }
-
- appPerms.persistChanges(true, affectedPermissions);
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissionGroup.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissionGroup.java
deleted file mode 100644
index cf146ac7a48e..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissionGroup.java
+++ /dev/null
@@ -1,1574 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.datatransfer.permbackup.model;
-
-import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
-import static android.Manifest.permission.ACCESS_FINE_LOCATION;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_FOREGROUND;
-import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringRes;
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.app.Application;
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PermissionGroupInfo;
-import android.content.pm.PermissionInfo;
-import android.os.Build;
-import android.os.UserHandle;
-import android.permission.PermissionManager;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.server.companion.datatransfer.permbackup.utils.ArrayUtils;
-import com.android.server.companion.datatransfer.permbackup.utils.LocationUtils;
-import com.android.server.companion.datatransfer.permbackup.utils.SoftRestrictedPermissionPolicy;
-import com.android.server.companion.datatransfer.permbackup.utils.Utils;
-
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * All permissions of a permission group that are requested by an app.
- *
- * <p>Some permissions only grant access to the protected resource while the app is running in the
- * foreground. These permissions are considered "split" into this foreground and a matching
- * "background" permission.
- *
- * <p>All background permissions of the group are not in the main group and will not be affected
- * by operations on the group. The background permissions can be found in the {@link
- * #getBackgroundPermissions() background permissions group}.
- */
-public final class AppPermissionGroup implements Comparable<AppPermissionGroup> {
- private static final String LOG_TAG = AppPermissionGroup.class.getSimpleName();
- private static final String PLATFORM_PACKAGE_NAME = "android";
-
- private static final String KILL_REASON_APP_OP_CHANGE = "Permission related app op changed";
-
- /**
- * Importance level to define the threshold for whether a package is in a state which resets the
- * timer on its one-time permission session
- */
- private static final int ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER =
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
-
- /**
- * Importance level to define the threshold for whether a package is in a state which keeps its
- * one-time permission session alive after the timer ends
- */
- private static final int ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE =
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
-
- private final Context mContext;
- private final UserHandle mUserHandle;
- private final PackageManager mPackageManager;
- private final AppOpsManager mAppOps;
- private final ActivityManager mActivityManager;
- private final Collator mCollator;
-
- private final PackageInfo mPackageInfo;
- private final String mName;
- private final String mDeclaringPackage;
- private final CharSequence mLabel;
- private final CharSequence mFullLabel;
- private final @StringRes int mRequest;
- private final @StringRes int mRequestDetail;
- private final @StringRes int mBackgroundRequest;
- private final @StringRes int mBackgroundRequestDetail;
- private final @StringRes int mUpgradeRequest;
- private final @StringRes int mUpgradeRequestDetail;
- private final CharSequence mDescription;
- private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
- private final String mIconPkg;
- private final int mIconResId;
-
- /** Delay changes until {@link #persistChanges} is called */
- private final boolean mDelayChanges;
-
- /**
- * Some permissions are split into foreground and background permission. All non-split and
- * foreground permissions are in {@link #mPermissions}, all background permissions are in
- * this field.
- */
- private AppPermissionGroup mBackgroundPermissions;
-
- private final boolean mAppSupportsRuntimePermissions;
- private final boolean mIsEphemeralApp;
- private final boolean mIsNonIsolatedStorage;
- private boolean mContainsEphemeralPermission;
- private boolean mContainsPreRuntimePermission;
-
- /**
- * Does this group contain at least one permission that is split into a foreground and
- * background permission? This does not necessarily mean that the app also requested the
- * background permission.
- */
- private boolean mHasPermissionWithBackgroundMode;
-
- private boolean mTriggerLocationAccessCheckOnPersist;
-
- private boolean mIsSelfRevoked;
-
- /**
- * Create the app permission group.
- *
- * @param context the {@code Context} to retrieve system services.
- * @param packageInfo package information about the app.
- * @param permissionName the name of the permission this object represents.
- * @param delayChanges whether to delay changes until {@link #persistChanges} is called.
- *
- * @return the AppPermissionGroup.
- */
- public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
- String permissionName, boolean delayChanges) {
- PermissionInfo permissionInfo;
- try {
- permissionInfo = context.getPackageManager().getPermissionInfo(permissionName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
-
- if ((permissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
- != PermissionInfo.PROTECTION_DANGEROUS
- || (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) == 0
- || (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) != 0) {
- return null;
- }
-
- String group = Utils.getGroupOfPermission(permissionInfo);
- PackageItemInfo groupInfo = permissionInfo;
- if (group != null) {
- try {
- groupInfo = context.getPackageManager().getPermissionGroupInfo(group, 0);
- } catch (PackageManager.NameNotFoundException e) {
- /* ignore */
- }
- }
-
- List<PermissionInfo> permissionInfos = null;
- if (groupInfo instanceof PermissionGroupInfo) {
- try {
- permissionInfos = Utils.getPermissionInfosForGroup(context.getPackageManager(),
- groupInfo.name);
- } catch (PackageManager.NameNotFoundException e) {
- /* ignore */
- }
- }
-
- return create(context, packageInfo, groupInfo, permissionInfos, delayChanges);
- }
-
- /**
- * Create the app permission group.
- *
- * @param app the current application
- * @param packageName the name of the package
- * @param permissionGroupName the name of the permission group
- * @param user the user of the package
- * @param delayChanges whether to delay changes until {@link #persistChanges} is called.
- *
- * @return the AppPermissionGroup.
- */
- public static AppPermissionGroup create(Application app, String packageName,
- String permissionGroupName, UserHandle user, boolean delayChanges) {
- try {
- PackageInfo packageInfo = Utils.getUserContext(app, user).getPackageManager()
- .getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
- PackageItemInfo groupInfo = Utils.getGroupInfo(permissionGroupName, app);
- if (groupInfo == null) {
- return null;
- }
-
- List<PermissionInfo> permissionInfos = null;
- if (groupInfo instanceof PermissionGroupInfo) {
- permissionInfos = Utils.getPermissionInfosForGroup(app.getPackageManager(),
- groupInfo.name);
- }
- return create(app, packageInfo, groupInfo, permissionInfos, delayChanges);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
- /**
- * Create the app permission group.
- *
- * @param context the {@code Context} to retrieve system services.
- * @param packageInfo package information about the app.
- * @param groupInfo the information about the group created.
- * @param permissionInfos the information about the permissions belonging to the group.
- * @param delayChanges whether to delay changes until {@link #persistChanges} is called.
- *
- * @return the AppPermissionGroup.
- */
- public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
- PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos, boolean delayChanges) {
- PackageManager packageManager = context.getPackageManager();
- CharSequence groupLabel = groupInfo.loadLabel(packageManager);
- CharSequence fullGroupLabel = groupInfo.loadSafeLabel(packageManager, 0,
- TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE);
- return create(context, packageInfo, groupInfo, permissionInfos, groupLabel,
- fullGroupLabel, delayChanges);
- }
-
- /**
- * Create the app permission group.
- *
- * @param context the {@code Context} to retrieve system services.
- * @param packageInfo package information about the app.
- * @param groupInfo the information about the group created.
- * @param permissionInfos the information about the permissions belonging to the group.
- * @param groupLabel the label of the group.
- * @param fullGroupLabel the untruncated label of the group.
- * @param delayChanges whether to delay changes until {@link #persistChanges} is called.
- *
- * @return the AppPermissionGroup.
- */
- public static AppPermissionGroup create(Context context, PackageInfo packageInfo,
- PackageItemInfo groupInfo, List<PermissionInfo> permissionInfos,
- CharSequence groupLabel, CharSequence fullGroupLabel, boolean delayChanges) {
- PackageManager packageManager = context.getPackageManager();
- UserHandle userHandle = UserHandle.getUserHandleForUid(packageInfo.applicationInfo.uid);
-
- if (groupInfo instanceof PermissionInfo) {
- permissionInfos = new ArrayList<>();
- permissionInfos.add((PermissionInfo) groupInfo);
- }
-
- if (permissionInfos == null || permissionInfos.isEmpty()) {
- return null;
- }
-
- AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-
- AppPermissionGroup group = new AppPermissionGroup(context, packageInfo, groupInfo.name,
- groupInfo.packageName, groupLabel, fullGroupLabel,
- /* description */ null, /* request */ 0,
- /* requestDetail */ 0, /* backgroundRequest */ 0,
- /* backgroundRequestDetail */ 0, /* upgradeRequest */0,
- /* upgradeRequestDetail */ 0, groupInfo.packageName, groupInfo.icon,
- userHandle, delayChanges, appOpsManager);
-
- final Set<String> exemptedRestrictedPermissions = context.getPackageManager()
- .getWhitelistedRestrictedPermissions(packageInfo.packageName,
- Utils.FLAGS_PERMISSION_WHITELIST_ALL);
-
- // Parse and create permissions requested by the app
- ArrayMap<String, Permission> allPermissions = new ArrayMap<>();
- final int permissionCount = packageInfo.requestedPermissions == null ? 0
- : packageInfo.requestedPermissions.length;
- String packageName = packageInfo.packageName;
- for (int i = 0; i < permissionCount; i++) {
- String requestedPermission = packageInfo.requestedPermissions[i];
-
- PermissionInfo requestedPermissionInfo = null;
-
- for (PermissionInfo permissionInfo : permissionInfos) {
- if (requestedPermission.equals(permissionInfo.name)) {
- requestedPermissionInfo = permissionInfo;
- break;
- }
- }
-
- if (requestedPermissionInfo == null) {
- continue;
- }
-
- // Collect only runtime permissions.
- if ((requestedPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
- != PermissionInfo.PROTECTION_DANGEROUS) {
- continue;
- }
-
- // Don't allow toggling non-platform permission groups for legacy apps via app ops.
- if (packageInfo.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1
- && !PLATFORM_PACKAGE_NAME.equals(groupInfo.packageName)) {
- continue;
- }
-
- final boolean granted = (packageInfo.requestedPermissionsFlags[i]
- & PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0;
-
- final String appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)
- ? AppOpsManager.permissionToOp(requestedPermissionInfo.name) : null;
-
- final boolean appOpAllowed;
- if (appOp == null) {
- appOpAllowed = false;
- } else {
- int appOpsMode = appOpsManager.unsafeCheckOpRaw(appOp,
- packageInfo.applicationInfo.uid, packageName);
- appOpAllowed = appOpsMode == MODE_ALLOWED || appOpsMode == MODE_FOREGROUND;
- }
-
- final int flags = packageManager.getPermissionFlags(
- requestedPermission, packageName, userHandle);
-
- Permission permission = new Permission(requestedPermission, requestedPermissionInfo,
- granted, appOp, appOpAllowed, flags);
-
- if (requestedPermissionInfo.backgroundPermission != null) {
- group.mHasPermissionWithBackgroundMode = true;
- }
-
- allPermissions.put(requestedPermission, permission);
- }
-
- int numPermissions = allPermissions.size();
- if (numPermissions == 0) {
- return null;
- }
-
- // Link up foreground and background permissions
- for (int i = 0; i < allPermissions.size(); i++) {
- Permission permission = allPermissions.valueAt(i);
-
- if (permission.getBackgroundPermissionName() != null) {
- Permission backgroundPermission = allPermissions.get(
- permission.getBackgroundPermissionName());
-
- if (backgroundPermission != null) {
- backgroundPermission.addForegroundPermissions(permission);
- permission.setBackgroundPermission(backgroundPermission);
-
- // The background permissions isAppOpAllowed refers to the background state of
- // the foregound permission's appOp. Hence we can only set it once we know the
- // matching foreground permission.
- // @see #allowAppOp
- if (context.getSystemService(AppOpsManager.class).unsafeCheckOpRaw(
- permission.getAppOp(), packageInfo.applicationInfo.uid,
- packageInfo.packageName) == MODE_ALLOWED) {
- backgroundPermission.setAppOpAllowed(true);
- }
- }
- }
- }
-
- // Add permissions found to this group
- for (int i = 0; i < numPermissions; i++) {
- Permission permission = allPermissions.valueAt(i);
-
- if ((!permission.isHardRestricted()
- || exemptedRestrictedPermissions.contains(permission.getName()))
- && (!permission.isSoftRestricted()
- || SoftRestrictedPermissionPolicy.shouldShow(packageInfo, permission))) {
- if (permission.isBackgroundPermission()) {
- if (group.getBackgroundPermissions() == null) {
- group.mBackgroundPermissions = new AppPermissionGroup(group.mContext,
- group.getApp(), group.getName(), group.getDeclaringPackage(),
- group.getLabel(), group.getFullLabel(), group.getDescription(),
- group.getRequest(), group.getRequestDetail(),
- group.getBackgroundRequest(), group.getBackgroundRequestDetail(),
- group.getUpgradeRequest(), group.getUpgradeRequestDetail(),
- group.getIconPkg(), group.getIconResId(), group.getUser(),
- delayChanges, appOpsManager);
- }
-
- group.getBackgroundPermissions().addPermission(permission);
- } else {
- group.addPermission(permission);
- }
- }
- }
-
- if (group.getPermissions().isEmpty()) {
- return null;
- }
-
- return group;
- }
-
- private AppPermissionGroup(Context context, PackageInfo packageInfo, String name,
- String declaringPackage, CharSequence label, CharSequence fullLabel,
- CharSequence description, @StringRes int request, @StringRes int requestDetail,
- @StringRes int backgroundRequest, @StringRes int backgroundRequestDetail,
- @StringRes int upgradeRequest, @StringRes int upgradeRequestDetail,
- String iconPkg, int iconResId, UserHandle userHandle, boolean delayChanges,
- @NonNull AppOpsManager appOpsManager) {
- int targetSDK = packageInfo.applicationInfo.targetSdkVersion;
-
- mContext = context;
- mUserHandle = userHandle;
- mPackageManager = mContext.getPackageManager();
- mPackageInfo = packageInfo;
- mAppSupportsRuntimePermissions = targetSDK > Build.VERSION_CODES.LOLLIPOP_MR1;
- mIsEphemeralApp = packageInfo.applicationInfo.isInstantApp();
- mAppOps = appOpsManager;
- mActivityManager = context.getSystemService(ActivityManager.class);
- mDeclaringPackage = declaringPackage;
- mName = name;
- mLabel = label;
- mFullLabel = fullLabel;
- mDescription = description;
- mCollator = Collator.getInstance(
- context.getResources().getConfiguration().getLocales().get(0));
- mRequest = request;
- mRequestDetail = requestDetail;
- mBackgroundRequest = backgroundRequest;
- mBackgroundRequestDetail = backgroundRequestDetail;
- mUpgradeRequest = upgradeRequest;
- mUpgradeRequestDetail = upgradeRequestDetail;
- mDelayChanges = delayChanges;
- if (iconResId != 0) {
- mIconPkg = iconPkg;
- mIconResId = iconResId;
- } else {
- mIconPkg = context.getPackageName();
- mIconResId = 0; // doesn't matter to CDM
- }
-
- mIsNonIsolatedStorage = targetSDK < Build.VERSION_CODES.P
- || (targetSDK < Build.VERSION_CODES.R
- && mAppOps.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE,
- packageInfo.applicationInfo.uid, packageInfo.packageName) == MODE_ALLOWED);
- }
-
- boolean doesSupportRuntimePermissions() {
- return mAppSupportsRuntimePermissions;
- }
-
- boolean isGrantingAllowed() {
- return (!mIsEphemeralApp || mContainsEphemeralPermission)
- && (mAppSupportsRuntimePermissions || mContainsPreRuntimePermission);
- }
-
- boolean isReviewRequired() {
- if (mAppSupportsRuntimePermissions) {
- return false;
- }
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isReviewRequired()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Are any of the permissions in this group user sensitive.
- *
- * @return {@code true} if any of the permissions in the group is user sensitive.
- */
- public boolean isUserSensitive() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isUserSensitive()) {
- return true;
- }
- }
- return false;
- }
-
- void unsetReviewRequired() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isReviewRequired()) {
- permission.unsetReviewRequired();
- }
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
- }
- }
-
- boolean hasGrantedByDefaultPermission() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isGrantedByDefault()) {
- return true;
- }
- }
- return false;
- }
-
- public PackageInfo getApp() {
- return mPackageInfo;
- }
-
- String getName() {
- return mName;
- }
-
- String getDeclaringPackage() {
- return mDeclaringPackage;
- }
-
- String getIconPkg() {
- return mIconPkg;
- }
-
- int getIconResId() {
- return mIconResId;
- }
-
- CharSequence getLabel() {
- return mLabel;
- }
-
- /**
- * Get the full un-ellipsized label of the permission group.
- *
- * @return the full label of the group.
- */
- public CharSequence getFullLabel() {
- return mFullLabel;
- }
-
- /**
- * @hide
- * @return The resource Id of the request string.
- */
- public @StringRes int getRequest() {
- return mRequest;
- }
-
- /**
- * Get the (subtitle) message explaining to the user that the permission is only granted to
- * the apps running in the foreground.
- *
- * @return the message or 0 if unset
- */
- public @StringRes int getRequestDetail() {
- return mRequestDetail;
- }
-
- /**
- * Get the title of the dialog explaining to the user that the permission is granted while
- * the app is in background and in foreground.
- *
- * @return the message or 0 if unset
- */
- public @StringRes int getBackgroundRequest() {
- return mBackgroundRequest;
- }
-
- /**
- * Get the (subtitle) message explaining to the user that the she/he is about to allow the
- * app to have background access.
- *
- * @return the message or 0 if unset
- */
- public @StringRes int getBackgroundRequestDetail() {
- return mBackgroundRequestDetail;
- }
-
- /**
- * Get the title of the dialog explaining to the user that the permission, which was
- * previously only granted for foreground, is granted while the app is in background and in
- * foreground.
- *
- * @return the message or 0 if unset
- */
- public @StringRes int getUpgradeRequest() {
- return mUpgradeRequest;
- }
-
- /**
- * Get the (subtitle) message explaining to the user that the she/he is about to allow the
- * app to have background access while currently having foreground only.
- *
- * @return the message or 0 if unset
- */
- public @StringRes int getUpgradeRequestDetail() {
- return mUpgradeRequestDetail;
- }
-
- public CharSequence getDescription() {
- return mDescription;
- }
-
- public UserHandle getUser() {
- return mUserHandle;
- }
-
- /**
- * Check if the group contains the permission.
- */
- public boolean hasPermission(String permission) {
- return mPermissions.get(permission) != null;
- }
-
- /**
- * Return a permission if in this group.
- *
- * @param permissionName The name of the permission
- *
- * @return The permission
- */
- public @Nullable Permission getPermission(@NonNull String permissionName) {
- return mPermissions.get(permissionName);
- }
-
- /**
- * Check if at least one of the permissions in the entire permission group should be considered
- * granted.
- */
- public boolean areRuntimePermissionsGranted() {
- return areRuntimePermissionsGranted(null);
- }
-
- /**
- * Check if at least one of the permissions in the filterPermissions should be considered
- * granted.
- */
- public boolean areRuntimePermissionsGranted(String[] filterPermissions) {
- return areRuntimePermissionsGranted(filterPermissions, false);
- }
-
- /**
- * @param filterPermissions the permissions to check for, null for all in this group
- * @param asOneTime add the requirement that at least one of the granted permissions must have
- * the ONE_TIME flag to return true
- */
- public boolean areRuntimePermissionsGranted(String[] filterPermissions, boolean asOneTime) {
- return areRuntimePermissionsGranted(filterPermissions, asOneTime, true);
- }
-
- /**
- * Returns true if at least one of the permissions in filterPermissions (or the entire
- * permission group if null) should be considered granted and satisfy the requirements
- * described by asOneTime and includingAppOp.
- *
- * @param filterPermissions the permissions to check for, null for all in this group
- * @param asOneTime add the requirement that the granted permission must have the ONE_TIME flag
- * @param includingAppOp add the requirement that if the granted permissions has a
- * corresponding AppOp, it must be allowed.
- */
- public boolean areRuntimePermissionsGranted(String[] filterPermissions, boolean asOneTime,
- boolean includingAppOp) {
- if (LocationUtils.isLocationGroupAndProvider(mContext, mName, mPackageInfo.packageName)) {
- return LocationUtils.isLocationEnabled(mContext) && !asOneTime;
- }
- // The permission of the extra location controller package is determined by the status of
- // the controller package itself.
- if (LocationUtils.isLocationGroupAndControllerExtraPackage(
- mContext, mName, mPackageInfo.packageName)) {
- return LocationUtils.isExtraLocationControllerPackageEnabled(mContext) && !asOneTime;
- }
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (filterPermissions != null
- && !ArrayUtils.contains(filterPermissions, permission.getName())) {
- continue;
- }
- boolean isGranted = includingAppOp ? permission.isGrantedIncludingAppOp()
- : permission.isGranted();
- if (isGranted && (!asOneTime || permission.isOneTime())) {
- return true;
- }
- }
- if (mBackgroundPermissions != null) {
- // If asOneTime is true and none of the foreground permissions are one-time, but some
- // background permissions are, then we still want to return true.
- return mBackgroundPermissions.areRuntimePermissionsGranted(filterPermissions,
- asOneTime, includingAppOp);
- }
- return false;
- }
-
- boolean grantRuntimePermissions(boolean setByTheUser, boolean fixedByTheUser) {
- return grantRuntimePermissions(setByTheUser, fixedByTheUser, null);
- }
-
- /**
- * Set mode of an app-op if needed.
- *
- * @param op The op to set
- * @param uid The uid the app-op belongs to
- * @param mode The new mode
- *
- * @return {@code true} iff app-op was changed
- */
- private boolean setAppOpMode(@NonNull String op, int uid, int mode) {
- int currentMode = mAppOps.unsafeCheckOpRaw(op, uid, mPackageInfo.packageName);
- if (currentMode == mode) {
- return false;
- }
-
- mAppOps.setUidMode(op, uid, mode);
- return true;
- }
-
- /**
- * Allow the app op for a permission/uid.
- *
- * <p>There are three cases:
- * <dl>
- * <dt>The permission is not split into foreground/background</dt>
- * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd>
- * <dt>The permission is a foreground permission:</dt>
- * <dd><dl><dt>The background permission permission is granted</dt>
- * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_ALLOWED}</dd>
- * <dt>The background permission permission is <u>not</u> granted</dt>
- * <dd>The app op matching the permission will be set to
- * {@link AppOpsManager#MODE_FOREGROUND}</dd>
- * </dl></dd>
- * <dt>The permission is a background permission:</dt>
- * <dd>All granted foreground permissions for this background permission will be set to
- * {@link AppOpsManager#MODE_ALLOWED}</dd>
- * </dl>
- *
- * @param permission The permission which has an appOps that should be allowed
- * @param uid The uid of the process the app op is for
- *
- * @return {@code true} iff app-op was changed
- */
- private boolean allowAppOp(Permission permission, int uid) {
- boolean wasChanged = false;
-
- if (permission.isBackgroundPermission()) {
- ArrayList<Permission> foregroundPermissions = permission.getForegroundPermissions();
-
- int numForegroundPermissions = foregroundPermissions.size();
- for (int i = 0; i < numForegroundPermissions; i++) {
- Permission foregroundPermission = foregroundPermissions.get(i);
- if (foregroundPermission.isAppOpAllowed()) {
- wasChanged |= setAppOpMode(foregroundPermission.getAppOp(), uid, MODE_ALLOWED);
- }
- }
- } else {
- if (permission.hasBackgroundPermission()) {
- Permission backgroundPermission = permission.getBackgroundPermission();
-
- if (backgroundPermission == null) {
- // The app requested a permission that has a background permission but it did
- // not request the background permission, hence it can never get background
- // access
- wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_FOREGROUND);
- } else {
- if (backgroundPermission.isAppOpAllowed()) {
- wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_ALLOWED);
- } else {
- wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_FOREGROUND);
- }
- }
- } else {
- wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_ALLOWED);
- }
- }
-
- return wasChanged;
- }
-
- /**
- * Kills the app the permissions belong to (and all apps sharing the same uid)
- *
- * @param reason The reason why the apps are killed
- */
- private void killApp(String reason) {
- mActivityManager.killUid(mPackageInfo.applicationInfo.uid, reason);
- }
-
- /**
- * Grant permissions of the group.
- *
- * <p>This also automatically grants all app ops for permissions that have app ops.
- * <p>This does <u>only</u> grant permissions in {@link #mPermissions}, i.e. usually not
- * the background permissions.
- *
- * @param setByTheUser If the user has made the decision. This does not unset the flag
- * @param fixedByTheUser If the user requested that she/he does not want to be asked again
- * @param filterPermissions If {@code null} all permissions of the group will be granted.
- * Otherwise only permissions in {@code filterPermissions} will be
- * granted.
- *
- * @return {@code true} iff all permissions of this group could be granted.
- */
- public boolean grantRuntimePermissions(boolean setByTheUser, boolean fixedByTheUser,
- String[] filterPermissions) {
- boolean killApp = false;
- boolean wasAllGranted = true;
-
- // We toggle permissions only to apps that support runtime
- // permissions, otherwise we toggle the app op corresponding
- // to the permission if the permission is granted to the app.
- for (Permission permission : mPermissions.values()) {
- if (filterPermissions != null
- && !ArrayUtils.contains(filterPermissions, permission.getName())) {
- continue;
- }
-
- if (!permission.isGrantingAllowed(mIsEphemeralApp, mAppSupportsRuntimePermissions)) {
- // Skip unallowed permissions.
- continue;
- }
-
- boolean wasGranted = permission.isGrantedIncludingAppOp();
-
- if (mAppSupportsRuntimePermissions) {
- // Do not touch permissions fixed by the system.
- if (permission.isSystemFixed()) {
- wasAllGranted = false;
- break;
- }
-
- // Ensure the permission app op is enabled before the permission grant.
- if (permission.affectsAppOp() && !permission.isAppOpAllowed()) {
- permission.setAppOpAllowed(true);
- }
-
- // Grant the permission if needed.
- if (!permission.isGranted()) {
- permission.setGranted(true);
- }
-
- // Update the permission flags.
- if (!fixedByTheUser) {
- if (permission.isUserFixed()) {
- permission.setUserFixed(false);
- }
- if (setByTheUser) {
- if (!permission.isUserSet()) {
- permission.setUserSet(true);
- }
- }
- } else {
- if (!permission.isUserFixed()) {
- permission.setUserFixed(true);
- }
- if (permission.isUserSet()) {
- permission.setUserSet(false);
- }
- }
- if (permission.isReviewRequired()) {
- permission.unsetReviewRequired();
- }
- } else {
- // Legacy apps cannot have a not granted permission but just in case.
- if (!permission.isGranted()) {
- continue;
- }
-
- // If the permissions has no corresponding app op, then it is a
- // third-party one and we do not offer toggling of such permissions.
- if (permission.affectsAppOp()) {
- if (!permission.isAppOpAllowed()) {
- permission.setAppOpAllowed(true);
-
- // Legacy apps do not know that they have to retry access to a
- // resource due to changes in runtime permissions (app ops in this
- // case). Therefore, we restart them on app op change, so they
- // can pick up the change.
- killApp = true;
- }
-
- // Mark that the permission is not kept granted only for compatibility.
- if (permission.isRevokedCompat()) {
- permission.setRevokedCompat(false);
- }
- }
-
- // Granting a permission explicitly means the user already
- // reviewed it so clear the review flag on every grant.
- if (permission.isReviewRequired()) {
- permission.unsetReviewRequired();
- }
- }
-
- // If we newly grant background access to the fine location, double-guess the user some
- // time later if this was really the right choice.
- if (!wasGranted && permission.isGrantedIncludingAppOp()) {
- if (permission.getName().equals(ACCESS_FINE_LOCATION)) {
- Permission bgPerm = permission.getBackgroundPermission();
- if (bgPerm != null) {
- if (bgPerm.isGrantedIncludingAppOp()) {
- mTriggerLocationAccessCheckOnPersist = true;
- }
- }
- } else if (permission.getName().equals(ACCESS_BACKGROUND_LOCATION)) {
- ArrayList<Permission> fgPerms = permission.getForegroundPermissions();
- if (fgPerms != null) {
- int numFgPerms = fgPerms.size();
- for (int fgPermNum = 0; fgPermNum < numFgPerms; fgPermNum++) {
- Permission fgPerm = fgPerms.get(fgPermNum);
-
- if (fgPerm.getName().equals(ACCESS_FINE_LOCATION)) {
- if (fgPerm.isGrantedIncludingAppOp()) {
- mTriggerLocationAccessCheckOnPersist = true;
- }
-
- break;
- }
- }
- }
- }
- }
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
-
- if (killApp) {
- killApp(KILL_REASON_APP_OP_CHANGE);
- }
- }
-
- return wasAllGranted;
- }
-
- boolean revokeRuntimePermissions(boolean fixedByTheUser) {
- return revokeRuntimePermissions(fixedByTheUser, null);
- }
-
- /**
- * Disallow the app op for a permission/uid.
- *
- * <p>There are three cases:
- * <dl>
- * <dt>The permission is not split into foreground/background</dt>
- * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd>
- * <dt>The permission is a foreground permission:</dt>
- * <dd>The app op matching the permission will be set to {@link AppOpsManager#MODE_IGNORED}</dd>
- * <dt>The permission is a background permission:</dt>
- * <dd>All granted foreground permissions for this background permission will be set to
- * {@link AppOpsManager#MODE_FOREGROUND}</dd>
- * </dl>
- *
- * @param permission The permission which has an appOps that should be disallowed
- * @param uid The uid of the process the app op if for
- *
- * @return {@code true} iff app-op was changed
- */
- private boolean disallowAppOp(Permission permission, int uid) {
- boolean wasChanged = false;
-
- if (permission.isBackgroundPermission()) {
- ArrayList<Permission> foregroundPermissions = permission.getForegroundPermissions();
-
- int numForegroundPermissions = foregroundPermissions.size();
- for (int i = 0; i < numForegroundPermissions; i++) {
- Permission foregroundPermission = foregroundPermissions.get(i);
- if (foregroundPermission.isAppOpAllowed()) {
- wasChanged |= setAppOpMode(foregroundPermission.getAppOp(), uid,
- MODE_FOREGROUND);
- }
- }
- } else {
- wasChanged = setAppOpMode(permission.getAppOp(), uid, MODE_IGNORED);
- }
-
- return wasChanged;
- }
-
- /**
- * Revoke permissions of the group.
- *
- * <p>This also disallows all app ops for permissions that have app ops.
- * <p>This does <u>only</u> revoke permissions in {@link #mPermissions}, i.e. usually not
- * the background permissions.
- *
- * @param fixedByTheUser If the user requested that she/he does not want to be asked again
- * @param filterPermissions If {@code null} all permissions of the group will be revoked.
- * Otherwise only permissions in {@code filterPermissions} will be
- * revoked.
- *
- * @return {@code true} iff all permissions of this group could be revoked.
- */
- public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
- boolean killApp = false;
- boolean wasAllRevoked = true;
-
- // We toggle permissions only to apps that support runtime
- // permissions, otherwise we toggle the app op corresponding
- // to the permission if the permission is granted to the app.
- for (Permission permission : mPermissions.values()) {
- if (filterPermissions != null
- && !ArrayUtils.contains(filterPermissions, permission.getName())) {
- continue;
- }
-
- // Do not touch permissions fixed by the system.
- if (permission.isSystemFixed()) {
- wasAllRevoked = false;
- break;
- }
-
- if (mAppSupportsRuntimePermissions) {
- // Revoke the permission if needed.
- if (permission.isGranted()) {
- permission.setGranted(false);
- }
-
- // Update the permission flags.
- if (fixedByTheUser) {
- // Take a note that the user fixed the permission.
- if (permission.isUserSet() || !permission.isUserFixed()) {
- permission.setUserSet(false);
- permission.setUserFixed(true);
- }
- } else {
- if (!permission.isUserSet() || permission.isUserFixed()) {
- permission.setUserSet(true);
- permission.setUserFixed(false);
- }
- }
-
- if (permission.affectsAppOp()) {
- permission.setAppOpAllowed(false);
- }
- } else {
- // Legacy apps cannot have a non-granted permission but just in case.
- if (!permission.isGranted()) {
- continue;
- }
-
- // If the permission has no corresponding app op, then it is a
- // third-party one and we do not offer toggling of such permissions.
- if (permission.affectsAppOp()) {
- if (permission.isAppOpAllowed()) {
- permission.setAppOpAllowed(false);
-
- // Disabling an app op may put the app in a situation in which it
- // has a handle to state it shouldn't have, so we have to kill the
- // app. This matches the revoke runtime permission behavior.
- killApp = true;
- }
-
- // Mark that the permission is kept granted only for compatibility.
- if (!permission.isRevokedCompat()) {
- permission.setRevokedCompat(true);
- }
- }
- }
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
-
- if (killApp) {
- killApp(KILL_REASON_APP_OP_CHANGE);
- }
- }
-
- return wasAllRevoked;
- }
-
- /**
- * Mark permissions in this group as policy fixed.
- *
- * @param filterPermissions The permissions to mark
- */
- public void setPolicyFixed(@NonNull String[] filterPermissions) {
- for (String permissionName : filterPermissions) {
- Permission permission = mPermissions.get(permissionName);
-
- if (permission != null) {
- permission.setPolicyFixed(true);
- }
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
- }
- }
-
- /**
- * Set the user-fixed flag for all permissions in this group.
- *
- * @param isUsedFixed if the flag should be set or not
- */
- public void setUserFixed(boolean isUsedFixed) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- permission.setUserFixed(isUsedFixed);
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
- }
- }
-
- /**
- * Mark this group as having been self-revoked.
- */
- public void setSelfRevoked() {
- mIsSelfRevoked = true;
- }
-
- /**
- * Set the one-time flag for all permissions in this group.
- *
- * @param isOneTime if the flag should be set or not
- */
- public void setOneTime(boolean isOneTime) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- permission.setOneTime(isOneTime);
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
- }
- }
-
- /**
- * Set the user-set flag for all permissions in this group.
- *
- * @param isUserSet if the flag should be set or not
- */
- public void setUserSet(boolean isUserSet) {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- permission.setUserSet(isUserSet);
- }
-
- if (!mDelayChanges) {
- persistChanges(false);
- }
- }
-
- /**
- * Get all permissions in the group.
- */
- public ArrayList<Permission> getPermissions() {
- return new ArrayList<>(mPermissions.values());
- }
-
- /**
- * @return An {@link AppPermissionGroup}-object that contains all background permissions for
- * this group.
- */
- public AppPermissionGroup getBackgroundPermissions() {
- return mBackgroundPermissions;
- }
-
- /**
- * @return {@code true} iff the app request at least one permission in this group that has a
- * background permission. It is possible that the app does not request the matching background
- * permission and hence will only ever get foreground access, never background access.
- */
- public boolean hasPermissionWithBackgroundMode() {
- return mHasPermissionWithBackgroundMode;
- }
-
- /**
- * Is the group a storage permission group that is referring to an app that does not have
- * isolated storage
- *
- * @return {@code true} iff this is a storage group on an app that does not have isolated
- * storage
- */
- public boolean isNonIsolatedStorage() {
- return mIsNonIsolatedStorage;
- }
-
- /**
- * Whether this is group that contains all the background permission for regular permission
- * group.
- *
- * @return {@code true} iff this is a background permission group.
- *
- * @see #getBackgroundPermissions()
- */
- public boolean isBackgroundGroup() {
- return mPermissions.valueAt(0).isBackgroundPermission();
- }
-
- /**
- * Whether this group supports one-time permissions
- * @return {@code true} iff this group supports one-time permissions
- */
- public boolean supportsOneTimeGrant() {
- return Utils.supportsOneTimeGrant(getName());
- }
-
- int getFlags() {
- int flags = 0;
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- flags |= permission.getFlags();
- }
- return flags;
- }
-
- boolean isUserFixed() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isUserFixed()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Check if there's a permission in the group is policy fixed.
- */
- public boolean isPolicyFixed() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isPolicyFixed()) {
- return true;
- }
- }
- return false;
- }
-
- boolean isUserSet() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isUserSet()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Check if there's a permission in the group is system fixed.
- */
- public boolean isSystemFixed() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isSystemFixed()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * @return Whether any of the permissions in this group is one-time
- */
- public boolean isOneTime() {
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isOneTime()) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * @return Whether at least one permission is granted and every granted permission is one-time
- */
- public boolean isStrictlyOneTime() {
- boolean oneTimePermissionFound = false;
- final int permissionCount = mPermissions.size();
- for (int i = 0; i < permissionCount; i++) {
- Permission permission = mPermissions.valueAt(i);
- if (permission.isGranted()) {
- if (!permission.isOneTime()) {
- return false;
- }
- oneTimePermissionFound = true;
- }
- }
- return oneTimePermissionFound;
- }
-
- @Override
- public int compareTo(AppPermissionGroup another) {
- final int result = mCollator.compare(mLabel.toString(), another.mLabel.toString());
- if (result == 0) {
- // Unbadged before badged.
- return mPackageInfo.applicationInfo.uid
- - another.mPackageInfo.applicationInfo.uid;
- }
- return result;
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof AppPermissionGroup)) {
- return false;
- }
-
- AppPermissionGroup other = (AppPermissionGroup) o;
-
- boolean equal = mName.equals(other.mName)
- && mPackageInfo.packageName.equals(other.mPackageInfo.packageName)
- && mUserHandle.equals(other.mUserHandle)
- && mPermissions.equals(other.mPermissions);
- if (!equal) {
- return false;
- }
-
- if (mBackgroundPermissions != null && other.getBackgroundPermissions() != null) {
- return mBackgroundPermissions.getPermissions().equals(
- other.getBackgroundPermissions().getPermissions());
- }
- return mBackgroundPermissions == other.getBackgroundPermissions();
- }
-
- @Override
- public int hashCode() {
- ArrayList<Permission> backgroundPermissions = new ArrayList<>();
- if (mBackgroundPermissions != null) {
- backgroundPermissions = mBackgroundPermissions.getPermissions();
- }
- return Objects.hash(mName, mPackageInfo.packageName, mUserHandle, mPermissions,
- backgroundPermissions);
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append(getClass().getSimpleName());
- builder.append("{name=").append(mName);
- if (mBackgroundPermissions != null) {
- builder.append(", <has background permissions>}");
- }
- if (!mPermissions.isEmpty()) {
- builder.append(", <has permissions>}");
- } else {
- builder.append('}');
- }
- return builder.toString();
- }
-
- private void addPermission(Permission permission) {
- mPermissions.put(permission.getName(), permission);
- if (permission.isEphemeral()) {
- mContainsEphemeralPermission = true;
- }
- if (!permission.isRuntimeOnly()) {
- mContainsPreRuntimePermission = true;
- }
- }
-
- /**
- * If the changes to this group were delayed, persist them to the platform.
- *
- * @param mayKillBecauseOfAppOpsChange If the app these permissions belong to may be killed if
- * app ops change. If this is set to {@code false} the
- * caller has to make sure to kill the app if needed.
- */
- public void persistChanges(boolean mayKillBecauseOfAppOpsChange) {
- persistChanges(mayKillBecauseOfAppOpsChange, null, null);
- }
-
- /**
- * If the changes to this group were delayed, persist them to the platform.
- *
- * @param mayKillBecauseOfAppOpsChange If the app these permissions belong to may be killed if
- * app ops change. If this is set to {@code false} the
- * caller has to make sure to kill the app if needed.
- * @param revokeReason If any permissions are getting revoked, the reason for revoking them.
- */
- public void persistChanges(boolean mayKillBecauseOfAppOpsChange, String revokeReason) {
- persistChanges(mayKillBecauseOfAppOpsChange, revokeReason, null);
- }
-
- /**
- * If the changes to this group were delayed, persist them to the platform.
- *
- * @param mayKillBecauseOfAppOpsChange If the app these permissions belong to may be killed if
- * app ops change. If this is set to {@code false} the
- * caller has to make sure to kill the app if needed.
- * @param revokeReason If any permissions are getting revoked, the reason for revoking them.
- * @param filterPermissions If provided, only persist state for the given permissions
- */
- public void persistChanges(boolean mayKillBecauseOfAppOpsChange, String revokeReason,
- Set<String> filterPermissions) {
- int uid = mPackageInfo.applicationInfo.uid;
-
- int numPermissions = mPermissions.size();
- boolean shouldKillApp = false;
-
- for (int i = 0; i < numPermissions; i++) {
- Permission permission = mPermissions.valueAt(i);
-
- if (filterPermissions != null && !filterPermissions.contains(permission.getName())) {
- continue;
- }
-
- if (!permission.isSystemFixed()) {
- if (permission.isGranted()) {
- mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
- permission.getName(), mUserHandle);
- } else {
- boolean isCurrentlyGranted = mContext.checkPermission(permission.getName(), -1,
- uid) == PERMISSION_GRANTED;
-
- if (isCurrentlyGranted) {
- if (revokeReason == null) {
- mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
- permission.getName(), mUserHandle);
- } else {
- mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
- permission.getName(), mUserHandle, revokeReason);
- }
- }
- }
- }
-
-// int flags = (permission.isUserSet() ? PackageManager.FLAG_PERMISSION_USER_SET : 0)
-// | (permission.isUserFixed() ? PackageManager.FLAG_PERMISSION_USER_FIXED : 0)
-// | (permission.isRevokedCompat()
-// ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0)
-// | (permission.isPolicyFixed() ?
-// PackageManager.FLAG_PERMISSION_POLICY_FIXED : 0)
-// | (permission.isReviewRequired()
-// ? PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED : 0)
-// | (permission.isOneTime() ? PackageManager.FLAG_PERMISSION_ONE_TIME : 0)
-// | (permission.isSelectedLocationAccuracy()
-// ? PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY : 0);
-
-// mPackageManager.updatePermissionFlags(permission.getName(),
-// mPackageInfo.packageName,
-// PackageManager.FLAG_PERMISSION_USER_SET
-// | PackageManager.FLAG_PERMISSION_USER_FIXED
-// | PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
-// | PackageManager.FLAG_PERMISSION_POLICY_FIXED
-// | (permission.isReviewRequired()
-// ? 0 : PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED)
-// | PackageManager.FLAG_PERMISSION_ONE_TIME
-// | PackageManager.FLAG_PERMISSION_AUTO_REVOKED // clear auto revoke
-// | PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY,
-// flags, mUserHandle);
-
- if (permission.affectsAppOp()) {
- if (!permission.isSystemFixed()) {
- // Enabling/Disabling an app op may put the app in a situation in which it has
- // a handle to state it shouldn't have, so we have to kill the app. This matches
- // the revoke runtime permission behavior.
- if (permission.isAppOpAllowed()) {
- boolean wasChanged = allowAppOp(permission, uid);
- shouldKillApp |= wasChanged && !mAppSupportsRuntimePermissions;
- } else {
- shouldKillApp |= disallowAppOp(permission, uid);
- }
- }
- }
- }
-
- if (mayKillBecauseOfAppOpsChange && shouldKillApp) {
- killApp(KILL_REASON_APP_OP_CHANGE);
- }
-
-// if (mTriggerLocationAccessCheckOnPersist) {
-// new LocationAccessCheck(mContext, null).checkLocationAccessSoon();
-// mTriggerLocationAccessCheckOnPersist = false;
-// }
-
-// String packageName = mPackageInfo.packageName;
-// if (areRuntimePermissionsGranted(null, true, false)) {
-// // Required to read device config in Utils.getOneTimePermissions*().
-// final long token = Binder.clearCallingIdentity();
-// try {
-// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
-// mContext.getSystemService(PermissionManager.class)
-// .startOneTimePermissionSession(packageName,
-// Utils.getOneTimePermissionsTimeout(),
-// Utils.getOneTimePermissionsKilledDelay(mIsSelfRevoked),
-// ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
-// ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE);
-// } else {
-// mContext.getSystemService(PermissionManager.class)
-// .startOneTimePermissionSession(packageName,
-// Utils.getOneTimePermissionsTimeout(),
-// ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
-// ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE);
-// }
-// } finally {
-// Binder.restoreCallingIdentity(token);
-// }
-// } else {
-// mContext.getSystemService(PermissionManager.class)
-// .stopOneTimePermissionSession(packageName);
-// }
- }
-
- /**
- * Check if permission group contains a runtime permission that split from an installed
- * permission and the split happened in an Android version higher than app's targetSdk.
- *
- * @return {@code true} if there is such permission, {@code false} otherwise
- */
- public boolean hasInstallToRuntimeSplit() {
- PermissionManager permissionManager =
- (PermissionManager) mContext.getSystemService(PermissionManager.class);
-
- int numSplitPerms = permissionManager.getSplitPermissions().size();
- for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
- PermissionManager.SplitPermissionInfo spi =
- permissionManager.getSplitPermissions().get(splitPermNum);
- String splitPerm = spi.getSplitPermission();
-
- PermissionInfo pi;
- try {
- pi = mPackageManager.getPermissionInfo(splitPerm, 0);
- } catch (NameNotFoundException e) {
- Log.w(LOG_TAG, "No such permission: " + splitPerm, e);
- continue;
- }
-
- // Skip if split permission is not "install" permission.
- if (pi.getProtection() != pi.PROTECTION_NORMAL) {
- continue;
- }
-
- List<String> newPerms = spi.getNewPermissions();
- int numNewPerms = newPerms.size();
- for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) {
- String newPerm = newPerms.get(newPermNum);
-
- if (!hasPermission(newPerm)) {
- continue;
- }
-
- try {
- pi = mPackageManager.getPermissionInfo(newPerm, 0);
- } catch (NameNotFoundException e) {
- Log.w(LOG_TAG, "No such permission: " + newPerm, e);
- continue;
- }
-
- // Skip if new permission is not "runtime" permission.
- if (pi.getProtection() != pi.PROTECTION_DANGEROUS) {
- continue;
- }
-
- if (mPackageInfo.applicationInfo.targetSdkVersion < spi.getTargetSdk()) {
- return true;
- }
- }
- }
- return false;
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissions.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissions.java
deleted file mode 100644
index 9ef8d532dbfc..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/AppPermissions.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.datatransfer.permbackup.model;
-
-import android.content.Context;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * An app that requests permissions.
- *
- * <p>Allows to query all permission groups of the app and which permission belongs to which group.
- */
-public final class AppPermissions {
- /**
- * All permission groups the app requests. Background permission groups are attached to their
- * foreground groups.
- */
- private final ArrayList<AppPermissionGroup> mGroups = new ArrayList<>();
-
- /** Cache: group name -> group */
- private final ArrayMap<String, AppPermissionGroup> mGroupNameToGroup = new ArrayMap<>();
-
- /** Cache: permission name -> group. Might point to background group */
- private final ArrayMap<String, AppPermissionGroup> mPermissionNameToGroup = new ArrayMap<>();
-
- private final Context mContext;
-
- private final CharSequence mAppLabel;
-
- private final Runnable mOnErrorCallback;
-
- private final boolean mSortGroups;
-
- /** Do not actually commit changes to the platform until {@link #persistChanges} is called */
- private final boolean mDelayChanges;
-
- private PackageInfo mPackageInfo;
-
- public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups,
- Runnable onErrorCallback) {
- this(context, packageInfo, sortGroups, false, onErrorCallback);
- }
-
- public AppPermissions(Context context, PackageInfo packageInfo, boolean sortGroups,
- boolean delayChanges, Runnable onErrorCallback) {
- mContext = context;
- mPackageInfo = packageInfo;
- mAppLabel = null; // doesn't matter for CDM
- mSortGroups = sortGroups;
- mDelayChanges = delayChanges;
- mOnErrorCallback = onErrorCallback;
- loadPermissionGroups();
- }
-
- public PackageInfo getPackageInfo() {
- return mPackageInfo;
- }
-
- /**
- * Refresh package info and permission groups.
- */
- public void refresh() {
- loadPackageInfo();
- loadPermissionGroups();
- }
-
- public CharSequence getAppLabel() {
- return mAppLabel;
- }
-
- /**
- * Get permission group by name.
- */
- public AppPermissionGroup getPermissionGroup(String name) {
- return mGroupNameToGroup.get(name);
- }
-
- public List<AppPermissionGroup> getPermissionGroups() {
- return mGroups;
- }
-
- /**
- * Check if the group is review required.
- */
- public boolean isReviewRequired() {
- final int groupCount = mGroups.size();
- for (int i = 0; i < groupCount; i++) {
- AppPermissionGroup group = mGroups.get(i);
- if (group.isReviewRequired()) {
- return true;
- }
- }
- return false;
- }
-
- private void loadPackageInfo() {
- try {
- mPackageInfo = mContext.createPackageContextAsUser(mPackageInfo.packageName, 0,
- UserHandle.getUserHandleForUid(mPackageInfo.applicationInfo.uid))
- .getPackageManager().getPackageInfo(mPackageInfo.packageName,
- PackageManager.GET_PERMISSIONS);
- } catch (PackageManager.NameNotFoundException e) {
- if (mOnErrorCallback != null) {
- mOnErrorCallback.run();
- }
- }
- }
-
- /**
- * Add all individual permissions of the {@code group} to the {@link #mPermissionNameToGroup}
- * lookup table.
- *
- * @param group The group of permissions to add
- */
- private void addAllPermissions(AppPermissionGroup group) {
- ArrayList<Permission> perms = group.getPermissions();
-
- int numPerms = perms.size();
- for (int permNum = 0; permNum < numPerms; permNum++) {
- mPermissionNameToGroup.put(perms.get(permNum).getName(), group);
- }
- }
-
- private void loadPermissionGroups() {
- mGroups.clear();
- mGroupNameToGroup.clear();
- mPermissionNameToGroup.clear();
-
- if (mPackageInfo.requestedPermissions != null) {
- for (String requestedPerm : mPackageInfo.requestedPermissions) {
- if (getGroupForPermission(requestedPerm) == null) {
- AppPermissionGroup group = AppPermissionGroup.create(mContext, mPackageInfo,
- requestedPerm, mDelayChanges);
- if (group == null) {
- continue;
- }
-
- mGroups.add(group);
- mGroupNameToGroup.put(group.getName(), group);
-
- addAllPermissions(group);
-
- AppPermissionGroup backgroundGroup = group.getBackgroundPermissions();
- if (backgroundGroup != null) {
- addAllPermissions(backgroundGroup);
- }
- }
- }
-
- if (mSortGroups) {
- Collections.sort(mGroups);
- }
- }
- }
-
- /**
- * Find the group a permission belongs to.
- *
- * <p>The group found might be a background group.
- *
- * @param permission The name of the permission
- *
- * @return The group the permission belongs to
- */
- public AppPermissionGroup getGroupForPermission(String permission) {
- return mPermissionNameToGroup.get(permission);
- }
-
- /**
- * If the changes to the permission groups were delayed, persist them now.
- *
- * @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is
- * set to {@code false} the caller has to make sure to kill
- * the app if needed.
- */
- public void persistChanges(boolean mayKillBecauseOfAppOpsChange) {
- persistChanges(mayKillBecauseOfAppOpsChange, null);
- }
-
- /**
- * If the changes to the permission groups were delayed, persist them now.
- *
- * @param mayKillBecauseOfAppOpsChange If the app may be killed if app ops change. If this is
- * set to {@code false} the caller has to make sure to kill
- * the app if needed.
- * @param filterPermissions If provided, only persist state for the given permissions
- */
- public void persistChanges(boolean mayKillBecauseOfAppOpsChange,
- Set<String> filterPermissions) {
- if (mDelayChanges) {
- int numGroups = mGroups.size();
-
- for (int i = 0; i < numGroups; i++) {
- AppPermissionGroup group = mGroups.get(i);
- group.persistChanges(mayKillBecauseOfAppOpsChange, null, filterPermissions);
-
- AppPermissionGroup backgroundGroup = group.getBackgroundPermissions();
- if (backgroundGroup != null) {
- backgroundGroup.persistChanges(mayKillBecauseOfAppOpsChange, null,
- filterPermissions);
- }
- }
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/Permission.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/Permission.java
deleted file mode 100644
index 2bec970a1dea..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/model/Permission.java
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.datatransfer.permbackup.model;
-
-import android.annotation.NonNull;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
-
-import java.util.ArrayList;
-import java.util.Objects;
-
-/**
- * A permission and its properties.
- *
- * @see AppPermissionGroup
- */
-public final class Permission {
- private final @NonNull PermissionInfo mPermissionInfo;
- private final String mName;
- private final String mBackgroundPermissionName;
- private final String mAppOp;
-
- private boolean mGranted;
- private boolean mAppOpAllowed;
- private int mFlags;
- private boolean mIsEphemeral;
- private boolean mIsRuntimeOnly;
- private Permission mBackgroundPermission;
- private ArrayList<Permission> mForegroundPermissions;
- private boolean mWhitelisted;
-
- public Permission(String name, @NonNull PermissionInfo permissionInfo, boolean granted,
- String appOp, boolean appOpAllowed, int flags) {
- mPermissionInfo = permissionInfo;
- mName = name;
- mBackgroundPermissionName = permissionInfo.backgroundPermission;
- mGranted = granted;
- mAppOp = appOp;
- mAppOpAllowed = appOpAllowed;
- mFlags = flags;
- mIsEphemeral =
- (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
- mIsRuntimeOnly =
- (permissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
- }
-
- /**
- * Mark this permission as background permission for {@code foregroundPermissions}.
- *
- * @param foregroundPermission The foreground permission
- */
- public void addForegroundPermissions(Permission foregroundPermission) {
- if (mForegroundPermissions == null) {
- mForegroundPermissions = new ArrayList<>(1);
- }
- mForegroundPermissions.add(foregroundPermission);
- }
-
- /**
- * Mark this permission as foreground permission for {@code backgroundPermission}.
- *
- * @param backgroundPermission The background permission
- */
- public void setBackgroundPermission(Permission backgroundPermission) {
- mBackgroundPermission = backgroundPermission;
- }
-
- public PermissionInfo getPermissionInfo() {
- return mPermissionInfo;
- }
-
- public String getName() {
- return mName;
- }
-
- public String getAppOp() {
- return mAppOp;
- }
-
- public int getFlags() {
- return mFlags;
- }
-
- boolean isHardRestricted() {
- return (mPermissionInfo.flags & PermissionInfo.FLAG_HARD_RESTRICTED) != 0;
- }
-
- boolean isSoftRestricted() {
- return (mPermissionInfo.flags & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0;
- }
-
- /**
- * Does this permission affect app ops.
- *
- * <p>I.e. does this permission have a matching app op or is this a background permission. All
- * background permissions affect the app op of its assigned foreground permission.
- *
- * @return {@code true} if this permission affects app ops
- */
- public boolean affectsAppOp() {
- return mAppOp != null || isBackgroundPermission();
- }
-
- /**
- * Check if the permission is granted.
- *
- * <p>This ignores the state of the app-op. I.e. for apps not handling runtime permissions, this
- * always returns {@code true}.
- *
- * @return If the permission is granted
- */
- public boolean isGranted() {
- return mGranted;
- }
-
- /**
- * Check if the permission is granted, also considering the state of the app-op.
- *
- * <p>For the UI, check the grant state of the whole group via
- * {@link AppPermissionGroup#areRuntimePermissionsGranted}.
- *
- * @return {@code true} if the permission (and the app-op) is granted.
- */
- public boolean isGrantedIncludingAppOp() {
- return mGranted && (!affectsAppOp() || isAppOpAllowed()) && !isReviewRequired();
- }
-
- public boolean isReviewRequired() {
- return (mFlags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0;
- }
-
- /**
- * Unset review required flag.
- */
- public void unsetReviewRequired() {
- mFlags &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
- }
-
- public void setGranted(boolean mGranted) {
- this.mGranted = mGranted;
- }
-
- public boolean isAppOpAllowed() {
- return mAppOpAllowed;
- }
-
- /**
- * Check if it's user fixed.
- */
- public boolean isUserFixed() {
- return (mFlags & PackageManager.FLAG_PERMISSION_USER_FIXED) != 0;
- }
-
- /**
- * Set user fixed flag.
- */
- public void setUserFixed(boolean userFixed) {
- if (userFixed) {
- mFlags |= PackageManager.FLAG_PERMISSION_USER_FIXED;
- } else {
- mFlags &= ~PackageManager.FLAG_PERMISSION_USER_FIXED;
- }
- }
-
- /**
- * Sets the one-time permission flag
- * @param oneTime true to set the flag, false to unset it
- */
- public void setOneTime(boolean oneTime) {
- if (oneTime) {
- mFlags |= PackageManager.FLAG_PERMISSION_ONE_TIME;
- } else {
- mFlags &= ~PackageManager.FLAG_PERMISSION_ONE_TIME;
- }
- }
-
- public boolean isSelectedLocationAccuracy() {
- return (mFlags & PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY) != 0;
- }
-
- /**
- * Sets the selected-location-accuracy permission flag
- * @param selectedLocationAccuracy true to set the flag, false to unset it
- */
- public void setSelectedLocationAccuracy(boolean selectedLocationAccuracy) {
- if (selectedLocationAccuracy) {
- mFlags |= PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
- } else {
- mFlags &= ~PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
- }
- }
-
- public boolean isSystemFixed() {
- return (mFlags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0;
- }
-
- public boolean isPolicyFixed() {
- return (mFlags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0;
- }
-
- public boolean isUserSet() {
- return (mFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
- }
-
- public boolean isGrantedByDefault() {
- return (mFlags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0;
- }
-
- /**
- * Is the permission user sensitive, i.e. should it always be shown to the user.
- *
- * <p>Non-sensitive permission are usually hidden behind a setting in an overflow menu or
- * some other kind of flag.
- *
- * @return {@code true} if the permission is user sensitive.
- */
- public boolean isUserSensitive() {
- if (isGrantedIncludingAppOp()) {
- return (mFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
- } else {
- return (mFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) != 0;
- }
- }
-
- /**
- * If this permission is split into a foreground and background permission, this is the name
- * of the background permission.
- *
- * @return The name of the background permission or {@code null} if the permission is not split
- */
- public String getBackgroundPermissionName() {
- return mBackgroundPermissionName;
- }
-
- /**
- * @return If this permission is split into a foreground and background permission,
- * returns the background permission
- */
- public Permission getBackgroundPermission() {
- return mBackgroundPermission;
- }
-
- /**
- * @return If this permission is split into a foreground and background permission,
- * returns the foreground permission
- */
- public ArrayList<Permission> getForegroundPermissions() {
- return mForegroundPermissions;
- }
-
- /**
- * @return {@code true} iff this is the foreground permission of a background-foreground-split
- * permission
- */
- public boolean hasBackgroundPermission() {
- return mBackgroundPermissionName != null;
- }
-
- /**
- * @return {@code true} iff this is the background permission of a background-foreground-split
- * permission
- */
- public boolean isBackgroundPermission() {
- return mForegroundPermissions != null;
- }
-
- /**
- * @see PackageManager#FLAG_PERMISSION_ONE_TIME
- */
- public boolean isOneTime() {
- return (mFlags & PackageManager.FLAG_PERMISSION_ONE_TIME) != 0;
- }
-
- /**
- * Set userSet flag.
- */
- public void setUserSet(boolean userSet) {
- if (userSet) {
- mFlags |= PackageManager.FLAG_PERMISSION_USER_SET;
- } else {
- mFlags &= ~PackageManager.FLAG_PERMISSION_USER_SET;
- }
- }
-
- /**
- * Set policy fixed flag.
- */
- public void setPolicyFixed(boolean policyFixed) {
- if (policyFixed) {
- mFlags |= PackageManager.FLAG_PERMISSION_POLICY_FIXED;
- } else {
- mFlags &= ~PackageManager.FLAG_PERMISSION_POLICY_FIXED;
- }
- }
-
- /**
- * Check if the permission is revoke compat.
- */
- public boolean isRevokedCompat() {
- return (mFlags & PackageManager.FLAG_PERMISSION_REVOKED_COMPAT) != 0;
- }
-
- /**
- * Set revoke compat flag.
- */
- public void setRevokedCompat(boolean revokedCompat) {
- if (revokedCompat) {
- mFlags |= PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
- } else {
- mFlags &= ~PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
- }
- }
-
- /**
- * Set app op allowed flag.
- */
- public void setAppOpAllowed(boolean mAppOpAllowed) {
- this.mAppOpAllowed = mAppOpAllowed;
- }
-
- /**
- * Check if it's ephemeral.
- */
- public boolean isEphemeral() {
- return mIsEphemeral;
- }
-
- /**
- * Check if it's runtime only.
- */
- public boolean isRuntimeOnly() {
- return mIsRuntimeOnly;
- }
-
- /**
- * Check if it's granting allowed.
- */
- public boolean isGrantingAllowed(boolean isEphemeralApp, boolean supportsRuntimePermissions) {
- return (!isEphemeralApp || isEphemeral())
- && (supportsRuntimePermissions || !isRuntimeOnly());
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof Permission)) {
- return false;
- }
-
- Permission other = (Permission) o;
-
- if (!Objects.equals(getName(), other.getName()) || getFlags() != other.getFlags()
- || isGranted() != other.isGranted()) {
- return false;
- }
-
-
- // Only compare permission names, in order to avoid recursion
- if (getBackgroundPermission() != null && other.getBackgroundPermission() != null) {
- if (!Objects.equals(getBackgroundPermissionName(),
- other.getBackgroundPermissionName())) {
- return false;
- }
- } else if (getBackgroundPermission() != other.getBackgroundPermission()) {
- return false;
- }
-
- if (getForegroundPermissions() != null && other.getForegroundPermissions() != null) {
- ArrayList<Permission> others = other.getForegroundPermissions();
- if (getForegroundPermissions().size() != others.size()) {
- return false;
- }
- for (int i = 0; i < others.size(); i++) {
- if (!getForegroundPermissions().get(i).getName().equals(others.get(i).getName())) {
- return false;
- }
- }
- } else if (getForegroundPermissions() != null || other.getForegroundPermissions() != null) {
- return false;
- }
-
- return Objects.equals(getAppOp(), other.getAppOp())
- && isAppOpAllowed() == other.isAppOpAllowed();
- }
-
- @Override
- public int hashCode() {
- ArrayList<String> linkedPermissionNames = new ArrayList<>();
- if (mBackgroundPermission != null) {
- linkedPermissionNames.add(mBackgroundPermission.getName());
- }
- if (mForegroundPermissions != null) {
- for (Permission linkedPermission: mForegroundPermissions) {
- if (linkedPermission != null) {
- linkedPermissionNames.add(linkedPermission.getName());
- }
- }
- }
- return Objects.hash(mName, mFlags, mGranted, mAppOp, mAppOpAllowed, linkedPermissionNames);
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/ArrayUtils.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/ArrayUtils.java
deleted file mode 100644
index 7027528fc203..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/ArrayUtils.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.datatransfer.permbackup.utils;
-
-import android.annotation.Nullable;
-
-import java.util.Objects;
-
-/**
- * Utils for array manipulation.
- */
-public final class ArrayUtils {
- private ArrayUtils() { /* cannot be instantiated */ }
-
- /**
- * Checks if an array is null or has no elements.
- *
- * @param array the array to check for
- *
- * @return whether the array is null or has no elements.
- */
- public static <T> boolean isEmpty(@Nullable T[] array) {
- return array == null || array.length == 0;
- }
-
- /**
- * Checks that value is present as at least one of the elements of the array.
- * @param array the array to check in
- * @param value the value to check for
- * @return true if the value is present in the array
- */
- public static <T> boolean contains(T[] array, T value) {
- return indexOf(array, value) != -1;
- }
-
- /**
- * Return first index of {@code value} in {@code array}, or {@code -1} if
- * not found.
- */
- public static <T> int indexOf(T[] array, T value) {
- if (array == null) return -1;
- for (int i = 0; i < array.length; i++) {
- if (Objects.equals(array[i], value)) return i;
- }
- return -1;
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/LocationUtils.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/LocationUtils.java
deleted file mode 100644
index 9402e46d16d9..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/LocationUtils.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.datatransfer.permbackup.utils;
-
-import static android.location.LocationManager.EXTRA_LOCATION_ENABLED;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.location.LocationManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-/**
- * Utils for location service.
- */
-public class LocationUtils {
-
- public static final String LOCATION_PERMISSION = Manifest.permission_group.LOCATION;
- public static final String ACTIVITY_RECOGNITION_PERMISSION =
- Manifest.permission_group.ACTIVITY_RECOGNITION;
-
- private static final String TAG = LocationUtils.class.getSimpleName();
- private static final long LOCATION_UPDATE_DELAY_MS = 1000;
- private static final Handler sMainHandler = new Handler(Looper.getMainLooper());
-
-
- /** Start the settings page for the location controller extra package. */
- public static void startLocationControllerExtraPackageSettings(@NonNull Context context,
- @NonNull UserHandle user) {
- try {
- context.startActivityAsUser(new Intent(
- Settings.ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS), user);
- } catch (ActivityNotFoundException e) {
- // In rare cases where location controller extra package is set, but
- // no activity exists to handle the location controller extra package settings
- // intent, log an error instead of crashing permission controller.
- Log.e(TAG, "No activity to handle "
- + "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS");
- }
- }
-
- /**
- * Check if location is enabled.
- */
- public static boolean isLocationEnabled(Context context) {
- return context.getSystemService(LocationManager.class).isLocationEnabled();
- }
-
- /** Checks if the provided package is a location provider. */
- public static boolean isLocationProvider(Context context, String packageName) {
- return context.getSystemService(LocationManager.class).isProviderPackage(packageName);
- }
-
- /**
- * Check if group is location and the package is a location provider.
- */
- public static boolean isLocationGroupAndProvider(Context context, String groupName,
- String packageName) {
- return LOCATION_PERMISSION.equals(groupName) && isLocationProvider(context, packageName);
- }
-
- /**
- * Check if group is location and package is extra location controller.
- */
- public static boolean isLocationGroupAndControllerExtraPackage(@NonNull Context context,
- @NonNull String groupName, @NonNull String packageName) {
- return (LOCATION_PERMISSION.equals(groupName)
- || ACTIVITY_RECOGNITION_PERMISSION.equals(groupName))
- && packageName.equals(context.getSystemService(LocationManager.class)
- .getExtraLocationControllerPackage());
- }
-
- /** Returns whether the location controller extra package is enabled. */
- public static boolean isExtraLocationControllerPackageEnabled(Context context) {
- try {
- return context.getSystemService(LocationManager.class)
- .isExtraLocationControllerPackageEnabled();
- } catch (Exception e) {
- return false;
- }
-
- }
-
- /**
- * A Listener which responds to enabling or disabling of location on the device
- */
- public interface LocationListener {
-
- /**
- * A callback run any time we receive a broadcast stating the location enable state has
- * changed.
- * @param enabled Whether or not location is enabled
- */
- void onLocationStateChange(boolean enabled);
- }
-
- private static final ArrayList<LocationListener> sLocationListeners = new ArrayList<>();
-
- private static BroadcastReceiver sLocationBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- boolean isEnabled = intent.getBooleanExtra(EXTRA_LOCATION_ENABLED, true);
- sMainHandler.postDelayed(() -> {
- synchronized (sLocationListeners) {
- for (LocationListener l : sLocationListeners) {
- l.onLocationStateChange(isEnabled);
- }
- }
- }, LOCATION_UPDATE_DELAY_MS);
- }
- };
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/SoftRestrictedPermissionPolicy.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/SoftRestrictedPermissionPolicy.java
deleted file mode 100644
index d4940502f1e2..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/SoftRestrictedPermissionPolicy.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.datatransfer.permbackup.utils;
-
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
-import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
-
-import android.annotation.NonNull;
-import android.content.pm.PackageInfo;
-import android.os.Build;
-
-import com.android.server.companion.datatransfer.permbackup.model.Permission;
-
-/**
- * The behavior of soft restricted permissions is different for each permission. This class collects
- * the policies in one place.
- *
- * This is the twin of {@link com.android.server.policy.SoftRestrictedPermissionPolicy}
- */
-public abstract class SoftRestrictedPermissionPolicy {
-
- /**
- * Check if the permission should be shown in the UI.
- *
- * @param pkg the package the permission belongs to
- * @param permission the permission
- *
- * @return {@code true} iff the permission should be shown in the UI.
- */
- public static boolean shouldShow(@NonNull PackageInfo pkg, @NonNull Permission permission) {
- switch (permission.getName()) {
- case READ_EXTERNAL_STORAGE:
- case WRITE_EXTERNAL_STORAGE: {
- boolean isWhiteListed =
- (permission.getFlags() & Utils.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT)
- != 0;
- int targetSDK = pkg.applicationInfo.targetSdkVersion;
-
- return isWhiteListed || targetSDK >= Build.VERSION_CODES.Q;
- }
- default:
- return true;
- }
- }
-
- /**
- * Check if the permission should be shown in the UI.
- *
- * @param pkg the LightPackageInfo the permission belongs to
- * @param permissionName the name of the permission
- * @param permissionFlags the PermissionController flags (not the PermissionInfo flags) for
- * the permission
- *
- * @return {@code true} iff the permission should be shown in the UI.
- */
- public static boolean shouldShow(@NonNull PackageInfo pkg, @NonNull String permissionName,
- int permissionFlags) {
- switch (permissionName) {
- case READ_EXTERNAL_STORAGE:
- case WRITE_EXTERNAL_STORAGE: {
- boolean isWhiteListed =
- (permissionFlags & Utils.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
- return isWhiteListed || pkg.applicationInfo.targetSdkVersion
- >= Build.VERSION_CODES.Q;
- }
- default:
- return true;
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/Utils.java b/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/Utils.java
deleted file mode 100644
index 9350549d233e..000000000000
--- a/services/companion/java/com/android/server/companion/datatransfer/permbackup/utils/Utils.java
+++ /dev/null
@@ -1,819 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion.datatransfer.permbackup.utils;
-
-import static android.Manifest.permission_group.ACTIVITY_RECOGNITION;
-import static android.Manifest.permission_group.CALENDAR;
-import static android.Manifest.permission_group.CALL_LOG;
-import static android.Manifest.permission_group.CAMERA;
-import static android.Manifest.permission_group.CONTACTS;
-import static android.Manifest.permission_group.LOCATION;
-import static android.Manifest.permission_group.MICROPHONE;
-import static android.Manifest.permission_group.NEARBY_DEVICES;
-import static android.Manifest.permission_group.NOTIFICATIONS;
-import static android.Manifest.permission_group.PHONE;
-import static android.Manifest.permission_group.READ_MEDIA_AURAL;
-import static android.Manifest.permission_group.READ_MEDIA_VISUAL;
-import static android.Manifest.permission_group.SENSORS;
-import static android.Manifest.permission_group.SMS;
-import static android.Manifest.permission_group.STORAGE;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
-import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.Manifest;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AppOpsManager;
-import android.app.Application;
-import android.app.role.RoleManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageItemInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.PermissionInfo;
-import android.content.pm.ResolveInfo;
-import android.hardware.SensorPrivacyManager;
-import android.os.Build;
-import android.os.Process;
-import android.os.UserHandle;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.text.format.DateFormat;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-
-import com.android.server.companion.datatransfer.permbackup.model.AppPermissionGroup;
-
-import java.lang.annotation.Retention;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-
-/**
- * Util class for BackupHelper
- */
-public final class Utils {
-
- @Retention(SOURCE)
- @IntDef(value = {LAST_24H_SENSOR_TODAY, LAST_24H_SENSOR_YESTERDAY,
- LAST_24H_CONTENT_PROVIDER, NOT_IN_LAST_7D})
- public @interface AppPermsLastAccessType {}
- public static final int LAST_24H_SENSOR_TODAY = 1;
- public static final int LAST_24H_SENSOR_YESTERDAY = 2;
- public static final int LAST_24H_CONTENT_PROVIDER = 3;
- public static final int LAST_7D_SENSOR = 4;
- public static final int LAST_7D_CONTENT_PROVIDER = 5;
- public static final int NOT_IN_LAST_7D = 6;
-
- private static final List<String> SENSOR_DATA_PERMISSIONS = List.of(
- Manifest.permission_group.LOCATION,
- Manifest.permission_group.CAMERA,
- Manifest.permission_group.MICROPHONE
- );
-
- public static final List<String> STORAGE_SUPERGROUP_PERMISSIONS =
-// (SDK_INT < Build.VERSION_CODES.TIRAMISU) ? List.of() :
- List.of(
- Manifest.permission_group.STORAGE,
- Manifest.permission_group.READ_MEDIA_AURAL,
- Manifest.permission_group.READ_MEDIA_VISUAL
- );
-
- private static final String LOG_TAG = "Utils";
-
- public static final String OS_PKG = "android";
-
- public static final float DEFAULT_MAX_LABEL_SIZE_PX = 500f;
-
- /** The time an app needs to be unused in order to be hibernated */
- public static final String PROPERTY_HIBERNATION_UNUSED_THRESHOLD_MILLIS =
- "auto_revoke_unused_threshold_millis2";
-
- /** The frequency of running the job for hibernating apps */
- public static final String PROPERTY_HIBERNATION_CHECK_FREQUENCY_MILLIS =
- "auto_revoke_check_frequency_millis";
-
- /** Whether hibernation targets apps that target a pre-S SDK */
- public static final String PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS =
- "app_hibernation_targets_pre_s_apps";
-
- /** Whether or not app hibernation is enabled on the device **/
- public static final String PROPERTY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled";
-
- /** Whether to show the Permissions Hub. */
- private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
-
- /** The timeout for one-time permissions */
- private static final String PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS =
- "one_time_permissions_timeout_millis";
-
- /** The delay before ending a one-time permission session when all processes are dead */
- private static final String PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS =
- "one_time_permissions_killed_delay_millis";
-
- /** Whether to show location access check notifications. */
- private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED =
- "location_access_check_enabled";
-
- /** The time an app needs to be unused in order to be hibernated */
- public static final String PROPERTY_PERMISSION_DECISIONS_CHECK_OLD_FREQUENCY_MILLIS =
- "permission_decisions_check_old_frequency_millis";
-
- /** The time an app needs to be unused in order to be hibernated */
- public static final String PROPERTY_PERMISSION_DECISIONS_MAX_DATA_AGE_MILLIS =
- "permission_decisions_max_data_age_millis";
-
- /** Whether or not warning banner is displayed when device sensors are off **/
- public static final String PROPERTY_WARNING_BANNER_DISPLAY_ENABLED = "warning_banner_enabled";
-
- /** All permission whitelists. */
- public static final int FLAGS_PERMISSION_WHITELIST_ALL =
- PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
- | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
- | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER;
-
- /** All permission restriction exemptions. */
- public static final int FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT =
- FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT
- | FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT
- | FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
-
- /**
- * The default length of the timeout for one-time permissions
- */
- public static final long ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS = 1 * 60 * 1000; // 1 minute
-
- /**
- * The default length to wait before ending a one-time permission session after all processes
- * are dead.
- */
- public static final long ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS = 5 * 1000;
-
- /** Mapping permission -> group for all dangerous platform permissions */
- private static final ArrayMap<String, String> PLATFORM_PERMISSIONS;
-
- /** Mapping group -> permissions for all dangerous platform permissions */
- private static final ArrayMap<String, ArrayList<String>> PLATFORM_PERMISSION_GROUPS;
-
- /** Set of groups that will be able to receive one-time grant */
- private static final ArraySet<String> ONE_TIME_PERMISSION_GROUPS;
-
- /** Permission -> Sensor codes */
- private static final ArrayMap<String, Integer> PERM_SENSOR_CODES;
-
- public static final int FLAGS_ALWAYS_USER_SENSITIVE =
- FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
- | FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
-
- private static final String SYSTEM_PKG = "android";
-
- private static final String SYSTEM_AMBIENT_AUDIO_INTELLIGENCE =
- "android.app.role.SYSTEM_AMBIENT_AUDIO_INTELLIGENCE";
- private static final String SYSTEM_UI_INTELLIGENCE =
- "android.app.role.SYSTEM_UI_INTELLIGENCE";
- private static final String SYSTEM_AUDIO_INTELLIGENCE =
- "android.app.role.SYSTEM_AUDIO_INTELLIGENCE";
- private static final String SYSTEM_NOTIFICATION_INTELLIGENCE =
- "android.app.role.SYSTEM_NOTIFICATION_INTELLIGENCE";
- private static final String SYSTEM_TEXT_INTELLIGENCE =
- "android.app.role.SYSTEM_TEXT_INTELLIGENCE";
- private static final String SYSTEM_VISUAL_INTELLIGENCE =
- "android.app.role.SYSTEM_VISUAL_INTELLIGENCE";
-
- // TODO: theianchen Using hardcoded values here as a WIP solution for now.
- private static final String[] EXEMPTED_ROLES = {
- SYSTEM_AMBIENT_AUDIO_INTELLIGENCE,
- SYSTEM_UI_INTELLIGENCE,
- SYSTEM_AUDIO_INTELLIGENCE,
- SYSTEM_NOTIFICATION_INTELLIGENCE,
- SYSTEM_TEXT_INTELLIGENCE,
- SYSTEM_VISUAL_INTELLIGENCE,
- };
-
- static {
- PLATFORM_PERMISSIONS = new ArrayMap<>();
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CONTACTS, CONTACTS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CONTACTS, CONTACTS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.GET_ACCOUNTS, CONTACTS);
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALENDAR, CALENDAR);
- PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALENDAR, CALENDAR);
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.SEND_SMS, SMS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_SMS, SMS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_SMS, SMS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_MMS, SMS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.RECEIVE_WAP_PUSH, SMS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CELL_BROADCASTS, SMS);
-
- // If permissions are added to the Storage group, they must be added to the
- // STORAGE_PERMISSIONS list in PermissionManagerService in frameworks/base
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_EXTERNAL_STORAGE, STORAGE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, STORAGE);
-// if (SDK_INT < Build.VERSION_CODES.TIRAMISU) {
-// PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, STORAGE);
-// }
-
-// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_AUDIO, READ_MEDIA_AURAL);
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_IMAGES, READ_MEDIA_VISUAL);
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_MEDIA_VIDEO, READ_MEDIA_VISUAL);
- PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_MEDIA_LOCATION, READ_MEDIA_VISUAL);
-// }
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_FINE_LOCATION, LOCATION);
- PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_COARSE_LOCATION, LOCATION);
- PLATFORM_PERMISSIONS.put(Manifest.permission.ACCESS_BACKGROUND_LOCATION, LOCATION);
-
-// if (SDK_INT >= Build.VERSION_CODES.S) {
- PLATFORM_PERMISSIONS.put(Manifest.permission.BLUETOOTH_ADVERTISE, NEARBY_DEVICES);
- PLATFORM_PERMISSIONS.put(Manifest.permission.BLUETOOTH_CONNECT, NEARBY_DEVICES);
- PLATFORM_PERMISSIONS.put(Manifest.permission.BLUETOOTH_SCAN, NEARBY_DEVICES);
- PLATFORM_PERMISSIONS.put(Manifest.permission.UWB_RANGING, NEARBY_DEVICES);
-// }
-// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- PLATFORM_PERMISSIONS.put(Manifest.permission.NEARBY_WIFI_DEVICES, NEARBY_DEVICES);
-// }
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_CALL_LOG, CALL_LOG);
- PLATFORM_PERMISSIONS.put(Manifest.permission.WRITE_CALL_LOG, CALL_LOG);
- PLATFORM_PERMISSIONS.put(Manifest.permission.PROCESS_OUTGOING_CALLS, CALL_LOG);
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_STATE, PHONE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.READ_PHONE_NUMBERS, PHONE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.CALL_PHONE, PHONE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.ADD_VOICEMAIL, PHONE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.USE_SIP, PHONE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.ANSWER_PHONE_CALLS, PHONE);
- PLATFORM_PERMISSIONS.put(Manifest.permission.ACCEPT_HANDOVER, PHONE);
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_AUDIO, MICROPHONE);
-// if (SDK_INT >= Build.VERSION_CODES.S) {
- PLATFORM_PERMISSIONS.put(Manifest.permission.RECORD_BACKGROUND_AUDIO, MICROPHONE);
-// }
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.ACTIVITY_RECOGNITION, ACTIVITY_RECOGNITION);
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.CAMERA, CAMERA);
-// if (SDK_INT >= Build.VERSION_CODES.S) {
- PLATFORM_PERMISSIONS.put(Manifest.permission.BACKGROUND_CAMERA, CAMERA);
-// }
-
- PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS, SENSORS);
-
-// if (SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- PLATFORM_PERMISSIONS.put(Manifest.permission.POST_NOTIFICATIONS, NOTIFICATIONS);
- PLATFORM_PERMISSIONS.put(Manifest.permission.BODY_SENSORS_BACKGROUND, SENSORS);
-// }
-
- PLATFORM_PERMISSION_GROUPS = new ArrayMap<>();
- int numPlatformPermissions = PLATFORM_PERMISSIONS.size();
- for (int i = 0; i < numPlatformPermissions; i++) {
- String permission = PLATFORM_PERMISSIONS.keyAt(i);
- String permissionGroup = PLATFORM_PERMISSIONS.valueAt(i);
-
- ArrayList<String> permissionsOfThisGroup = PLATFORM_PERMISSION_GROUPS.get(
- permissionGroup);
- if (permissionsOfThisGroup == null) {
- permissionsOfThisGroup = new ArrayList<>();
- PLATFORM_PERMISSION_GROUPS.put(permissionGroup, permissionsOfThisGroup);
- }
-
- permissionsOfThisGroup.add(permission);
- }
-
- ONE_TIME_PERMISSION_GROUPS = new ArraySet<>();
- ONE_TIME_PERMISSION_GROUPS.add(LOCATION);
- ONE_TIME_PERMISSION_GROUPS.add(CAMERA);
- ONE_TIME_PERMISSION_GROUPS.add(MICROPHONE);
-
- PERM_SENSOR_CODES = new ArrayMap<>();
-// if (SDK_INT >= Build.VERSION_CODES.S) {
- PERM_SENSOR_CODES.put(CAMERA, SensorPrivacyManager.Sensors.CAMERA);
- PERM_SENSOR_CODES.put(MICROPHONE, SensorPrivacyManager.Sensors.MICROPHONE);
-// }
-
- }
-
- private Utils() {
- /* do nothing - hide constructor */
- }
-
- private static ArrayMap<UserHandle, Context> sUserContexts = new ArrayMap<>();
-
- /**
- * Creates and caches a PackageContext for the requested user, or returns the previously cached
- * value. The package of the PackageContext is the application's package.
- *
- * @param app The currently running application
- * @param user The desired user for the context
- *
- * @return The generated or cached Context for the requested user
- *
- * @throws PackageManager.NameNotFoundException If the app has no package name attached
- */
- public static @NonNull Context getUserContext(Application app, UserHandle user) throws
- PackageManager.NameNotFoundException {
- if (!sUserContexts.containsKey(user)) {
- sUserContexts.put(user, app.getApplicationContext()
- .createPackageContextAsUser(app.getPackageName(), 0, user));
- }
- return sUserContexts.get(user);
- }
-
- /**
- * Returns true if a permission is dangerous, installed, and not removed
- * @param permissionInfo The permission we wish to check
- * @return If all of the conditions are met
- */
- public static boolean isPermissionDangerousInstalledNotRemoved(PermissionInfo permissionInfo) {
- return permissionInfo != null
- && permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS
- && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0
- && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0;
- }
-
- /**
- * Get permission group a platform permission belongs to, or null if the permission is not a
- * platform permission.
- *
- * @param permission the permission to resolve
- *
- * @return The group the permission belongs to
- */
- public static @Nullable String getGroupOfPlatformPermission(@NonNull String permission) {
- return PLATFORM_PERMISSIONS.get(permission);
- }
-
- /**
- * Get name of the permission group a permission belongs to.
- *
- * @param permission the {@link PermissionInfo info} of the permission to resolve
- *
- * @return The group the permission belongs to
- */
- public static @Nullable String getGroupOfPermission(@NonNull PermissionInfo permission) {
- String groupName = Utils.getGroupOfPlatformPermission(permission.name);
- if (groupName == null) {
- groupName = permission.group;
- }
-
- return groupName;
- }
-
- /**
- * Get the names for all platform permissions belonging to a group.
- *
- * @param group the group
- *
- * @return The permission names or an empty list if the
- * group is not does not have platform runtime permissions
- */
- public static @NonNull List<String> getPlatformPermissionNamesOfGroup(@NonNull String group) {
- final ArrayList<String> permissions = PLATFORM_PERMISSION_GROUPS.get(group);
- return (permissions != null) ? permissions : Collections.emptyList();
- }
-
- /**
- * Get the {@link PermissionInfo infos} for all platform permissions belonging to a group.
- *
- * @param pm Package manager to use to resolve permission infos
- * @param group the group
- *
- * @return The infos for platform permissions belonging to the group or an empty list if the
- * group is not does not have platform runtime permissions
- */
- public static @NonNull List<PermissionInfo> getPlatformPermissionsOfGroup(
- @NonNull PackageManager pm, @NonNull String group) {
- ArrayList<PermissionInfo> permInfos = new ArrayList<>();
-
- ArrayList<String> permissions = PLATFORM_PERMISSION_GROUPS.get(group);
- if (permissions == null) {
- return Collections.emptyList();
- }
-
- int numPermissions = permissions.size();
- for (int i = 0; i < numPermissions; i++) {
- String permName = permissions.get(i);
- PermissionInfo permInfo;
- try {
- permInfo = pm.getPermissionInfo(permName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- throw new IllegalStateException(permName + " not defined by platform", e);
- }
-
- permInfos.add(permInfo);
- }
-
- return permInfos;
- }
-
- /**
- * Get the {@link PermissionInfo infos} for all permission infos belonging to a group.
- *
- * @param pm Package manager to use to resolve permission infos
- * @param group the group
- *
- * @return The infos of permissions belonging to the group or an empty list if the group
- * does not have runtime permissions
- */
- public static @NonNull List<PermissionInfo> getPermissionInfosForGroup(
- @NonNull PackageManager pm, @NonNull String group)
- throws PackageManager.NameNotFoundException {
- List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0);
- permissions.addAll(getPlatformPermissionsOfGroup(pm, group));
-
- /*
- * If the undefined group is requested, the package manager will return all platform
- * permissions, since they are marked as Undefined in the manifest. Do not return these
- * permissions.
- */
- if (group.equals(Manifest.permission_group.UNDEFINED)) {
- List<PermissionInfo> undefinedPerms = new ArrayList<>();
- for (PermissionInfo permissionInfo : permissions) {
- String permGroup = getGroupOfPlatformPermission(permissionInfo.name);
- if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) {
- undefinedPerms.add(permissionInfo);
- }
- }
- return undefinedPerms;
- }
-
- return permissions;
- }
-
- /**
- * Get the {@link PermissionInfo infos} for all runtime installed permission infos belonging to
- * a group.
- *
- * @param pm Package manager to use to resolve permission infos
- * @param group the group
- *
- * @return The infos of installed runtime permissions belonging to the group or an empty list
- * if the group does not have runtime permissions
- */
- public static @NonNull List<PermissionInfo> getInstalledRuntimePermissionInfosForGroup(
- @NonNull PackageManager pm, @NonNull String group)
- throws PackageManager.NameNotFoundException {
- List<PermissionInfo> permissions = pm.queryPermissionsByGroup(group, 0);
- permissions.addAll(getPlatformPermissionsOfGroup(pm, group));
-
- List<PermissionInfo> installedRuntime = new ArrayList<>();
- for (PermissionInfo permissionInfo: permissions) {
- if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS
- && (permissionInfo.flags & PermissionInfo.FLAG_INSTALLED) != 0
- && (permissionInfo.flags & PermissionInfo.FLAG_REMOVED) == 0) {
- installedRuntime.add(permissionInfo);
- }
- }
-
- /*
- * If the undefined group is requested, the package manager will return all platform
- * permissions, since they are marked as Undefined in the manifest. Do not return these
- * permissions.
- */
- if (group.equals(Manifest.permission_group.UNDEFINED)) {
- List<PermissionInfo> undefinedPerms = new ArrayList<>();
- for (PermissionInfo permissionInfo : installedRuntime) {
- String permGroup = getGroupOfPlatformPermission(permissionInfo.name);
- if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) {
- undefinedPerms.add(permissionInfo);
- }
- }
- return undefinedPerms;
- }
-
- return installedRuntime;
- }
-
- /**
- * Get the {@link PackageItemInfo infos} for the given permission group.
- *
- * @param groupName the group
- * @param context the {@code Context} to retrieve {@code PackageManager}
- *
- * @return The info of permission group or null if the group does not have runtime permissions.
- */
- public static @Nullable PackageItemInfo getGroupInfo(@NonNull String groupName,
- @NonNull Context context) {
- try {
- return context.getPackageManager().getPermissionGroupInfo(groupName, 0);
- } catch (NameNotFoundException e) {
- /* ignore */
- }
- try {
- return context.getPackageManager().getPermissionInfo(groupName, 0);
- } catch (NameNotFoundException e) {
- /* ignore */
- }
- return null;
- }
-
- /**
- * Get the {@link PermissionInfo infos} for all permission infos belonging to a group.
- *
- * @param groupName the group
- * @param context the {@code Context} to retrieve {@code PackageManager}
- *
- * @return The infos of permissions belonging to the group or null if the group does not have
- * runtime permissions.
- */
- public static @Nullable List<PermissionInfo> getGroupPermissionInfos(@NonNull String groupName,
- @NonNull Context context) {
- try {
- return Utils.getPermissionInfosForGroup(context.getPackageManager(), groupName);
- } catch (NameNotFoundException e) {
- /* ignore */
- }
- try {
- PermissionInfo permissionInfo = context.getPackageManager()
- .getPermissionInfo(groupName, 0);
- List<PermissionInfo> permissions = new ArrayList<>();
- permissions.add(permissionInfo);
- return permissions;
- } catch (NameNotFoundException e) {
- /* ignore */
- }
- return null;
- }
-
- /**
- * Get the names of the platform permission groups.
- *
- * @return the names of the platform permission groups.
- */
- public static List<String> getPlatformPermissionGroups() {
- return new ArrayList<>(PLATFORM_PERMISSION_GROUPS.keySet());
- }
-
- /**
- * Get the names of the runtime platform permissions
- *
- * @return the names of the runtime platform permissions.
- */
- public static List<String> getRuntimePlatformPermissionNames() {
- return new ArrayList<>(PLATFORM_PERMISSIONS.keySet());
- }
-
- /**
- * Is the permissions a platform runtime permission
- *
- * @return the names of the runtime platform permissions.
- */
- public static boolean isRuntimePlatformPermission(@NonNull String permission) {
- return PLATFORM_PERMISSIONS.containsKey(permission);
- }
-
- /**
- * Is the group or background group user sensitive?
- *
- * @param group The group that might be user sensitive
- *
- * @return {@code true} if the group (or it's subgroup) is user sensitive.
- */
- public static boolean isGroupOrBgGroupUserSensitive(AppPermissionGroup group) {
- return group.isUserSensitive() || (group.getBackgroundPermissions() != null
- && group.getBackgroundPermissions().isUserSensitive());
- }
-
- /**
- * Whether or not the given package has non-isolated storage permissions
- * @param context The current context
- * @param packageName The package name to check
- * @return True if the package has access to non-isolated storage, false otherwise
- * @throws NameNotFoundException
- */
- public static boolean isNonIsolatedStorage(@NonNull Context context,
- @NonNull String packageName) throws NameNotFoundException {
- PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
- AppOpsManager manager = context.getSystemService(AppOpsManager.class);
-
-
- return packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.P
- || (packageInfo.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R
- && manager.unsafeCheckOpNoThrow(OPSTR_LEGACY_STORAGE,
- packageInfo.applicationInfo.uid, packageInfo.packageName) == MODE_ALLOWED);
- }
-
- /**
- * Build a string representing the given time if it happened on the current day and the date
- * otherwise.
- *
- * @param context the context.
- * @param lastAccessTime the time in milliseconds.
- *
- * @return a string representing the time or date of the given time or null if the time is 0.
- */
- public static @Nullable String getAbsoluteTimeString(@NonNull Context context,
- long lastAccessTime) {
- if (lastAccessTime == 0) {
- return null;
- }
- if (isToday(lastAccessTime)) {
- return DateFormat.getTimeFormat(context).format(lastAccessTime);
- } else {
- return DateFormat.getMediumDateFormat(context).format(lastAccessTime);
- }
- }
-
- /**
- * Check whether the given time (in milliseconds) is in the current day.
- *
- * @param time the time in milliseconds
- *
- * @return whether the given time is in the current day.
- */
- private static boolean isToday(long time) {
- Calendar today = Calendar.getInstance(Locale.getDefault());
- today.setTimeInMillis(System.currentTimeMillis());
- today.set(Calendar.HOUR_OF_DAY, 0);
- today.set(Calendar.MINUTE, 0);
- today.set(Calendar.SECOND, 0);
- today.set(Calendar.MILLISECOND, 0);
-
- Calendar date = Calendar.getInstance(Locale.getDefault());
- date.setTimeInMillis(time);
- return !date.before(today);
- }
-
- /**
- * Whether the Location Access Check is enabled.
- *
- * @return {@code true} iff the Location Access Check is enabled.
- */
- public static boolean isLocationAccessCheckEnabled() {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_LOCATION_ACCESS_CHECK_ENABLED, true);
- }
-
- /**
- * Get one time permissions timeout
- */
- public static long getOneTimePermissionsTimeout() {
- return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
- PROPERTY_ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS, ONE_TIME_PERMISSIONS_TIMEOUT_MILLIS);
- }
-
- /**
- * Returns the delay in milliseconds before revoking permissions at the end of a one-time
- * permission session if all processes have been killed.
- * If the session was triggered by a self-revocation, then revocation should happen
- * immediately. For a regular one-time permission session, a grace period allows a quick
- * app restart without losing the permission.
- * @param isSelfRevoked If true, return the delay for a self-revocation session. Otherwise,
- * return delay for a regular one-time permission session.
- */
- public static long getOneTimePermissionsKilledDelay(boolean isSelfRevoked) {
- if (isSelfRevoked) {
- // For a self-revoked session, we revoke immediately when the process dies.
- return 0;
- }
- return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
- PROPERTY_ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS,
- ONE_TIME_PERMISSIONS_KILLED_DELAY_MILLIS);
- }
-
- /**
- * Whether the permission group supports one-time
- * @param permissionGroup The permission group to check
- * @return {@code true} iff the group supports one-time
- */
- public static boolean supportsOneTimeGrant(String permissionGroup) {
- return ONE_TIME_PERMISSION_GROUPS.contains(permissionGroup);
- }
-
- /**
- * Checks whether a package has an active one-time permission according to the system server's
- * flags
- *
- * @param context the {@code Context} to retrieve {@code PackageManager}
- * @param packageName The package to check for
- * @return Whether a package has an active one-time permission
- */
- public static boolean hasOneTimePermissions(Context context, String packageName) {
- String[] permissions;
- PackageManager pm = context.getPackageManager();
- try {
- permissions = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
- .requestedPermissions;
- } catch (NameNotFoundException e) {
- Log.w(LOG_TAG, "Checking for one-time permissions in nonexistent package");
- return false;
- }
- if (permissions == null) {
- return false;
- }
- for (String permissionName : permissions) {
- if ((pm.getPermissionFlags(permissionName, packageName, Process.myUserHandle())
- & PackageManager.FLAG_PERMISSION_ONE_TIME) != 0
- && pm.checkPermission(permissionName, packageName)
- == PackageManager.PERMISSION_GRANTED) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Gets the label of the Settings application
- *
- * @param pm The packageManager used to get the activity resolution
- *
- * @return The CharSequence title of the settings app
- */
- @Nullable
- public static CharSequence getSettingsLabelForNotifications(PackageManager pm) {
- // We pretend we're the Settings app sending the notification, so figure out its name.
- Intent openSettingsIntent = new Intent(Settings.ACTION_SETTINGS);
- ResolveInfo resolveInfo = pm.resolveActivity(openSettingsIntent, MATCH_SYSTEM_ONLY);
- if (resolveInfo == null) {
- return null;
- }
- return pm.getApplicationLabel(resolveInfo.activityInfo.applicationInfo);
- }
-
- /**
- * Get all the exempted packages.
- */
- public static Set<String> getExemptedPackages(@NonNull RoleManager roleManager) {
- Set<String> exemptedPackages = new HashSet<>();
-
- exemptedPackages.add(SYSTEM_PKG);
- for (int i = 0; i < EXEMPTED_ROLES.length; i++) {
- exemptedPackages.addAll(roleManager.getRoleHolders(EXEMPTED_ROLES[i]));
- }
-
- return exemptedPackages;
- }
-
- /**
- * Returns if the permission group is Camera or Microphone (status bar indicators).
- **/
- public static boolean isStatusBarIndicatorPermission(@NonNull String permissionGroupName) {
- return CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName);
- }
-
- /**
- * Navigate to notification settings for all apps
- * @param context The current Context
- */
- public static void navigateToNotificationSettings(@NonNull Context context) {
- Intent notificationIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS);
- context.startActivity(notificationIntent);
- }
-
- /**
- * Navigate to notification settings for an app
- * @param context The current Context
- * @param packageName The package to navigate to
- * @param user Specifies the user of the package which should be navigated to. If null, the
- * current user is used.
- */
- public static void navigateToAppNotificationSettings(@NonNull Context context,
- @NonNull String packageName, @NonNull UserHandle user) {
- Intent notificationIntent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
- notificationIntent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
- context.startActivityAsUser(notificationIntent, user);
- }
-
- /**
- * Returns if a card should be shown if the sensor is blocked
- **/
- public static boolean shouldDisplayCardIfBlocked(@NonNull String permissionGroupName) {
- return DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_WARNING_BANNER_DISPLAY_ENABLED, true) && (
- CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName)
- || LOCATION.equals(permissionGroupName));
- }
-}
diff --git a/services/core/java/com/android/server/AnimationThread.java b/services/core/java/com/android/server/AnimationThread.java
index fad743eafdaa..826e7b52a9df 100644
--- a/services/core/java/com/android/server/AnimationThread.java
+++ b/services/core/java/com/android/server/AnimationThread.java
@@ -40,7 +40,7 @@ public final class AnimationThread extends ServiceThread {
sInstance = new AnimationThread();
sInstance.start();
sInstance.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER);
- sHandler = new Handler(sInstance.getLooper());
+ sHandler = makeSharedHandler(sInstance.getLooper());
}
}
diff --git a/services/core/java/com/android/server/PermissionThread.java b/services/core/java/com/android/server/PermissionThread.java
new file mode 100644
index 000000000000..3f747a8cf008
--- /dev/null
+++ b/services/core/java/com/android/server/PermissionThread.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.Trace;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Shared singleton thread for the system. This is a thread for handling
+ * calls to and from the PermissionController and handling synchronization
+ * between permissions and appops states.
+ */
+public final class PermissionThread extends ServiceThread {
+ private static final long SLOW_DISPATCH_THRESHOLD_MS = 100;
+ private static final long SLOW_DELIVERY_THRESHOLD_MS = 200;
+
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static PermissionThread sInstance;
+ private static Handler sHandler;
+ private static HandlerExecutor sHandlerExecutor;
+
+ private PermissionThread() {
+ super("android.perm", android.os.Process.THREAD_PRIORITY_DEFAULT, /* allowIo= */ true);
+ }
+
+ @GuardedBy("sLock")
+ private static void ensureThreadLocked() {
+ if (sInstance != null) {
+ return;
+ }
+
+ sInstance = new PermissionThread();
+ sInstance.start();
+ final Looper looper = sInstance.getLooper();
+ looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
+ looper.setSlowLogThresholdMs(
+ SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
+ sHandler = new Handler(sInstance.getLooper());
+ sHandlerExecutor = new HandlerExecutor(sHandler);
+ }
+
+ /**
+ * Obtain a singleton instance of the PermissionThread.
+ */
+ public static PermissionThread get() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sInstance;
+ }
+ }
+
+ /**
+ * Obtain a singleton instance of a handler executing in the PermissionThread.
+ */
+ public static Handler getHandler() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sHandler;
+ }
+ }
+
+
+ /**
+ * Obtain a singleton instance of an executor of the PermissionThread.
+ */
+ public static Executor getExecutor() {
+ synchronized (sLock) {
+ ensureThreadLocked();
+ return sHandlerExecutor;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 06c11fa4a20c..b2fc574dea20 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -35,14 +35,17 @@ import android.util.Slog;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.DumpUtils;
import libcore.io.IoUtils;
import java.io.DataInputStream;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
@@ -679,6 +682,20 @@ public class PersistentDataBlockService extends SystemService {
enforcePersistentDataBlockAccess();
return mContext.getString(R.string.config_persistentDataPackageName);
}
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+ pw.println("mDataBlockFile: " + mDataBlockFile);
+ pw.println("mIsRunningDSU: " + mIsRunningDSU);
+ pw.println("mInitDoneSignal: " + mInitDoneSignal);
+ pw.println("mAllowedUid: " + mAllowedUid);
+ pw.println("mBlockDeviceSize: " + mBlockDeviceSize);
+ synchronized (mLock) {
+ pw.println("mIsWritable: " + mIsWritable);
+ }
+ }
};
private PersistentDataBlockManagerInternal mInternalService =
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index ad89afb791cc..049f4bea6fa1 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -19,7 +19,7 @@ package com.android.server.adb;
import static com.android.internal.util.dump.DumpUtils.writeStringIfNotNull;
import android.annotation.NonNull;
-import android.annotation.TestApi;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -102,11 +102,26 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keysi
+ * Provides communication to the Android Debug Bridge daemon to allow, deny, or clear public keys
* that are authorized to connect to the ADB service itself.
+ *
+ * <p>The AdbDebuggingManager controls two files:
+ * <ol>
+ * <li>adb_keys
+ * <li>adb_temp_keys.xml
+ * </ol>
+ *
+ * <p>The ADB Daemon (adbd) reads <em>only</em> the adb_keys file for authorization. Public keys
+ * from registered hosts are stored in adb_keys, one entry per line.
+ *
+ * <p>AdbDebuggingManager also keeps adb_temp_keys.xml, which is used for two things
+ * <ol>
+ * <li>Removing unused keys from the adb_keys file
+ * <li>Managing authorized WiFi access points for ADB over WiFi
+ * </ol>
*/
public class AdbDebuggingManager {
- private static final String TAG = "AdbDebuggingManager";
+ private static final String TAG = AdbDebuggingManager.class.getSimpleName();
private static final boolean DEBUG = false;
private static final boolean MDNS_DEBUG = false;
@@ -118,18 +133,20 @@ public class AdbDebuggingManager {
// as a subsequent connection occurs within the allowed duration.
private static final String ADB_TEMP_KEYS_FILE = "adb_temp_keys.xml";
private static final int BUFFER_SIZE = 65536;
+ private static final Ticker SYSTEM_TICKER = () -> System.currentTimeMillis();
private final Context mContext;
private final ContentResolver mContentResolver;
- private final Handler mHandler;
- private AdbDebuggingThread mThread;
+ @VisibleForTesting final AdbDebuggingHandler mHandler;
+ @Nullable private AdbDebuggingThread mThread;
private boolean mAdbUsbEnabled = false;
private boolean mAdbWifiEnabled = false;
private String mFingerprints;
// A key can be used more than once (e.g. USB, wifi), so need to keep a refcount
- private final Map<String, Integer> mConnectedKeys;
- private String mConfirmComponent;
- private final File mTestUserKeyFile;
+ private final Map<String, Integer> mConnectedKeys = new HashMap<>();
+ private final String mConfirmComponent;
+ @Nullable private final File mUserKeyFile;
+ @Nullable private final File mTempKeysFile;
private static final String WIFI_PERSISTENT_CONFIG_PROPERTY =
"persist.adb.tls_server.enable";
@@ -138,37 +155,44 @@ public class AdbDebuggingManager {
private static final int PAIRING_CODE_LENGTH = 6;
private PairingThread mPairingThread = null;
// A list of keys connected via wifi
- private final Set<String> mWifiConnectedKeys;
+ private final Set<String> mWifiConnectedKeys = new HashSet<>();
// The current info of the adbwifi connection.
- private AdbConnectionInfo mAdbConnectionInfo;
+ private AdbConnectionInfo mAdbConnectionInfo = new AdbConnectionInfo();
// Polls for a tls port property when adb wifi is enabled
private AdbConnectionPortPoller mConnectionPortPoller;
private final PortListenerImpl mPortListener = new PortListenerImpl();
+ private final Ticker mTicker;
public AdbDebuggingManager(Context context) {
- mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
- mContext = context;
- mContentResolver = mContext.getContentResolver();
- mTestUserKeyFile = null;
- mConnectedKeys = new HashMap<String, Integer>();
- mWifiConnectedKeys = new HashSet<String>();
- mAdbConnectionInfo = new AdbConnectionInfo();
+ this(
+ context,
+ /* confirmComponent= */ null,
+ getAdbFile(ADB_KEYS_FILE),
+ getAdbFile(ADB_TEMP_KEYS_FILE),
+ /* adbDebuggingThread= */ null,
+ SYSTEM_TICKER);
}
/**
* Constructor that accepts the component to be invoked to confirm if the user wants to allow
* an adb connection from the key.
*/
- @TestApi
- protected AdbDebuggingManager(Context context, String confirmComponent, File testUserKeyFile) {
- mHandler = new AdbDebuggingHandler(FgThread.get().getLooper());
+ @VisibleForTesting
+ AdbDebuggingManager(
+ Context context,
+ String confirmComponent,
+ File testUserKeyFile,
+ File tempKeysFile,
+ AdbDebuggingThread adbDebuggingThread,
+ Ticker ticker) {
mContext = context;
mContentResolver = mContext.getContentResolver();
mConfirmComponent = confirmComponent;
- mTestUserKeyFile = testUserKeyFile;
- mConnectedKeys = new HashMap<String, Integer>();
- mWifiConnectedKeys = new HashSet<String>();
- mAdbConnectionInfo = new AdbConnectionInfo();
+ mUserKeyFile = testUserKeyFile;
+ mTempKeysFile = tempKeysFile;
+ mThread = adbDebuggingThread;
+ mTicker = ticker;
+ mHandler = new AdbDebuggingHandler(FgThread.get().getLooper(), mThread);
}
static void sendBroadcastWithDebugPermission(@NonNull Context context, @NonNull Intent intent,
@@ -189,8 +213,7 @@ public class AdbDebuggingManager {
// consisting of only letters, digits, and hyphens, must begin and end
// with a letter or digit, must not contain consecutive hyphens, and
// must contain at least one letter.
- @VisibleForTesting
- static final String SERVICE_PROTOCOL = "adb-tls-pairing";
+ @VisibleForTesting static final String SERVICE_PROTOCOL = "adb-tls-pairing";
private final String mServiceType = String.format("_%s._tcp.", SERVICE_PROTOCOL);
private int mPort;
@@ -352,16 +375,24 @@ public class AdbDebuggingManager {
}
}
- class AdbDebuggingThread extends Thread {
+ @VisibleForTesting
+ static class AdbDebuggingThread extends Thread {
private boolean mStopped;
private LocalSocket mSocket;
private OutputStream mOutputStream;
private InputStream mInputStream;
+ private Handler mHandler;
+ @VisibleForTesting
AdbDebuggingThread() {
super(TAG);
}
+ @VisibleForTesting
+ void setHandler(Handler handler) {
+ mHandler = handler;
+ }
+
@Override
public void run() {
if (DEBUG) Slog.d(TAG, "Entering thread");
@@ -536,7 +567,7 @@ public class AdbDebuggingManager {
}
}
- class AdbConnectionInfo {
+ private static class AdbConnectionInfo {
private String mBssid;
private String mSsid;
private int mPort;
@@ -743,11 +774,14 @@ public class AdbDebuggingManager {
// Notification when adbd socket is disconnected.
static final int MSG_ADBD_SOCKET_DISCONNECTED = 27;
+ // === Messages from other parts of the system
+ private static final int MESSAGE_KEY_FILES_UPDATED = 28;
+
// === Messages we can send to adbd ===========
static final String MSG_DISCONNECT_DEVICE = "DD";
static final String MSG_DISABLE_ADBDWIFI = "DA";
- private AdbKeyStore mAdbKeyStore;
+ @Nullable @VisibleForTesting AdbKeyStore mAdbKeyStore;
// Usb, Wi-Fi transports can be enabled together or separately, so don't break the framework
// connection unless all transport types are disconnected.
@@ -762,19 +796,19 @@ public class AdbDebuggingManager {
}
};
- AdbDebuggingHandler(Looper looper) {
+ /** Constructor that accepts the AdbDebuggingThread to which responses should be sent. */
+ @VisibleForTesting
+ AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread) {
super(looper);
+ mThread = thread;
}
- /**
- * Constructor that accepts the AdbDebuggingThread to which responses should be sent
- * and the AdbKeyStore to be used to store the temporary grants.
- */
- @TestApi
- AdbDebuggingHandler(Looper looper, AdbDebuggingThread thread, AdbKeyStore adbKeyStore) {
- super(looper);
- mThread = thread;
- mAdbKeyStore = adbKeyStore;
+ /** Initialize the AdbKeyStore so tests can grab mAdbKeyStore immediately. */
+ @VisibleForTesting
+ void initKeyStore() {
+ if (mAdbKeyStore == null) {
+ mAdbKeyStore = new AdbKeyStore();
+ }
}
// Show when at least one device is connected.
@@ -805,6 +839,7 @@ public class AdbDebuggingManager {
registerForAuthTimeChanges();
mThread = new AdbDebuggingThread();
+ mThread.setHandler(mHandler);
mThread.start();
mAdbKeyStore.updateKeyStore();
@@ -825,8 +860,7 @@ public class AdbDebuggingManager {
if (!mConnectedKeys.isEmpty()) {
for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) {
- mAdbKeyStore.setLastConnectionTime(entry.getKey(),
- System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(entry.getKey(), mTicker.currentTimeMillis());
}
sendPersistKeyStoreMessage();
mConnectedKeys.clear();
@@ -836,9 +870,7 @@ public class AdbDebuggingManager {
}
public void handleMessage(Message msg) {
- if (mAdbKeyStore == null) {
- mAdbKeyStore = new AdbKeyStore();
- }
+ initKeyStore();
switch (msg.what) {
case MESSAGE_ADB_ENABLED:
@@ -873,7 +905,7 @@ public class AdbDebuggingManager {
if (!mConnectedKeys.containsKey(key)) {
mConnectedKeys.put(key, 1);
}
- mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(key, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
}
@@ -911,9 +943,7 @@ public class AdbDebuggingManager {
mConnectedKeys.clear();
// If the key store has not yet been instantiated then do so now; this avoids
// the unnecessary creation of the key store when adb is not enabled.
- if (mAdbKeyStore == null) {
- mAdbKeyStore = new AdbKeyStore();
- }
+ initKeyStore();
mWifiConnectedKeys.clear();
mAdbKeyStore.deleteKeyStore();
cancelJobToUpdateAdbKeyStore();
@@ -928,7 +958,8 @@ public class AdbDebuggingManager {
alwaysAllow = true;
int refcount = mConnectedKeys.get(key) - 1;
if (refcount == 0) {
- mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(
+ key, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
mConnectedKeys.remove(key);
@@ -954,7 +985,7 @@ public class AdbDebuggingManager {
if (!mConnectedKeys.isEmpty()) {
for (Map.Entry<String, Integer> entry : mConnectedKeys.entrySet()) {
mAdbKeyStore.setLastConnectionTime(entry.getKey(),
- System.currentTimeMillis());
+ mTicker.currentTimeMillis());
}
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
@@ -975,7 +1006,7 @@ public class AdbDebuggingManager {
} else {
mConnectedKeys.put(key, mConnectedKeys.get(key) + 1);
}
- mAdbKeyStore.setLastConnectionTime(key, System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(key, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
logAdbConnectionChanged(key, AdbProtoEnums.AUTOMATICALLY_ALLOWED, true);
@@ -1197,6 +1228,10 @@ public class AdbDebuggingManager {
}
break;
}
+ case MESSAGE_KEY_FILES_UPDATED: {
+ mAdbKeyStore.reloadKeyMap();
+ break;
+ }
}
}
@@ -1368,8 +1403,7 @@ public class AdbDebuggingManager {
AdbDebuggingManager.sendBroadcastWithDebugPermission(mContext, intent,
UserHandle.ALL);
// Add the key into the keystore
- mAdbKeyStore.setLastConnectionTime(publicKey,
- System.currentTimeMillis());
+ mAdbKeyStore.setLastConnectionTime(publicKey, mTicker.currentTimeMillis());
sendPersistKeyStoreMessage();
scheduleJobToUpdateAdbKeyStore();
}
@@ -1440,19 +1474,13 @@ public class AdbDebuggingManager {
extras.add(new AbstractMap.SimpleEntry<String, String>("ssid", ssid));
extras.add(new AbstractMap.SimpleEntry<String, String>("bssid", bssid));
int currentUserId = ActivityManager.getCurrentUser();
- UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
- String componentString;
- if (userInfo.isAdmin()) {
- componentString = Resources.getSystem().getString(
- com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent);
- } else {
- componentString = Resources.getSystem().getString(
- com.android.internal.R.string.config_customAdbWifiNetworkConfirmationComponent);
- }
+ String componentString =
+ Resources.getSystem().getString(
+ R.string.config_customAdbWifiNetworkConfirmationComponent);
ComponentName componentName = ComponentName.unflattenFromString(componentString);
+ UserInfo userInfo = UserManager.get(mContext).getUserInfo(currentUserId);
if (startConfirmationActivity(componentName, userInfo.getUserHandle(), extras)
- || startConfirmationService(componentName, userInfo.getUserHandle(),
- extras)) {
+ || startConfirmationService(componentName, userInfo.getUserHandle(), extras)) {
return;
}
Slog.e(TAG, "Unable to start customAdbWifiNetworkConfirmation[SecondaryUser]Component "
@@ -1534,7 +1562,7 @@ public class AdbDebuggingManager {
/**
* Returns a new File with the specified name in the adb directory.
*/
- private File getAdbFile(String fileName) {
+ private static File getAdbFile(String fileName) {
File dataDir = Environment.getDataDirectory();
File adbDir = new File(dataDir, ADB_DIRECTORY);
@@ -1547,66 +1575,38 @@ public class AdbDebuggingManager {
}
File getAdbTempKeysFile() {
- return getAdbFile(ADB_TEMP_KEYS_FILE);
+ return mTempKeysFile;
}
File getUserKeyFile() {
- return mTestUserKeyFile == null ? getAdbFile(ADB_KEYS_FILE) : mTestUserKeyFile;
+ return mUserKeyFile;
}
- private void writeKey(String key) {
- try {
- File keyFile = getUserKeyFile();
-
- if (keyFile == null) {
- return;
- }
-
- FileOutputStream fo = new FileOutputStream(keyFile, true);
- fo.write(key.getBytes());
- fo.write('\n');
- fo.close();
-
- FileUtils.setPermissions(keyFile.toString(),
- FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
- } catch (IOException ex) {
- Slog.e(TAG, "Error writing key:" + ex);
+ private void writeKeys(Iterable<String> keys) {
+ if (mUserKeyFile == null) {
+ return;
}
- }
- private void writeKeys(Iterable<String> keys) {
- AtomicFile atomicKeyFile = null;
+ AtomicFile atomicKeyFile = new AtomicFile(mUserKeyFile);
+ // Note: Do not use a try-with-resources with the FileOutputStream, because AtomicFile
+ // requires that it's cleaned up with AtomicFile.failWrite();
FileOutputStream fo = null;
try {
- File keyFile = getUserKeyFile();
-
- if (keyFile == null) {
- return;
- }
-
- atomicKeyFile = new AtomicFile(keyFile);
fo = atomicKeyFile.startWrite();
for (String key : keys) {
fo.write(key.getBytes());
fo.write('\n');
}
atomicKeyFile.finishWrite(fo);
-
- FileUtils.setPermissions(keyFile.toString(),
- FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
} catch (IOException ex) {
Slog.e(TAG, "Error writing keys: " + ex);
- if (atomicKeyFile != null) {
- atomicKeyFile.failWrite(fo);
- }
+ atomicKeyFile.failWrite(fo);
+ return;
}
- }
- private void deleteKeyFile() {
- File keyFile = getUserKeyFile();
- if (keyFile != null) {
- keyFile.delete();
- }
+ FileUtils.setPermissions(
+ mUserKeyFile.toString(),
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP, -1, -1);
}
/**
@@ -1736,6 +1736,13 @@ public class AdbDebuggingManager {
}
/**
+ * Notify that they key files were updated so the AdbKeyManager reloads the keys.
+ */
+ public void notifyKeyFilesUpdated() {
+ mHandler.sendEmptyMessage(AdbDebuggingHandler.MESSAGE_KEY_FILES_UPDATED);
+ }
+
+ /**
* Sends a message to the handler to persist the keystore.
*/
private void sendPersistKeyStoreMessage() {
@@ -1769,7 +1776,7 @@ public class AdbDebuggingManager {
try {
dump.write("keystore", AdbDebuggingManagerProto.KEYSTORE,
- FileUtils.readTextFile(getAdbTempKeysFile(), 0, null));
+ FileUtils.readTextFile(mTempKeysFile, 0, null));
} catch (IOException e) {
Slog.i(TAG, "Cannot read keystore: ", e);
}
@@ -1783,12 +1790,12 @@ public class AdbDebuggingManager {
* ADB_ALLOWED_CONNECTION_TIME setting.
*/
class AdbKeyStore {
- private Map<String, Long> mKeyMap;
- private Set<String> mSystemKeys;
- private File mKeyFile;
private AtomicFile mAtomicKeyFile;
- private List<String> mTrustedNetworks;
+ private final Set<String> mSystemKeys;
+ private final Map<String, Long> mKeyMap = new HashMap<>();
+ private final List<String> mTrustedNetworks = new ArrayList<>();
+
private static final int KEYSTORE_VERSION = 1;
private static final int MAX_SUPPORTED_KEYSTORE_VERSION = 1;
private static final String XML_KEYSTORE_START_TAG = "keyStore";
@@ -1810,26 +1817,22 @@ public class AdbDebuggingManager {
public static final long NO_PREVIOUS_CONNECTION = 0;
/**
- * Constructor that uses the default location for the persistent adb keystore.
+ * Create an AdbKeyStore instance.
+ *
+ * <p>Upon creation, we parse {@link #mTempKeysFile} to determine authorized WiFi APs and
+ * retrieve the map of stored ADB keys and their last connected times. After that, we read
+ * the {@link #mUserKeyFile}, and any keys that exist in that file that do not exist in the
+ * map are added to the map (for backwards compatibility).
*/
AdbKeyStore() {
- init();
- }
-
- /**
- * Constructor that uses the specified file as the location for the persistent adb keystore.
- */
- AdbKeyStore(File keyFile) {
- mKeyFile = keyFile;
- init();
- }
-
- private void init() {
initKeyFile();
- mKeyMap = getKeyMap();
- mTrustedNetworks = getTrustedNetworks();
+ readTempKeysFile();
mSystemKeys = getSystemKeysFromFile(SYSTEM_KEY_FILE);
- addUserKeysToKeyStore();
+ addExistingUserKeysToKeyStore();
+ }
+
+ public void reloadKeyMap() {
+ readTempKeysFile();
}
public void addTrustedNetwork(String bssid) {
@@ -1868,7 +1871,6 @@ public class AdbDebuggingManager {
public void removeKey(String key) {
if (mKeyMap.containsKey(key)) {
mKeyMap.remove(key);
- writeKeys(mKeyMap.keySet());
sendPersistKeyStoreMessage();
}
}
@@ -1877,12 +1879,9 @@ public class AdbDebuggingManager {
* Initializes the key file that will be used to persist the adb grants.
*/
private void initKeyFile() {
- if (mKeyFile == null) {
- mKeyFile = getAdbTempKeysFile();
- }
- // getAdbTempKeysFile can return null if the adb file cannot be obtained
- if (mKeyFile != null) {
- mAtomicKeyFile = new AtomicFile(mKeyFile);
+ // mTempKeysFile can be null if the adb file cannot be obtained
+ if (mTempKeysFile != null) {
+ mAtomicKeyFile = new AtomicFile(mTempKeysFile);
}
}
@@ -1923,201 +1922,108 @@ public class AdbDebuggingManager {
}
/**
- * Returns the key map with the keys and last connection times from the key file.
+ * Update the key map and the trusted networks list with values parsed from the temp keys
+ * file.
*/
- private Map<String, Long> getKeyMap() {
- Map<String, Long> keyMap = new HashMap<String, Long>();
- // if the AtomicFile could not be instantiated before attempt again; if it still fails
- // return an empty key map.
+ private void readTempKeysFile() {
+ mKeyMap.clear();
+ mTrustedNetworks.clear();
if (mAtomicKeyFile == null) {
initKeyFile();
if (mAtomicKeyFile == null) {
- Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
- return keyMap;
+ Slog.e(
+ TAG,
+ "Unable to obtain the key file, " + mTempKeysFile + ", for reading");
+ return;
}
}
if (!mAtomicKeyFile.exists()) {
- return keyMap;
+ return;
}
try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
- TypedXmlPullParser parser = Xml.resolvePullParser(keyStream);
- // Check for supported keystore version.
- XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
- if (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) {
- Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag="
- + tagName);
- return keyMap;
- }
+ TypedXmlPullParser parser;
+ try {
+ parser = Xml.resolvePullParser(keyStream);
+ XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
+
int keystoreVersion = parser.getAttributeInt(null, XML_ATTRIBUTE_VERSION);
if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) {
Slog.e(TAG, "Keystore version=" + keystoreVersion
+ " not supported (max_supported="
+ MAX_SUPPORTED_KEYSTORE_VERSION + ")");
- return keyMap;
- }
- }
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null) {
- break;
- } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
- long connectionTime;
- try {
- connectionTime = parser.getAttributeLong(null,
- XML_ATTRIBUTE_LAST_CONNECTION);
- } catch (XmlPullParserException e) {
- Slog.e(TAG,
- "Caught a NumberFormatException parsing the last connection time: "
- + e);
- XmlUtils.skipCurrentTag(parser);
- continue;
+ return;
}
- keyMap.put(key, connectionTime);
+ } catch (XmlPullParserException e) {
+ // This could be because the XML document doesn't start with
+ // XML_KEYSTORE_START_TAG. Try again, instead just starting the document with
+ // the adbKey tag (the old format).
+ parser = Xml.resolvePullParser(keyStream);
}
+ readKeyStoreContents(parser);
} catch (IOException e) {
Slog.e(TAG, "Caught an IOException parsing the XML key file: ", e);
} catch (XmlPullParserException e) {
- Slog.w(TAG, "Caught XmlPullParserException parsing the XML key file: ", e);
- // The file could be written in a format prior to introducing keystore tag.
- return getKeyMapBeforeKeystoreVersion();
+ Slog.e(TAG, "Caught XmlPullParserException parsing the XML key file: ", e);
+ }
+ }
+
+ private void readKeyStoreContents(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ // This parser is very forgiving. For backwards-compatibility, we simply iterate through
+ // all the tags in the file, skipping over anything that's not an <adbKey> tag or a
+ // <wifiAP> tag. Invalid tags (such as ones that don't have a valid "lastConnection"
+ // attribute) are simply ignored.
+ while ((parser.next()) != XmlPullParser.END_DOCUMENT) {
+ String tagName = parser.getName();
+ if (XML_TAG_ADB_KEY.equals(tagName)) {
+ addAdbKeyToKeyMap(parser);
+ } else if (XML_TAG_WIFI_ACCESS_POINT.equals(tagName)) {
+ addTrustedNetworkToTrustedNetworks(parser);
+ } else {
+ Slog.w(TAG, "Ignoring tag '" + tagName + "'. Not recognized.");
+ }
+ XmlUtils.skipCurrentTag(parser);
}
- return keyMap;
}
-
- /**
- * Returns the key map with the keys and last connection times from the key file.
- * This implementation was prior to adding the XML_KEYSTORE_START_TAG.
- */
- private Map<String, Long> getKeyMapBeforeKeystoreVersion() {
- Map<String, Long> keyMap = new HashMap<String, Long>();
- // if the AtomicFile could not be instantiated before attempt again; if it still fails
- // return an empty key map.
- if (mAtomicKeyFile == null) {
- initKeyFile();
- if (mAtomicKeyFile == null) {
- Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
- return keyMap;
- }
- }
- if (!mAtomicKeyFile.exists()) {
- return keyMap;
- }
- try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
- TypedXmlPullParser parser = Xml.resolvePullParser(keyStream);
- XmlUtils.beginDocument(parser, XML_TAG_ADB_KEY);
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null) {
- break;
- } else if (!tagName.equals(XML_TAG_ADB_KEY)) {
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
- long connectionTime;
- try {
- connectionTime = parser.getAttributeLong(null,
- XML_ATTRIBUTE_LAST_CONNECTION);
- } catch (XmlPullParserException e) {
- Slog.e(TAG,
- "Caught a NumberFormatException parsing the last connection time: "
- + e);
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- keyMap.put(key, connectionTime);
- }
- } catch (IOException | XmlPullParserException e) {
- Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
+ private void addAdbKeyToKeyMap(TypedXmlPullParser parser) {
+ String key = parser.getAttributeValue(null, XML_ATTRIBUTE_KEY);
+ try {
+ long connectionTime =
+ parser.getAttributeLong(null, XML_ATTRIBUTE_LAST_CONNECTION);
+ mKeyMap.put(key, connectionTime);
+ } catch (XmlPullParserException e) {
+ Slog.e(TAG, "Error reading adbKey attributes", e);
}
- return keyMap;
}
- /**
- * Returns the map of trusted networks from the keystore file.
- *
- * This was implemented in keystore version 1.
- */
- private List<String> getTrustedNetworks() {
- List<String> trustedNetworks = new ArrayList<String>();
- // if the AtomicFile could not be instantiated before attempt again; if it still fails
- // return an empty key map.
- if (mAtomicKeyFile == null) {
- initKeyFile();
- if (mAtomicKeyFile == null) {
- Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for reading");
- return trustedNetworks;
- }
- }
- if (!mAtomicKeyFile.exists()) {
- return trustedNetworks;
- }
- try (FileInputStream keyStream = mAtomicKeyFile.openRead()) {
- TypedXmlPullParser parser = Xml.resolvePullParser(keyStream);
- // Check for supported keystore version.
- XmlUtils.beginDocument(parser, XML_KEYSTORE_START_TAG);
- if (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null || !XML_KEYSTORE_START_TAG.equals(tagName)) {
- Slog.e(TAG, "Expected " + XML_KEYSTORE_START_TAG + ", but got tag="
- + tagName);
- return trustedNetworks;
- }
- int keystoreVersion = parser.getAttributeInt(null, XML_ATTRIBUTE_VERSION);
- if (keystoreVersion > MAX_SUPPORTED_KEYSTORE_VERSION) {
- Slog.e(TAG, "Keystore version=" + keystoreVersion
- + " not supported (max_supported="
- + MAX_SUPPORTED_KEYSTORE_VERSION);
- return trustedNetworks;
- }
- }
- while (parser.next() != XmlPullParser.END_DOCUMENT) {
- String tagName = parser.getName();
- if (tagName == null) {
- break;
- } else if (!tagName.equals(XML_TAG_WIFI_ACCESS_POINT)) {
- XmlUtils.skipCurrentTag(parser);
- continue;
- }
- String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID);
- trustedNetworks.add(bssid);
- }
- } catch (IOException | XmlPullParserException | NumberFormatException e) {
- Slog.e(TAG, "Caught an exception parsing the XML key file: ", e);
- }
- return trustedNetworks;
+ private void addTrustedNetworkToTrustedNetworks(TypedXmlPullParser parser) {
+ String bssid = parser.getAttributeValue(null, XML_ATTRIBUTE_WIFI_BSSID);
+ mTrustedNetworks.add(bssid);
}
/**
* Updates the keystore with keys that were previously set to be always allowed before the
* connection time of keys was tracked.
*/
- private void addUserKeysToKeyStore() {
- File userKeyFile = getUserKeyFile();
+ private void addExistingUserKeysToKeyStore() {
+ if (mUserKeyFile == null || !mUserKeyFile.exists()) {
+ return;
+ }
boolean mapUpdated = false;
- if (userKeyFile != null && userKeyFile.exists()) {
- try (BufferedReader in = new BufferedReader(new FileReader(userKeyFile))) {
- long time = System.currentTimeMillis();
- String key;
- while ((key = in.readLine()) != null) {
- // if the keystore does not contain the key from the user key file then add
- // it to the Map with the current system time to prevent it from expiring
- // immediately if the user is actively using this key.
- if (!mKeyMap.containsKey(key)) {
- mKeyMap.put(key, time);
- mapUpdated = true;
- }
+ try (BufferedReader in = new BufferedReader(new FileReader(mUserKeyFile))) {
+ String key;
+ while ((key = in.readLine()) != null) {
+ // if the keystore does not contain the key from the user key file then add
+ // it to the Map with the current system time to prevent it from expiring
+ // immediately if the user is actively using this key.
+ if (!mKeyMap.containsKey(key)) {
+ mKeyMap.put(key, mTicker.currentTimeMillis());
+ mapUpdated = true;
}
- } catch (IOException e) {
- Slog.e(TAG, "Caught an exception reading " + userKeyFile + ": " + e);
}
+ } catch (IOException e) {
+ Slog.e(TAG, "Caught an exception reading " + mUserKeyFile + ": " + e);
}
if (mapUpdated) {
sendPersistKeyStoreMessage();
@@ -2138,7 +2044,9 @@ public class AdbDebuggingManager {
if (mAtomicKeyFile == null) {
initKeyFile();
if (mAtomicKeyFile == null) {
- Slog.e(TAG, "Unable to obtain the key file, " + mKeyFile + ", for writing");
+ Slog.e(
+ TAG,
+ "Unable to obtain the key file, " + mTempKeysFile + ", for writing");
return;
}
}
@@ -2169,17 +2077,21 @@ public class AdbDebuggingManager {
Slog.e(TAG, "Caught an exception writing the key map: ", e);
mAtomicKeyFile.failWrite(keyStream);
}
+ writeKeys(mKeyMap.keySet());
}
private boolean filterOutOldKeys() {
- boolean keysDeleted = false;
long allowedTime = getAllowedConnectionTime();
- long systemTime = System.currentTimeMillis();
+ if (allowedTime == 0) {
+ return false;
+ }
+ boolean keysDeleted = false;
+ long systemTime = mTicker.currentTimeMillis();
Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator();
while (keyMapIterator.hasNext()) {
Map.Entry<String, Long> keyEntry = keyMapIterator.next();
long connectionTime = keyEntry.getValue();
- if (allowedTime != 0 && systemTime > (connectionTime + allowedTime)) {
+ if (systemTime > (connectionTime + allowedTime)) {
keyMapIterator.remove();
keysDeleted = true;
}
@@ -2203,7 +2115,7 @@ public class AdbDebuggingManager {
if (allowedTime == 0) {
return minExpiration;
}
- long systemTime = System.currentTimeMillis();
+ long systemTime = mTicker.currentTimeMillis();
Iterator<Map.Entry<String, Long>> keyMapIterator = mKeyMap.entrySet().iterator();
while (keyMapIterator.hasNext()) {
Map.Entry<String, Long> keyEntry = keyMapIterator.next();
@@ -2224,7 +2136,9 @@ public class AdbDebuggingManager {
public void deleteKeyStore() {
mKeyMap.clear();
mTrustedNetworks.clear();
- deleteKeyFile();
+ if (mUserKeyFile != null) {
+ mUserKeyFile.delete();
+ }
if (mAtomicKeyFile == null) {
return;
}
@@ -2251,7 +2165,8 @@ public class AdbDebuggingManager {
* is set to true the time will be set even if it is older than the previously written
* connection time.
*/
- public void setLastConnectionTime(String key, long connectionTime, boolean force) {
+ @VisibleForTesting
+ void setLastConnectionTime(String key, long connectionTime, boolean force) {
// Do not set the connection time to a value that is earlier than what was previously
// stored as the last connection time unless force is set.
if (mKeyMap.containsKey(key) && mKeyMap.get(key) >= connectionTime && !force) {
@@ -2262,11 +2177,6 @@ public class AdbDebuggingManager {
if (mSystemKeys.contains(key)) {
return;
}
- // if this is the first time the key is being added then write it to the key file as
- // well.
- if (!mKeyMap.containsKey(key)) {
- writeKey(key);
- }
mKeyMap.put(key, connectionTime);
}
@@ -2298,12 +2208,8 @@ public class AdbDebuggingManager {
long allowedConnectionTime = getAllowedConnectionTime();
// if the allowed connection time is 0 then revert to the previous behavior of always
// allowing previously granted adb grants.
- if (allowedConnectionTime == 0 || (System.currentTimeMillis() < (lastConnectionTime
- + allowedConnectionTime))) {
- return true;
- } else {
- return false;
- }
+ return allowedConnectionTime == 0
+ || (mTicker.currentTimeMillis() < (lastConnectionTime + allowedConnectionTime));
}
/**
@@ -2315,4 +2221,15 @@ public class AdbDebuggingManager {
return mTrustedNetworks.contains(bssid);
}
}
+
+ /**
+ * A Guava-like interface for getting the current system time.
+ *
+ * This allows us to swap a fake ticker in for testing to reduce "Thread.sleep()" calls and test
+ * for exact expected times instead of random ones.
+ */
+ @VisibleForTesting
+ interface Ticker {
+ long currentTimeMillis();
+ }
}
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index 5d0c732d5f48..55d8dba69626 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -152,6 +152,14 @@ public class AdbService extends IAdbManager.Stub {
}
@Override
+ public void notifyKeyFilesUpdated() {
+ if (mDebuggingManager == null) {
+ return;
+ }
+ mDebuggingManager.notifyKeyFilesUpdated();
+ }
+
+ @Override
public void startAdbdForTransport(byte transportType) {
FgThread.getHandler().sendMessage(obtainMessage(
AdbService::setAdbdEnabledForTransport, AdbService.this, true, transportType));
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 36cd3dba9d5d..d63fd53a383d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8790,8 +8790,12 @@ public class ActivityManagerService extends IActivityManager.Stub
// otherwise the watchdog may be prevented from resetting the system.
// Bail early if not published yet
- if (ServiceManager.getService(Context.DROPBOX_SERVICE) == null) return;
- final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
+ final DropBoxManager dbox;
+ try {
+ dbox = mContext.getSystemService(DropBoxManager.class);
+ } catch (Exception e) {
+ return;
+ }
// Exit early if the dropbox isn't configured to accept this report type.
final String dropboxTag = processClass(process) + "_" + eventType;
@@ -15283,6 +15287,10 @@ public class ActivityManagerService extends IActivityManager.Stub
app.processName, app.toShortString(), cpuLimit, app)) {
mHandler.post(() -> {
synchronized (ActivityManagerService.this) {
+ if (app.getThread() == null
+ || app.mState.getSetProcState() < ActivityManager.PROCESS_STATE_HOME) {
+ return;
+ }
app.killLocked("excessive cpu " + cpuTimeUsed + " during "
+ uptimeSince + " dur=" + checkDur + " limit=" + cpuLimit,
ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
@@ -15308,6 +15316,10 @@ public class ActivityManagerService extends IActivityManager.Stub
app.processName, r.toString(), cpuLimit, app)) {
mHandler.post(() -> {
synchronized (ActivityManagerService.this) {
+ if (app.getThread() == null
+ || app.mState.getSetProcState() < ActivityManager.PROCESS_STATE_HOME) {
+ return;
+ }
mPhantomProcessList.killPhantomProcessGroupLocked(app, r,
ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE,
ApplicationExitInfo.SUBREASON_EXCESSIVE_CPU,
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index c4efbd7e8f51..15887f0df406 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -1,4 +1,3 @@
-
# Applications & Processes
yamasani@google.com
jsharkey@google.com
@@ -18,8 +17,6 @@ jji@google.com
ogunwale@google.com
# Permissions & Packages
-svetoslavganov@google.com
-toddke@google.com
patb@google.com
# Battery Stats
@@ -35,8 +32,8 @@ narayan@google.com
per-file *Assist* = file:/core/java/android/service/voice/OWNERS
per-file *Voice* = file:/core/java/android/service/voice/OWNERS
-per-file SettingsToPropertiesMapper.java = omakoto@google.com, svetoslavganov@google.com, yamasani@google.com
+per-file SettingsToPropertiesMapper.java = omakoto@google.com, yamasani@google.com
-per-file CarUserSwitchingDialog.java = keunyoung@google.com, felipeal@google.com, gurunagarajan@google.com
+per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNERS
per-file ContentProviderHelper.java = varunshah@google.com, omakoto@google.com, jsharkey@google.com, yamasani@google.com
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 0d9463958276..178b6bbe755e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2794,15 +2794,6 @@ public final class ProcessList {
}
int N = procs.size();
- for (int i = 0; i < N; ++i) {
- final ProcessRecord proc = procs.get(i).first;
- try {
- Process.setProcessFrozen(proc.getPid(), proc.uid, true);
- } catch (Exception e) {
- Slog.w(TAG, "Unable to freeze " + proc.getPid() + " " + proc.processName);
- }
- }
-
for (int i=0; i<N; i++) {
final Pair<ProcessRecord, Boolean> proc = procs.get(i);
removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index e4f624d4ac28..f8558e80588c 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -58,8 +58,7 @@
"name": "FrameworksServicesTests",
"options": [
{ "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
- { "include-filter": "com.android.server.am.MeasuredEnergySnapshotTest" },
- { "include-filter": "com.android.server.am.BatteryExternalStatsWorkerTest" }
+ { "include-filter": "com.android.server.power.stats.BatteryStatsTests" }
]
}
],
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index a545ed682f0f..845e932cdff9 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -2712,8 +2712,8 @@ public class Vpn {
*/
@NonNull private final ScheduledThreadPoolExecutor mExecutor;
- @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostTimeout;
- @Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionTimeout;
+ @Nullable private ScheduledFuture<?> mScheduledHandleNetworkLostFuture;
+ @Nullable private ScheduledFuture<?> mScheduledHandleRetryIkeSessionFuture;
/** Signal to ensure shutdown is honored even if a new Network is connected. */
private boolean mIsRunning = true;
@@ -2955,6 +2955,8 @@ public class Vpn {
}
try {
+ mTunnelIface.setUnderlyingNetwork(mIkeConnectionInfo.getNetwork());
+
// Transforms do not need to be persisted; the IkeSession will keep
// them alive for us
mIpSecManager.applyTunnelModeTransform(mTunnelIface, direction, transform);
@@ -3136,13 +3138,13 @@ public class Vpn {
// 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 =
+ mScheduledHandleRetryIkeSessionFuture =
mExecutor.schedule(() -> {
startOrMigrateIkeSession(mActiveNetwork);
- // Reset mScheduledHandleRetryIkeSessionTimeout since it's already run on
+ // Reset mScheduledHandleRetryIkeSessionFuture since it's already run on
// executor thread.
- mScheduledHandleRetryIkeSessionTimeout = null;
+ mScheduledHandleRetryIkeSessionFuture = null;
}, retryDelay, TimeUnit.SECONDS);
}
@@ -3185,12 +3187,10 @@ public class Vpn {
mActiveNetwork = null;
}
- if (mScheduledHandleNetworkLostTimeout != null
- && !mScheduledHandleNetworkLostTimeout.isCancelled()
- && !mScheduledHandleNetworkLostTimeout.isDone()) {
+ if (mScheduledHandleNetworkLostFuture != null) {
final IllegalStateException exception =
new IllegalStateException(
- "Found a pending mScheduledHandleNetworkLostTimeout");
+ "Found a pending mScheduledHandleNetworkLostFuture");
Log.i(
TAG,
"Unexpected error in onDefaultNetworkLost. Tear down session",
@@ -3207,13 +3207,26 @@ public class Vpn {
+ " on session with token "
+ mCurrentToken);
+ final int 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 =
+ mScheduledHandleNetworkLostFuture =
mExecutor.schedule(
() -> {
- handleSessionLost(null, network);
+ if (isActiveToken(token)) {
+ handleSessionLost(null, network);
+ } else {
+ Log.d(
+ TAG,
+ "Scheduled handleSessionLost fired for "
+ + "obsolete token "
+ + token);
+ }
+
+ // Reset mScheduledHandleNetworkLostFuture since it's
+ // already run on executor thread.
+ mScheduledHandleNetworkLostFuture = null;
},
NETWORK_LOST_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
@@ -3224,28 +3237,26 @@ public class Vpn {
}
private void cancelHandleNetworkLostTimeout() {
- if (mScheduledHandleNetworkLostTimeout != null
- && !mScheduledHandleNetworkLostTimeout.isDone()) {
+ if (mScheduledHandleNetworkLostFuture != null) {
// It does not matter what to put in #cancel(boolean), because it is impossible
- // that the task tracked by mScheduledHandleNetworkLostTimeout is
+ // that the task tracked by mScheduledHandleNetworkLostFuture 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;
+ mScheduledHandleNetworkLostFuture.cancel(false /* mayInterruptIfRunning */);
+ mScheduledHandleNetworkLostFuture = null;
}
}
private void cancelRetryNewIkeSessionFuture() {
- if (mScheduledHandleRetryIkeSessionTimeout != null
- && !mScheduledHandleRetryIkeSessionTimeout.isDone()) {
+ if (mScheduledHandleRetryIkeSessionFuture != null) {
// It does not matter what to put in #cancel(boolean), because it is impossible
- // that the task tracked by mScheduledHandleRetryIkeSessionTimeout is
+ // that the task tracked by mScheduledHandleRetryIkeSessionFuture 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;
+ mScheduledHandleRetryIkeSessionFuture.cancel(false /* mayInterruptIfRunning */);
+ mScheduledHandleRetryIkeSessionFuture = null;
}
}
@@ -3285,7 +3296,7 @@ public class Vpn {
}
private void handleSessionLost(@Nullable Exception exception, @Nullable Network network) {
- // Cancel mScheduledHandleNetworkLostTimeout if the session it is going to terminate is
+ // Cancel mScheduledHandleNetworkLostFuture if the session it is going to terminate is
// already terminated due to other failures.
cancelHandleNetworkLostTimeout();
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/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 1a568c30c899..97e9c6458376 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -49,7 +49,6 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
-import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ArrayBlockingQueue;
@@ -464,7 +463,7 @@ final class HdmiCecController {
}
int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
- LinkedList<Integer> pollingCandidates = new LinkedList<>();
+ ArrayList<Integer> pollingCandidates = new ArrayList<>();
switch (iterationStrategy) {
case Constants.POLL_ITERATION_IN_ORDER:
for (int i = Constants.ADDR_TV; i <= Constants.ADDR_SPECIFIC_USE; ++i) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index fb2d2ee08cbd..16bffd9a597b 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -71,7 +71,7 @@ abstract class HdmiCecLocalDevice {
protected final int mDeviceType;
protected int mPreferredAddress;
@GuardedBy("mLock")
- protected HdmiDeviceInfo mDeviceInfo;
+ private HdmiDeviceInfo mDeviceInfo;
protected int mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE;
protected int mLastKeyRepeatCount = 0;
@@ -666,11 +666,9 @@ abstract class HdmiCecLocalDevice {
* Computes the set of supported device features, and updates local state to match.
*/
private void updateDeviceFeatures() {
- synchronized (mLock) {
- setDeviceInfo(getDeviceInfo().toBuilder()
- .setDeviceFeatures(computeDeviceFeatures())
- .build());
- }
+ setDeviceInfo(getDeviceInfo().toBuilder()
+ .setDeviceFeatures(computeDeviceFeatures())
+ .build());
}
/**
@@ -678,9 +676,7 @@ abstract class HdmiCecLocalDevice {
*/
protected final DeviceFeatures getDeviceFeatures() {
updateDeviceFeatures();
- synchronized (mLock) {
- return getDeviceInfo().getDeviceFeatures();
- }
+ return getDeviceInfo().getDeviceFeatures();
}
@Constants.HandleMessageResult
@@ -982,14 +978,12 @@ abstract class HdmiCecLocalDevice {
return mDeviceType;
}
- @GuardedBy("mLock")
HdmiDeviceInfo getDeviceInfo() {
synchronized (mLock) {
return mDeviceInfo;
}
}
- @GuardedBy("mLock")
void setDeviceInfo(HdmiDeviceInfo info) {
synchronized (mLock) {
mDeviceInfo = info;
@@ -1042,10 +1036,8 @@ abstract class HdmiCecLocalDevice {
// Send <Give Features> if using CEC 2.0 or above.
if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) {
- synchronized (mLock) {
- mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures(
- getDeviceInfo().getLogicalAddress(), targetAddress));
- }
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures(
+ getDeviceInfo().getLogicalAddress(), targetAddress));
}
// If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 26e38bdb6d51..5cfe27a62e1b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -138,9 +138,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
// does not poll local devices, we should put device info of local device
// manually here.
for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) {
- synchronized (device.mLock) {
- mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo());
- }
+ mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo());
}
List<HotplugDetectionAction> hotplugActions =
@@ -179,11 +177,9 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
@ServiceThreadOnly
void deviceSelect(int id, IHdmiControlCallback callback) {
assertRunOnServiceThread();
- synchronized (mLock) {
- if (id == getDeviceInfo().getId()) {
- mService.oneTouchPlay(callback);
- return;
- }
+ if (id == getDeviceInfo().getId()) {
+ mService.oneTouchPlay(callback);
+ return;
}
HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id);
if (targetDevice == null) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index c0c02027a7a1..7e8a2cc6d835 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -82,7 +82,7 @@ abstract class HdmiCecLocalDeviceSource extends HdmiCecLocalDevice {
callback);
if (action == null) {
Slog.w(TAG, "Cannot initiate queryDisplayStatus");
- invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
+ invokeCallback(callback, HdmiControlManager.POWER_STATUS_UNKNOWN);
return;
}
addAndStartAction(action);
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index f8a74f4f3f55..6c37bf1bb9e2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1331,13 +1331,11 @@ public class HdmiControlService extends SystemService {
*/
private boolean sourceAddressIsLocal(HdmiCecMessage message) {
for (HdmiCecLocalDevice device : getAllLocalDevices()) {
- synchronized (device.mLock) {
- if (message.getSource() == device.getDeviceInfo().getLogicalAddress()
- && message.getSource() != Constants.ADDR_UNREGISTERED) {
- HdmiLogger.warning(
- "Unexpected source: message sent from device itself, " + message);
- return true;
- }
+ if (message.getSource() == device.getDeviceInfo().getLogicalAddress()
+ && message.getSource() != Constants.ADDR_UNREGISTERED) {
+ HdmiLogger.warning(
+ "Unexpected source: message sent from device itself, " + message);
+ return true;
}
}
return false;
@@ -1560,9 +1558,7 @@ public class HdmiControlService extends SystemService {
if (deviceInfo.getDisplayName().equals(newDisplayName)) {
continue;
}
- synchronized (device.mLock) {
- device.setDeviceInfo(deviceInfo.toBuilder().setDisplayName(newDisplayName).build());
- }
+ device.setDeviceInfo(deviceInfo.toBuilder().setDisplayName(newDisplayName).build());
sendCecCommand(
HdmiCecMessageBuilder.buildSetOsdNameCommand(
deviceInfo.getLogicalAddress(), Constants.ADDR_TV, newDisplayName));
@@ -2728,6 +2724,15 @@ public class HdmiControlService extends SystemService {
return mIsCecAvailable;
}
+ /**
+ * Queries the display status of the TV and calls {@code callback} upon completion.
+ *
+ * If this is a non-source device, or if the query fails for any reason, the callback will
+ * be called with {@link HdmiControlManager.POWER_STATUS_UNKNOWN}.
+ *
+ * If the query succeeds, the callback will be called with one of the other power status
+ * constants.
+ */
@ServiceThreadOnly
protected void queryDisplayStatus(final IHdmiControlCallback callback) {
assertRunOnServiceThread();
@@ -2745,7 +2750,7 @@ public class HdmiControlService extends SystemService {
if (source == null) {
Slog.w(TAG, "Local source device not available");
- invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
+ invokeCallback(callback, HdmiControlManager.POWER_STATUS_UNKNOWN);
return;
}
source.queryDisplayStatus(callback);
@@ -3114,13 +3119,7 @@ public class HdmiControlService extends SystemService {
if (isEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) {
queryDisplayStatus(new IHdmiControlCallback.Stub() {
public void onComplete(int status) {
- if (status == HdmiControlManager.POWER_STATUS_UNKNOWN
- || status == HdmiControlManager.RESULT_EXCEPTION
- || status == HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE) {
- mIsCecAvailable = false;
- } else {
- mIsCecAvailable = true;
- }
+ mIsCecAvailable = status != HdmiControlManager.POWER_STATUS_UNKNOWN;
if (!listeners.isEmpty()) {
invokeHdmiControlStatusChangeListenerLocked(listeners,
isEnabled, mIsCecAvailable);
@@ -4096,10 +4095,7 @@ public class HdmiControlService extends SystemService {
public void onAudioDeviceVolumeChanged(
@NonNull AudioDeviceAttributes audioDevice,
@NonNull VolumeInfo volumeInfo) {
- int localDeviceAddress;
- synchronized (mLocalDevice.mLock) {
- localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
- }
+ int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
sendCecCommand(SetAudioVolumeLevelMessage.build(
localDeviceAddress,
mSystemAudioDevice.getLogicalAddress(),
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 0186fe5c357c..4ffad91bbfad 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -149,10 +149,11 @@ final class IInputMethodInvoker {
@AnyThread
void startInput(IBinder startInputToken, IRemoteInputConnection inputConnection,
- EditorInfo attribute, boolean restarting, @InputMethodNavButtonFlags int navButtonFlags,
+ EditorInfo editorInfo, boolean restarting,
+ @InputMethodNavButtonFlags int navButtonFlags,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
try {
- mTarget.startInput(startInputToken, inputConnection, attribute, restarting,
+ mTarget.startInput(startInputToken, inputConnection, editorInfo, restarting,
navButtonFlags, imeDispatcher);
} catch (RemoteException e) {
logRemoteException(e);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
new file mode 100644
index 000000000000..68753ab909b3
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.android.server.inputmethod.SubtypeUtils.SUBTYPE_MODE_ANY;
+import static com.android.server.inputmethod.SubtypeUtils.SUBTYPE_MODE_KEYBOARD;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class provides utility methods to generate or filter {@link InputMethodInfo} for
+ * {@link InputMethodManagerService}.
+ *
+ * <p>This class is intentionally package-private. Utility methods here are tightly coupled with
+ * implementation details in {@link InputMethodManagerService}. Hence this class is not suitable
+ * for other components to directly use.</p>
+ */
+final class InputMethodInfoUtils {
+ private static final String TAG = "InputMethodInfoUtils";
+
+ /**
+ * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs
+ * that are mainly used until the system becomes ready. Note that {@link Locale} in this array
+ * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH}
+ * doesn't automatically match {@code Locale("en", "IN")}.
+ */
+ private static final Locale[] SEARCH_ORDER_OF_FALLBACK_LOCALES = {
+ Locale.ENGLISH, // "en"
+ Locale.US, // "en_US"
+ Locale.UK, // "en_GB"
+ };
+ private static final Locale ENGLISH_LOCALE = new Locale("en");
+
+ private static final class InputMethodListBuilder {
+ // Note: We use LinkedHashSet instead of android.util.ArraySet because the enumeration
+ // order can have non-trivial effect in the call sites.
+ @NonNull
+ private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
+
+ InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context,
+ boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry,
+ String requiredSubtypeMode) {
+ for (int i = 0; i < imis.size(); ++i) {
+ final InputMethodInfo imi = imis.get(i);
+ if (isSystemImeThatHasSubtypeOf(imi, context,
+ checkDefaultAttribute, locale, checkCountry, requiredSubtypeMode)) {
+ mInputMethodSet.add(imi);
+ }
+ }
+ return this;
+ }
+
+ // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
+ // documented more clearly.
+ InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
+ // If one or more auxiliary input methods are available, OK to stop populating the list.
+ for (final InputMethodInfo imi : mInputMethodSet) {
+ if (imi.isAuxiliaryIme()) {
+ return this;
+ }
+ }
+ boolean added = false;
+ for (int i = 0; i < imis.size(); ++i) {
+ final InputMethodInfo imi = imis.get(i);
+ if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
+ true /* checkDefaultAttribute */)) {
+ mInputMethodSet.add(imi);
+ added = true;
+ }
+ }
+ if (added) {
+ return this;
+ }
+ for (int i = 0; i < imis.size(); ++i) {
+ final InputMethodInfo imi = imis.get(i);
+ if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
+ false /* checkDefaultAttribute */)) {
+ mInputMethodSet.add(imi);
+ }
+ }
+ return this;
+
+ }
+
+ public boolean isEmpty() {
+ return mInputMethodSet.isEmpty();
+ }
+
+ @NonNull
+ public ArrayList<InputMethodInfo> build() {
+ return new ArrayList<>(mInputMethodSet);
+ }
+ }
+
+ private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
+ ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
+ @Nullable Locale fallbackLocale) {
+ // Once the system becomes ready, we pick up at least one keyboard in the following order.
+ // Secondary users fall into this category in general.
+ // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true
+ // 2. checkDefaultAttribute: true, locale: systemLocale, checkCountry: false
+ // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true
+ // 4. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false
+ // 5. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true
+ // 6. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false
+ // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
+
+ final InputMethodListBuilder builder = new InputMethodListBuilder();
+ builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
+ true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
+ if (!builder.isEmpty()) {
+ return builder;
+ }
+ builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
+ false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
+ if (!builder.isEmpty()) {
+ return builder;
+ }
+ builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
+ true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
+ if (!builder.isEmpty()) {
+ return builder;
+ }
+ builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
+ false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
+ if (!builder.isEmpty()) {
+ return builder;
+ }
+ builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
+ true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
+ if (!builder.isEmpty()) {
+ return builder;
+ }
+ builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
+ false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
+ if (!builder.isEmpty()) {
+ return builder;
+ }
+ Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray())
+ + " systemLocale=" + systemLocale + " fallbackLocale=" + fallbackLocale);
+ return builder;
+ }
+
+ static ArrayList<InputMethodInfo> getDefaultEnabledImes(
+ Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) {
+ final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
+ // We will primarily rely on the system locale, but also keep relying on the fallback locale
+ // as a last resort.
+ // Also pick up suitable IMEs regardless of the software keyboard support (e.g. Voice IMEs),
+ // then pick up suitable auxiliary IMEs when necessary (e.g. Voice IMEs with "automatic"
+ // subtype)
+ final Locale systemLocale = LocaleUtils.getSystemLocaleFromContext(context);
+ final InputMethodListBuilder builder =
+ getMinimumKeyboardSetWithSystemLocale(imis, context, systemLocale, fallbackLocale);
+ if (!onlyMinimum) {
+ builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
+ true /* checkCountry */, SUBTYPE_MODE_ANY)
+ .fillAuxiliaryImes(imis, context);
+ }
+ return builder.build();
+ }
+
+ static ArrayList<InputMethodInfo> getDefaultEnabledImes(
+ Context context, ArrayList<InputMethodInfo> imis) {
+ return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
+ }
+
+ /**
+ * Chooses an eligible system voice IME from the given IMEs.
+ *
+ * @param methodMap Map from the IME ID to {@link InputMethodInfo}.
+ * @param systemSpeechRecognizerPackageName System speech recognizer configured by the system
+ * config.
+ * @param currentDefaultVoiceImeId IME ID currently set to
+ * {@link Settings.Secure#DEFAULT_VOICE_INPUT_METHOD}
+ * @return {@link InputMethodInfo} that is found in {@code methodMap} and most suitable for
+ * the system voice IME.
+ */
+ @Nullable
+ static InputMethodInfo chooseSystemVoiceIme(
+ @NonNull ArrayMap<String, InputMethodInfo> methodMap,
+ @Nullable String systemSpeechRecognizerPackageName,
+ @Nullable String currentDefaultVoiceImeId) {
+ if (TextUtils.isEmpty(systemSpeechRecognizerPackageName)) {
+ return null;
+ }
+ final InputMethodInfo defaultVoiceIme = methodMap.get(currentDefaultVoiceImeId);
+ // If the config matches the package of the setting, use the current one.
+ if (defaultVoiceIme != null && defaultVoiceIme.isSystem()
+ && defaultVoiceIme.getPackageName().equals(systemSpeechRecognizerPackageName)) {
+ return defaultVoiceIme;
+ }
+ InputMethodInfo firstMatchingIme = null;
+ final int methodCount = methodMap.size();
+ for (int i = 0; i < methodCount; ++i) {
+ final InputMethodInfo imi = methodMap.valueAt(i);
+ if (!imi.isSystem()) {
+ continue;
+ }
+ if (!TextUtils.equals(imi.getPackageName(), systemSpeechRecognizerPackageName)) {
+ continue;
+ }
+ if (firstMatchingIme != null) {
+ Slog.e(TAG, "At most one InputMethodService can be published in "
+ + "systemSpeechRecognizer: " + systemSpeechRecognizerPackageName
+ + ". Ignoring all of them.");
+ return null;
+ }
+ firstMatchingIme = imi;
+ }
+ return firstMatchingIme;
+ }
+
+ static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
+ if (enabledImes == null || enabledImes.isEmpty()) {
+ return null;
+ }
+ // We'd prefer to fall back on a system IME, since that is safer.
+ int i = enabledImes.size();
+ int firstFoundSystemIme = -1;
+ while (i > 0) {
+ i--;
+ final InputMethodInfo imi = enabledImes.get(i);
+ if (imi.isAuxiliaryIme()) {
+ continue;
+ }
+ if (imi.isSystem() && SubtypeUtils.containsSubtypeOf(imi, ENGLISH_LOCALE,
+ false /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
+ return imi;
+ }
+ if (firstFoundSystemIme < 0 && imi.isSystem()) {
+ firstFoundSystemIme = i;
+ }
+ }
+ return enabledImes.get(Math.max(firstFoundSystemIme, 0));
+ }
+
+ private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi,
+ Context context, boolean checkDefaultAttribute) {
+ if (!imi.isSystem()) {
+ return false;
+ }
+ if (checkDefaultAttribute && !imi.isDefault(context)) {
+ return false;
+ }
+ if (!imi.isAuxiliaryIme()) {
+ return false;
+ }
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype s = imi.getSubtypeAt(i);
+ if (s.overridesImplicitlyEnabledSubtype()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis,
+ Context context) {
+ // At first, find the fallback locale from the IMEs that are declared as "default" in the
+ // current locale. Note that IME developers can declare an IME as "default" only for
+ // some particular locales but "not default" for other locales.
+ for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
+ for (int i = 0; i < imis.size(); ++i) {
+ if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
+ true /* checkDefaultAttribute */, fallbackLocale,
+ true /* checkCountry */, SubtypeUtils.SUBTYPE_MODE_KEYBOARD)) {
+ return fallbackLocale;
+ }
+ }
+ }
+ // If no fallback locale is found in the above condition, find fallback locales regardless
+ // of the "default" attribute as a last resort.
+ for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
+ for (int i = 0; i < imis.size(); ++i) {
+ if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
+ false /* checkDefaultAttribute */, fallbackLocale,
+ true /* checkCountry */, SubtypeUtils.SUBTYPE_MODE_KEYBOARD)) {
+ return fallbackLocale;
+ }
+ }
+ }
+ Slog.w(TAG, "Found no fallback locale. imis=" + Arrays.toString(imis.toArray()));
+ return null;
+ }
+
+ private static boolean isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context,
+ boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry,
+ String requiredSubtypeMode) {
+ if (!imi.isSystem()) {
+ return false;
+ }
+ if (checkDefaultAttribute && !imi.isDefault(context)) {
+ return false;
+ }
+ if (!SubtypeUtils.containsSubtypeOf(imi, requiredLocale, checkCountry,
+ requiredSubtypeMode)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7c4803b2b477..5a8190a833e3 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -596,9 +596,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
/**
- * The attributes last provided by the current client.
+ * The {@link EditorInfo} last provided by the current client.
*/
- EditorInfo mCurAttribute;
+ EditorInfo mCurEditorInfo;
/**
* A special {@link Matrix} to convert virtual screen coordinates to the IME target display
@@ -1787,7 +1787,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (selectedMethodId != null && !mMethodMap.get(selectedMethodId).isSystem()) {
return;
}
- final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
+ final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes(
context, mSettings.getEnabledInputMethodListLocked());
if (suitableImes.isEmpty()) {
Slog.i(TAG, "No default found");
@@ -2393,7 +2393,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
getCurTokenLocked(),
mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
UserHandle.getUserId(mCurClient.uid), mCurClient.selfReportedDisplayId,
- mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode,
+ mCurFocusedWindow, mCurEditorInfo, mCurFocusedWindowSoftInputMode,
getSequenceNumberLocked());
mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow);
mStartInputHistory.addEntry(info);
@@ -2413,7 +2413,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
final SessionState session = mCurClient.curSession;
setEnabledSessionLocked(session);
- session.method.startInput(startInputToken, mCurInputConnection, mCurAttribute, restarting,
+ session.method.startInput(startInputToken, mCurInputConnection, mCurEditorInfo, restarting,
navButtonFlags, mCurImeDispatcher);
if (mShowRequested) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
@@ -2479,7 +2479,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final Binder startInputToken = new Binder();
setEnabledSessionForAccessibilityLocked(mCurClient.mAccessibilitySessions);
AccessibilityManagerInternal.get().startInput(mCurRemoteAccessibilityInputConnection,
- mCurAttribute, !initial /* restarting */);
+ mCurEditorInfo, !initial /* restarting */);
}
if (accessibilitySession != null) {
@@ -2522,7 +2522,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
private InputBindResult startInputUncheckedLocked(@NonNull ClientState cs,
IRemoteInputConnection inputConnection,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
- @NonNull EditorInfo attribute, @StartInputFlags int startInputFlags,
+ @NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags,
@StartInputReason int startInputReason,
int unverifiedTargetSdkVersion,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
@@ -2541,9 +2541,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
- attribute.packageName)) {
+ editorInfo.packageName)) {
Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
- + " uid=" + cs.uid + " package=" + attribute.packageName);
+ + " uid=" + cs.uid + " package=" + editorInfo.packageName);
return InputBindResult.INVALID_PACKAGE_NAME;
}
@@ -2573,7 +2573,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mCurVirtualDisplayToScreenMatrix =
getVirtualDisplayToScreenMatrixLocked(cs.selfReportedDisplayId,
mDisplayIdToShowIme);
- mCurAttribute = attribute;
+ mCurEditorInfo = editorInfo;
// If configured, we want to avoid starting up the IME if it is not supposed to be showing
if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags,
@@ -3578,12 +3578,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
public InputBindResult startInputOrWindowGainedFocus(
@StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
@StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
- int windowFlags, @Nullable EditorInfo attribute, IRemoteInputConnection inputConnection,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
return startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken,
- startInputFlags, softInputMode, windowFlags, attribute, inputConnection,
+ startInputFlags, softInputMode, windowFlags, editorInfo, inputConnection,
remoteAccessibilityInputConnection, unverifiedTargetSdkVersion,
imeDispatcher);
}
@@ -3592,7 +3593,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
private InputBindResult startInputOrWindowGainedFocusInternal(
@StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
@StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
- int windowFlags, @Nullable EditorInfo attribute,
+ int windowFlags, @Nullable EditorInfo editorInfo,
@Nullable IRemoteInputConnection inputConnection,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion,
@@ -3608,13 +3609,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
"InputMethodManagerService#startInputOrWindowGainedFocus");
final int callingUserId = UserHandle.getCallingUserId();
final int userId;
- if (attribute != null && attribute.targetInputMethodUser != null
- && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
+ if (editorInfo != null && editorInfo.targetInputMethodUser != null
+ && editorInfo.targetInputMethodUser.getIdentifier() != callingUserId) {
mContext.enforceCallingPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL,
"Using EditorInfo.targetInputMethodUser requires"
+ " INTERACT_ACROSS_USERS_FULL.");
- userId = attribute.targetInputMethodUser.getIdentifier();
+ userId = editorInfo.targetInputMethodUser.getIdentifier();
if (!mUserManagerInternal.isUserRunning(userId)) {
// There is a chance that we hit here because of race condition. Let's just
// return an error code instead of crashing the caller process, which at
@@ -3632,7 +3633,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
try {
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
client, windowToken, startInputFlags, softInputMode, windowFlags,
- attribute, inputConnection, remoteAccessibilityInputConnection,
+ editorInfo, inputConnection, remoteAccessibilityInputConnection,
unverifiedTargetSdkVersion, userId, imeDispatcher);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -3643,7 +3644,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
+ InputMethodDebug.startInputReasonToString(startInputReason)
+ " windowFlags=#" + Integer.toHexString(windowFlags)
- + " editorInfo=" + attribute);
+ + " editorInfo=" + editorInfo);
return InputBindResult.NULL;
}
@@ -3658,7 +3659,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
private InputBindResult startInputOrWindowGainedFocusInternalLocked(
@StartInputReason int startInputReason, IInputMethodClient client,
@NonNull IBinder windowToken, @StartInputFlags int startInputFlags,
- @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute,
+ @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo,
IRemoteInputConnection inputContext,
@Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
@@ -3668,7 +3669,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
+ InputMethodDebug.startInputReasonToString(startInputReason)
+ " client=" + client.asBinder()
+ " inputContext=" + inputContext
- + " attribute=" + attribute
+ + " editorInfo=" + editorInfo
+ " startInputFlags="
+ InputMethodDebug.startInputFlagsToString(startInputFlags)
+ " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
@@ -3751,13 +3752,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (sameWindowFocused && isTextEditor) {
if (DEBUG) {
Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
- + " attribute=" + attribute + ", token = " + windowToken
+ + " editorInfo=" + editorInfo + ", token = " + windowToken
+ ", startInputReason="
+ InputMethodDebug.startInputReasonToString(startInputReason));
}
- if (attribute != null) {
+ if (editorInfo != null) {
return startInputUncheckedLocked(cs, inputContext,
- remoteAccessibilityInputConnection, attribute, startInputFlags,
+ remoteAccessibilityInputConnection, editorInfo, startInputFlags,
startInputReason, unverifiedTargetSdkVersion, imeDispatcher);
}
return new InputBindResult(
@@ -3796,10 +3797,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation).
// Because the app might leverage these flags to hide soft-keyboard with showing their own
// UI for input.
- if (isTextEditor && attribute != null
+ if (isTextEditor && editorInfo != null
&& shouldRestoreImeVisibility(windowToken, softInputMode)) {
res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection,
- attribute, startInputFlags, startInputReason, unverifiedTargetSdkVersion,
+ editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion,
imeDispatcher);
showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
@@ -3837,9 +3838,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// by the IME) or if running on a large screen where there
// is more room for the target window + IME.
if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
- if (attribute != null) {
+ if (editorInfo != null) {
res = startInputUncheckedLocked(cs, inputContext,
- remoteAccessibilityInputConnection, attribute, startInputFlags,
+ remoteAccessibilityInputConnection, editorInfo, startInputFlags,
startInputReason, unverifiedTargetSdkVersion,
imeDispatcher);
didStart = true;
@@ -3870,9 +3871,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
if (isSoftInputModeStateVisibleAllowed(
unverifiedTargetSdkVersion, startInputFlags)) {
- if (attribute != null) {
+ if (editorInfo != null) {
res = startInputUncheckedLocked(cs, inputContext,
- remoteAccessibilityInputConnection, attribute, startInputFlags,
+ remoteAccessibilityInputConnection, editorInfo, startInputFlags,
startInputReason, unverifiedTargetSdkVersion,
imeDispatcher);
didStart = true;
@@ -3891,9 +3892,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (isSoftInputModeStateVisibleAllowed(
unverifiedTargetSdkVersion, startInputFlags)) {
if (!sameWindowFocused) {
- if (attribute != null) {
+ if (editorInfo != null) {
res = startInputUncheckedLocked(cs, inputContext,
- remoteAccessibilityInputConnection, attribute, startInputFlags,
+ remoteAccessibilityInputConnection, editorInfo, startInputFlags,
startInputReason, unverifiedTargetSdkVersion,
imeDispatcher);
didStart = true;
@@ -3910,7 +3911,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
if (!didStart) {
- if (attribute != null) {
+ if (editorInfo != null) {
if (sameWindowFocused) {
// On previous platforms, when Dialogs re-gained focus, the Activity behind
// would briefly gain focus first, and dismiss the IME.
@@ -3924,7 +3925,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
}
res = startInputUncheckedLocked(cs, inputContext,
- remoteAccessibilityInputConnection, attribute, startInputFlags,
+ remoteAccessibilityInputConnection, editorInfo, startInputFlags,
startInputReason, unverifiedTargetSdkVersion,
imeDispatcher);
} else {
@@ -4048,7 +4049,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
if (subtype != null) {
setInputMethodWithSubtypeIdLocked(token, id,
- InputMethodUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
+ SubtypeUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
subtype.hashCode()));
} else {
setInputMethod(token, id);
@@ -4092,7 +4093,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// defined, there is no need to switch to the last IME.
if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
targetLastImiId = lastIme.first;
- subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
+ subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
}
}
@@ -4111,13 +4112,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final InputMethodInfo imi = enabled.get(i);
if (imi.getSubtypeCount() > 0 && imi.isSystem()) {
InputMethodSubtype keyboardSubtype =
- InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes,
- InputMethodUtils.getSubtypes(imi),
- InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
+ SubtypeUtils.findLastResortApplicableSubtypeLocked(mRes,
+ SubtypeUtils.getSubtypes(imi),
+ SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
if (keyboardSubtype != null) {
targetLastImiId = imi.getId();
- subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
- imi, keyboardSubtype.hashCode());
+ subtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
+ keyboardSubtype.hashCode());
if(keyboardSubtype.getLocale().equals(locale)) {
break;
}
@@ -4187,8 +4188,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (lastImi == null) return null;
try {
final int lastSubtypeHash = Integer.parseInt(lastIme.second);
- final int lastSubtypeId =
- InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
+ final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi,
+ lastSubtypeHash);
if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
return null;
}
@@ -4486,8 +4487,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mWindowManagerInternal.getWindowName(mLastImeTargetWindow));
proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE,
InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
- if (mCurAttribute != null) {
- mCurAttribute.dumpDebug(proto, CUR_ATTRIBUTE);
+ if (mCurEditorInfo != null) {
+ mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
}
proto.write(CUR_ID, getCurIdLocked());
proto.write(SHOW_REQUESTED, mShowRequested);
@@ -4602,7 +4603,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mWindowManagerInternal.onToggleImeRequested(
show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
- mCurFocusedWindowClient, mCurAttribute, info.focusedWindowName,
+ mCurFocusedWindowClient, mCurEditorInfo, info.focusedWindowName,
mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName));
}
@@ -4867,7 +4868,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@GuardedBy("ImfLock.class")
private boolean chooseNewDefaultIMELocked() {
- final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
+ final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME(
mSettings.getEnabledInputMethodListLocked());
if (imi != null) {
if (DEBUG) {
@@ -5010,7 +5011,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
final ArrayList<InputMethodInfo> defaultEnabledIme =
- InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList,
+ InputMethodInfoUtils.getDefaultEnabledImes(mContext, mMethodList,
reenableMinimumNonAuxSystemImes);
final int N = defaultEnabledIme.size();
for (int i = 0; i < N; ++i) {
@@ -5066,7 +5067,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
final String systemSpeechRecognizer =
mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod();
- final InputMethodInfo newSystemVoiceIme = InputMethodUtils.chooseSystemVoiceIme(
+ final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme(
mMethodMap, systemSpeechRecognizer, currentDefaultVoiceImeId);
if (newSystemVoiceIme == null) {
if (DEBUG) {
@@ -5192,8 +5193,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
if (subtypeHashCode != null) {
try {
- lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
- imi, Integer.parseInt(subtypeHashCode));
+ lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(imi,
+ Integer.parseInt(subtypeHashCode));
} catch (NumberFormatException e) {
Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
}
@@ -5228,7 +5229,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return null;
}
if (!subtypeIsSelected || mCurrentSubtype == null
- || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
+ || !SubtypeUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
int subtypeId = mSettings.getSelectedInputMethodSubtypeId(selectedMethodId);
if (subtypeId == NOT_A_SUBTYPE_ID) {
// If there are no selected subtypes, the framework will try to find
@@ -5241,17 +5242,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
} else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
- mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
+ mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
mRes, explicitlyOrImplicitlyEnabledSubtypes,
- InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true);
+ SubtypeUtils.SUBTYPE_MODE_KEYBOARD, null, true);
if (mCurrentSubtype == null) {
- mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
- mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
- true);
+ mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
+ mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, true);
}
}
} else {
- mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId);
+ mCurrentSubtype = SubtypeUtils.getSubtypes(imi).get(subtypeId);
}
}
return mCurrentSubtype;
@@ -5511,9 +5511,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// We cannot simply distinguish a bad IME that reports an arbitrary package name from
// an unfortunate IME whose internal state is already obsolete due to the asynchronous
// nature of our system. Let's compare it with our internal record.
- if (!TextUtils.equals(mCurAttribute.packageName, packageName)) {
- Slog.e(TAG, "Ignoring createInputContentUriToken mCurAttribute.packageName="
- + mCurAttribute.packageName + " packageName=" + packageName);
+ if (!TextUtils.equals(mCurEditorInfo.packageName, packageName)) {
+ Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName="
+ + mCurEditorInfo.packageName + " packageName=" + packageName);
return null;
}
// This user ID can never bee spoofed.
@@ -6168,8 +6168,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
setInputMethodEnabledLocked(inputMethodInfo.getId(), false);
}
// Re-enable with default enabled IMEs.
- for (InputMethodInfo imi :
- InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList)) {
+ for (InputMethodInfo imi : InputMethodInfoUtils.getDefaultEnabledImes(
+ mContext, mMethodList)) {
setInputMethodEnabledLocked(imi.getId(), true);
}
updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
@@ -6190,8 +6190,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mContext.getResources(), mContext.getContentResolver(), methodMap,
userId, false);
- nextEnabledImes = InputMethodUtils.getDefaultEnabledImes(mContext, methodList);
- nextIme = InputMethodUtils.getMostApplicableDefaultIME(nextEnabledImes).getId();
+ nextEnabledImes = InputMethodInfoUtils.getDefaultEnabledImes(mContext,
+ methodList);
+ nextIme = InputMethodInfoUtils.getMostApplicableDefaultIME(
+ nextEnabledImes).getId();
// Reset enabled IMEs.
settings.putEnabledInputMethodsStr("");
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index c255fe14c03e..11e6923aa75a 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -105,7 +105,7 @@ final class InputMethodMenuController {
if (currentSubtype != null) {
final String curMethodId = mService.getSelectedMethodIdLocked();
final InputMethodInfo currentImi = mMethodMap.get(curMethodId);
- lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
+ lastInputMethodSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(
currentImi, currentSubtype.hashCode());
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index f8894c64304d..a64322625797 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -241,8 +241,8 @@ final class InputMethodSubtypeSwitchingController {
}
private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) {
- return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi,
- subtype.hashCode()) : NOT_A_SUBTYPE_ID;
+ return subtype != null ? SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
+ : NOT_A_SUBTYPE_ID;
}
private static class StaticRotationList {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 2d1a22e7552d..70132670e68e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -27,7 +27,6 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Build;
-import android.os.LocaleList;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
@@ -40,8 +39,6 @@ import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.textservice.SpellCheckerInfo;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
@@ -50,9 +47,7 @@ import com.android.server.textservices.TextServicesManagerInternal;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Locale;
import java.util.function.Predicate;
/**
@@ -66,40 +61,13 @@ import java.util.function.Predicate;
final class InputMethodUtils {
public static final boolean DEBUG = false;
static final int NOT_A_SUBTYPE_ID = -1;
- private static final String SUBTYPE_MODE_ANY = null;
- static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
private static final String TAG = "InputMethodUtils";
- private static final Locale ENGLISH_LOCALE = new Locale("en");
private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
- private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
- "EnabledWhenDefaultIsNotAsciiCapable";
// The string for enabled input method is saved as follows:
// example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
private static final char INPUT_METHOD_SEPARATOR = ':';
private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
- /**
- * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs
- * that are mainly used until the system becomes ready. Note that {@link Locale} in this array
- * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH}
- * doesn't automatically match {@code Locale("en", "IN")}.
- */
- private static final Locale[] SEARCH_ORDER_OF_FALLBACK_LOCALES = {
- Locale.ENGLISH, // "en"
- Locale.US, // "en_US"
- Locale.UK, // "en_GB"
- };
-
- // A temporary workaround for the performance concerns in
- // #getImplicitlyApplicableSubtypesLocked(Resources, InputMethodInfo).
- // TODO: Optimize all the critical paths including this one.
- private static final Object sCacheLock = new Object();
- @GuardedBy("sCacheLock")
- private static LocaleList sCachedSystemLocales;
- @GuardedBy("sCacheLock")
- private static InputMethodInfo sCachedInputMethodInfo;
- @GuardedBy("sCacheLock")
- private static ArrayList<InputMethodSubtype> sCachedResult;
private InputMethodUtils() {
// This utility class is not publicly instantiable.
@@ -130,533 +98,6 @@ final class InputMethodUtils {
}
// ----------------------------------------------------------------------
- private static boolean isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context,
- boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry,
- String requiredSubtypeMode) {
- if (!imi.isSystem()) {
- return false;
- }
- if (checkDefaultAttribute && !imi.isDefault(context)) {
- return false;
- }
- if (!containsSubtypeOf(imi, requiredLocale, checkCountry, requiredSubtypeMode)) {
- return false;
- }
- return true;
- }
-
- @Nullable
- private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis,
- Context context) {
- // At first, find the fallback locale from the IMEs that are declared as "default" in the
- // current locale. Note that IME developers can declare an IME as "default" only for
- // some particular locales but "not default" for other locales.
- for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
- for (int i = 0; i < imis.size(); ++i) {
- if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
- true /* checkDefaultAttribute */, fallbackLocale,
- true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
- return fallbackLocale;
- }
- }
- }
- // If no fallback locale is found in the above condition, find fallback locales regardless
- // of the "default" attribute as a last resort.
- for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
- for (int i = 0; i < imis.size(); ++i) {
- if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
- false /* checkDefaultAttribute */, fallbackLocale,
- true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
- return fallbackLocale;
- }
- }
- }
- Slog.w(TAG, "Found no fallback locale. imis=" + Arrays.toString(imis.toArray()));
- return null;
- }
-
- private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi,
- Context context, boolean checkDefaultAttribute) {
- if (!imi.isSystem()) {
- return false;
- }
- if (checkDefaultAttribute && !imi.isDefault(context)) {
- return false;
- }
- if (!imi.isAuxiliaryIme()) {
- return false;
- }
- final int subtypeCount = imi.getSubtypeCount();
- for (int i = 0; i < subtypeCount; ++i) {
- final InputMethodSubtype s = imi.getSubtypeAt(i);
- if (s.overridesImplicitlyEnabledSubtype()) {
- return true;
- }
- }
- return false;
- }
-
- private static Locale getSystemLocaleFromContext(Context context) {
- try {
- return context.getResources().getConfiguration().locale;
- } catch (Resources.NotFoundException ex) {
- return null;
- }
- }
-
- private static final class InputMethodListBuilder {
- // Note: We use LinkedHashSet instead of android.util.ArraySet because the enumeration
- // order can have non-trivial effect in the call sites.
- @NonNull
- private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
-
- InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context,
- boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry,
- String requiredSubtypeMode) {
- for (int i = 0; i < imis.size(); ++i) {
- final InputMethodInfo imi = imis.get(i);
- if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale,
- checkCountry, requiredSubtypeMode)) {
- mInputMethodSet.add(imi);
- }
- }
- return this;
- }
-
- // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
- // documented more clearly.
- InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
- // If one or more auxiliary input methods are available, OK to stop populating the list.
- for (final InputMethodInfo imi : mInputMethodSet) {
- if (imi.isAuxiliaryIme()) {
- return this;
- }
- }
- boolean added = false;
- for (int i = 0; i < imis.size(); ++i) {
- final InputMethodInfo imi = imis.get(i);
- if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
- true /* checkDefaultAttribute */)) {
- mInputMethodSet.add(imi);
- added = true;
- }
- }
- if (added) {
- return this;
- }
- for (int i = 0; i < imis.size(); ++i) {
- final InputMethodInfo imi = imis.get(i);
- if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
- false /* checkDefaultAttribute */)) {
- mInputMethodSet.add(imi);
- }
- }
- return this;
- }
-
- public boolean isEmpty() {
- return mInputMethodSet.isEmpty();
- }
-
- @NonNull
- public ArrayList<InputMethodInfo> build() {
- return new ArrayList<>(mInputMethodSet);
- }
- }
-
- private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
- ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
- @Nullable Locale fallbackLocale) {
- // Once the system becomes ready, we pick up at least one keyboard in the following order.
- // Secondary users fall into this category in general.
- // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true
- // 2. checkDefaultAttribute: true, locale: systemLocale, checkCountry: false
- // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true
- // 4. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false
- // 5. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true
- // 6. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false
- // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
-
- final InputMethodListBuilder builder = new InputMethodListBuilder();
- builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
- true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
- if (!builder.isEmpty()) {
- return builder;
- }
- builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
- false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
- if (!builder.isEmpty()) {
- return builder;
- }
- builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
- true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
- if (!builder.isEmpty()) {
- return builder;
- }
- builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
- false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
- if (!builder.isEmpty()) {
- return builder;
- }
- builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
- true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
- if (!builder.isEmpty()) {
- return builder;
- }
- builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
- false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
- if (!builder.isEmpty()) {
- return builder;
- }
- Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray())
- + " systemLocale=" + systemLocale + " fallbackLocale=" + fallbackLocale);
- return builder;
- }
-
- static ArrayList<InputMethodInfo> getDefaultEnabledImes(
- Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) {
- final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
- // We will primarily rely on the system locale, but also keep relying on the fallback locale
- // as a last resort.
- // Also pick up suitable IMEs regardless of the software keyboard support (e.g. Voice IMEs),
- // then pick up suitable auxiliary IMEs when necessary (e.g. Voice IMEs with "automatic"
- // subtype)
- final Locale systemLocale = getSystemLocaleFromContext(context);
- final InputMethodListBuilder builder =
- getMinimumKeyboardSetWithSystemLocale(imis, context, systemLocale, fallbackLocale);
- if (!onlyMinimum) {
- builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
- true /* checkCountry */, SUBTYPE_MODE_ANY)
- .fillAuxiliaryImes(imis, context);
- }
- return builder.build();
- }
-
- static ArrayList<InputMethodInfo> getDefaultEnabledImes(
- Context context, ArrayList<InputMethodInfo> imis) {
- return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
- }
-
- /**
- * Chooses an eligible system voice IME from the given IMEs.
- *
- * @param methodMap Map from the IME ID to {@link InputMethodInfo}.
- * @param systemSpeechRecognizerPackageName System speech recognizer configured by the system
- * config.
- * @param currentDefaultVoiceImeId IME ID currently set to
- * {@link Settings.Secure#DEFAULT_VOICE_INPUT_METHOD}
- * @return {@link InputMethodInfo} that is found in {@code methodMap} and most suitable for
- * the system voice IME.
- */
- @Nullable
- static InputMethodInfo chooseSystemVoiceIme(
- @NonNull ArrayMap<String, InputMethodInfo> methodMap,
- @Nullable String systemSpeechRecognizerPackageName,
- @Nullable String currentDefaultVoiceImeId) {
- if (TextUtils.isEmpty(systemSpeechRecognizerPackageName)) {
- return null;
- }
- final InputMethodInfo defaultVoiceIme = methodMap.get(currentDefaultVoiceImeId);
- // If the config matches the package of the setting, use the current one.
- if (defaultVoiceIme != null && defaultVoiceIme.isSystem()
- && defaultVoiceIme.getPackageName().equals(systemSpeechRecognizerPackageName)) {
- return defaultVoiceIme;
- }
- InputMethodInfo firstMatchingIme = null;
- final int methodCount = methodMap.size();
- for (int i = 0; i < methodCount; ++i) {
- final InputMethodInfo imi = methodMap.valueAt(i);
- if (!imi.isSystem()) {
- continue;
- }
- if (!TextUtils.equals(imi.getPackageName(), systemSpeechRecognizerPackageName)) {
- continue;
- }
- if (firstMatchingIme != null) {
- Slog.e(TAG, "At most one InputMethodService can be published in "
- + "systemSpeechRecognizer: " + systemSpeechRecognizerPackageName
- + ". Ignoring all of them.");
- return null;
- }
- firstMatchingIme = imi;
- }
- return firstMatchingIme;
- }
-
- static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale,
- boolean checkCountry, String mode) {
- if (locale == null) {
- return false;
- }
- final int N = imi.getSubtypeCount();
- for (int i = 0; i < N; ++i) {
- final InputMethodSubtype subtype = imi.getSubtypeAt(i);
- if (checkCountry) {
- final Locale subtypeLocale = subtype.getLocaleObject();
- if (subtypeLocale == null ||
- !TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage()) ||
- !TextUtils.equals(subtypeLocale.getCountry(), locale.getCountry())) {
- continue;
- }
- } else {
- final Locale subtypeLocale = new Locale(getLanguageFromLocaleString(
- subtype.getLocale()));
- if (!TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage())) {
- continue;
- }
- }
- if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) ||
- mode.equalsIgnoreCase(subtype.getMode())) {
- return true;
- }
- }
- return false;
- }
-
- static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
- ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
- final int subtypeCount = imi.getSubtypeCount();
- for (int i = 0; i < subtypeCount; ++i) {
- subtypes.add(imi.getSubtypeAt(i));
- }
- return subtypes;
- }
-
- static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
- if (enabledImes == null || enabledImes.isEmpty()) {
- return null;
- }
- // We'd prefer to fall back on a system IME, since that is safer.
- int i = enabledImes.size();
- int firstFoundSystemIme = -1;
- while (i > 0) {
- i--;
- final InputMethodInfo imi = enabledImes.get(i);
- if (imi.isAuxiliaryIme()) {
- continue;
- }
- if (imi.isSystem() && containsSubtypeOf(
- imi, ENGLISH_LOCALE, false /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
- return imi;
- }
- if (firstFoundSystemIme < 0 && imi.isSystem()) {
- firstFoundSystemIme = i;
- }
- }
- return enabledImes.get(Math.max(firstFoundSystemIme, 0));
- }
-
- static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
- return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
- }
-
- static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
- if (imi != null) {
- final int subtypeCount = imi.getSubtypeCount();
- for (int i = 0; i < subtypeCount; ++i) {
- InputMethodSubtype ims = imi.getSubtypeAt(i);
- if (subtypeHashCode == ims.hashCode()) {
- return i;
- }
- }
- }
- return NOT_A_SUBTYPE_ID;
- }
-
- private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale =
- new LocaleUtils.LocaleExtractor<InputMethodSubtype>() {
- @Override
- public Locale get(InputMethodSubtype source) {
- return source != null ? source.getLocaleObject() : null;
- }
- };
-
- @VisibleForTesting
- static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
- Resources res, InputMethodInfo imi) {
- final LocaleList systemLocales = res.getConfiguration().getLocales();
-
- synchronized (sCacheLock) {
- // We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because
- // it does not check if subtypes are also identical.
- if (systemLocales.equals(sCachedSystemLocales) && sCachedInputMethodInfo == imi) {
- return new ArrayList<>(sCachedResult);
- }
- }
-
- // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesLockedImpl().
- // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive
- // LocaleList rather than Resource.
- final ArrayList<InputMethodSubtype> result =
- getImplicitlyApplicableSubtypesLockedImpl(res, imi);
- synchronized (sCacheLock) {
- // Both LocaleList and InputMethodInfo are immutable. No need to copy them here.
- sCachedSystemLocales = systemLocales;
- sCachedInputMethodInfo = imi;
- sCachedResult = new ArrayList<>(result);
- }
- return result;
- }
-
- private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl(
- Resources res, InputMethodInfo imi) {
- final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
- final LocaleList systemLocales = res.getConfiguration().getLocales();
- final String systemLocale = systemLocales.get(0).toString();
- if (TextUtils.isEmpty(systemLocale)) return new ArrayList<>();
- final int numSubtypes = subtypes.size();
-
- // Handle overridesImplicitlyEnabledSubtype mechanism.
- final ArrayMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new ArrayMap<>();
- for (int i = 0; i < numSubtypes; ++i) {
- // scan overriding implicitly enabled subtypes.
- final InputMethodSubtype subtype = subtypes.get(i);
- if (subtype.overridesImplicitlyEnabledSubtype()) {
- final String mode = subtype.getMode();
- if (!applicableModeAndSubtypesMap.containsKey(mode)) {
- applicableModeAndSubtypesMap.put(mode, subtype);
- }
- }
- }
- if (applicableModeAndSubtypesMap.size() > 0) {
- return new ArrayList<>(applicableModeAndSubtypesMap.values());
- }
-
- final ArrayMap<String, ArrayList<InputMethodSubtype>> nonKeyboardSubtypesMap =
- new ArrayMap<>();
- final ArrayList<InputMethodSubtype> keyboardSubtypes = new ArrayList<>();
-
- for (int i = 0; i < numSubtypes; ++i) {
- final InputMethodSubtype subtype = subtypes.get(i);
- final String mode = subtype.getMode();
- if (SUBTYPE_MODE_KEYBOARD.equals(mode)) {
- keyboardSubtypes.add(subtype);
- } else {
- if (!nonKeyboardSubtypesMap.containsKey(mode)) {
- nonKeyboardSubtypesMap.put(mode, new ArrayList<>());
- }
- nonKeyboardSubtypesMap.get(mode).add(subtype);
- }
- }
-
- final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<>();
- LocaleUtils.filterByLanguage(keyboardSubtypes, sSubtypeToLocale, systemLocales,
- applicableSubtypes);
-
- if (!applicableSubtypes.isEmpty()) {
- boolean hasAsciiCapableKeyboard = false;
- final int numApplicationSubtypes = applicableSubtypes.size();
- for (int i = 0; i < numApplicationSubtypes; ++i) {
- final InputMethodSubtype subtype = applicableSubtypes.get(i);
- if (subtype.isAsciiCapable()) {
- hasAsciiCapableKeyboard = true;
- break;
- }
- }
- if (!hasAsciiCapableKeyboard) {
- final int numKeyboardSubtypes = keyboardSubtypes.size();
- for (int i = 0; i < numKeyboardSubtypes; ++i) {
- final InputMethodSubtype subtype = keyboardSubtypes.get(i);
- final String mode = subtype.getMode();
- if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
- TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
- applicableSubtypes.add(subtype);
- }
- }
- }
- }
-
- if (applicableSubtypes.isEmpty()) {
- InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
- res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
- if (lastResortKeyboardSubtype != null) {
- applicableSubtypes.add(lastResortKeyboardSubtype);
- }
- }
-
- // For each non-keyboard mode, extract subtypes with system locales.
- for (final ArrayList<InputMethodSubtype> subtypeList : nonKeyboardSubtypesMap.values()) {
- LocaleUtils.filterByLanguage(subtypeList, sSubtypeToLocale, systemLocales,
- applicableSubtypes);
- }
-
- return applicableSubtypes;
- }
-
- /**
- * Returns the language component of a given locale string.
- * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)}
- */
- private static String getLanguageFromLocaleString(String locale) {
- final int idx = locale.indexOf('_');
- if (idx < 0) {
- return locale;
- } else {
- return locale.substring(0, idx);
- }
- }
-
- /**
- * If there are no selected subtypes, tries finding the most applicable one according to the
- * given locale.
- * @param subtypes this function will search the most applicable subtype in subtypes
- * @param mode subtypes will be filtered by mode
- * @param locale subtypes will be filtered by locale
- * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
- * it will return the first subtype matched with mode
- * @return the most applicable subtypeId
- */
- static InputMethodSubtype findLastResortApplicableSubtypeLocked(
- Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
- boolean canIgnoreLocaleAsLastResort) {
- if (subtypes == null || subtypes.size() == 0) {
- return null;
- }
- if (TextUtils.isEmpty(locale)) {
- locale = res.getConfiguration().locale.toString();
- }
- final String language = getLanguageFromLocaleString(locale);
- boolean partialMatchFound = false;
- InputMethodSubtype applicableSubtype = null;
- InputMethodSubtype firstMatchedModeSubtype = null;
- final int N = subtypes.size();
- for (int i = 0; i < N; ++i) {
- InputMethodSubtype subtype = subtypes.get(i);
- final String subtypeLocale = subtype.getLocale();
- final String subtypeLanguage = getLanguageFromLocaleString(subtypeLocale);
- // An applicable subtype should match "mode". If mode is null, mode will be ignored,
- // and all subtypes with all modes can be candidates.
- if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
- if (firstMatchedModeSubtype == null) {
- firstMatchedModeSubtype = subtype;
- }
- if (locale.equals(subtypeLocale)) {
- // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
- applicableSubtype = subtype;
- break;
- } else if (!partialMatchFound && language.equals(subtypeLanguage)) {
- // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
- applicableSubtype = subtype;
- partialMatchFound = true;
- }
- }
- }
-
- if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
- return firstMatchedModeSubtype;
- }
-
- // The first subtype applicable to the system locale will be defined as the most applicable
- // subtype.
- if (DEBUG) {
- if (applicableSubtype != null) {
- Slog.d(TAG, "Applicable InputMethodSubtype was found: "
- + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
- }
- }
- return applicableSubtype;
- }
-
static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
if (subtype == null) return true;
return !subtype.isAuxiliary();
@@ -790,6 +231,7 @@ final class InputMethodUtils {
/**
* Utility class for putting and getting settings for InputMethod
* TODO: Move all putters and getters of settings to this class.
+ * TODO(b/235661780): Make the setting supports multi-users.
*/
public static class InputMethodSettings {
private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
@@ -967,7 +409,7 @@ final class InputMethodUtils {
List<InputMethodSubtype> enabledSubtypes =
getEnabledInputMethodSubtypeListLocked(imi);
if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
- enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
context.getResources(), imi);
}
return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
@@ -1198,7 +640,7 @@ final class InputMethodUtils {
// are enabled implicitly, so needs to treat them to be enabled.
if (imi != null && imi.getSubtypeCount() > 0) {
List<InputMethodSubtype> implicitlySelectedSubtypes =
- getImplicitlyApplicableSubtypesLocked(mRes, imi);
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(mRes, imi);
if (implicitlySelectedSubtypes != null) {
final int N = implicitlySelectedSubtypes.size();
for (int i = 0; i < N; ++i) {
@@ -1216,7 +658,7 @@ final class InputMethodUtils {
try {
final int hashCode = Integer.parseInt(subtypeHashCode);
// Check whether the subtype id is valid or not
- if (isValidSubtypeId(imi, hashCode)) {
+ if (SubtypeUtils.isValidSubtypeId(imi, hashCode)) {
return s;
} else {
return NOT_A_SUBTYPE_ID_STR;
@@ -1336,7 +778,7 @@ final class InputMethodUtils {
return NOT_A_SUBTYPE_ID;
}
final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
- return getSubtypeIdFromHashCode(imi, subtypeHashCode);
+ return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode);
}
void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
diff --git a/services/core/java/com/android/server/inputmethod/LocaleUtils.java b/services/core/java/com/android/server/inputmethod/LocaleUtils.java
index 7a6853a25e5b..3d02b3af6bc1 100644
--- a/services/core/java/com/android/server/inputmethod/LocaleUtils.java
+++ b/services/core/java/com/android/server/inputmethod/LocaleUtils.java
@@ -19,6 +19,8 @@ package com.android.server.inputmethod;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
import android.icu.util.ULocale;
import android.os.LocaleList;
import android.text.TextUtils;
@@ -207,4 +209,25 @@ final class LocaleUtils {
dest.add(sources.get(entry.mIndex));
}
}
+
+ /**
+ * Returns the language component of a given locale string.
+ * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)}
+ */
+ static String getLanguageFromLocaleString(String locale) {
+ final int idx = locale.indexOf('_');
+ if (idx < 0) {
+ return locale;
+ } else {
+ return locale.substring(0, idx);
+ }
+ }
+
+ static Locale getSystemLocaleFromContext(Context context) {
+ try {
+ return context.getResources().getConfiguration().locale;
+ } catch (Resources.NotFoundException ex) {
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/SubtypeUtils.java b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
new file mode 100644
index 000000000000..eb85dd011288
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/SubtypeUtils.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.os.LocaleList;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class provides utility methods to handle and manage {@link InputMethodSubtype} for
+ * {@link InputMethodManagerService}.
+ *
+ * <p>This class is intentionally package-private. Utility methods here are tightly coupled with
+ * implementation details in {@link InputMethodManagerService}. Hence this class is not suitable
+ * for other components to directly use.</p>
+ */
+final class SubtypeUtils {
+ private static final String TAG = "SubtypeUtils";
+ public static final boolean DEBUG = false;
+
+ static final String SUBTYPE_MODE_ANY = null;
+ static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+
+ static final int NOT_A_SUBTYPE_ID = -1;
+ private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
+ "EnabledWhenDefaultIsNotAsciiCapable";
+
+ // A temporary workaround for the performance concerns in
+ // #getImplicitlyApplicableSubtypesLocked(Resources, InputMethodInfo).
+ // TODO: Optimize all the critical paths including this one.
+ // TODO(b/235661780): Make the cache supports multi-users.
+ private static final Object sCacheLock = new Object();
+ @GuardedBy("sCacheLock")
+ private static LocaleList sCachedSystemLocales;
+ @GuardedBy("sCacheLock")
+ private static InputMethodInfo sCachedInputMethodInfo;
+ @GuardedBy("sCacheLock")
+ private static ArrayList<InputMethodSubtype> sCachedResult;
+
+ static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale,
+ boolean checkCountry, String mode) {
+ if (locale == null) {
+ return false;
+ }
+ final int N = imi.getSubtypeCount();
+ for (int i = 0; i < N; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ if (checkCountry) {
+ final Locale subtypeLocale = subtype.getLocaleObject();
+ if (subtypeLocale == null ||
+ !TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage()) ||
+ !TextUtils.equals(subtypeLocale.getCountry(), locale.getCountry())) {
+ continue;
+ }
+ } else {
+ final Locale subtypeLocale = new Locale(LocaleUtils.getLanguageFromLocaleString(
+ subtype.getLocale()));
+ if (!TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage())) {
+ continue;
+ }
+ }
+ if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) ||
+ mode.equalsIgnoreCase(subtype.getMode())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
+ ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ subtypes.add(imi.getSubtypeAt(i));
+ }
+ return subtypes;
+ }
+
+ static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
+ return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
+ }
+
+ static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
+ if (imi != null) {
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ InputMethodSubtype ims = imi.getSubtypeAt(i);
+ if (subtypeHashCode == ims.hashCode()) {
+ return i;
+ }
+ }
+ }
+ return NOT_A_SUBTYPE_ID;
+ }
+
+ private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale =
+ source -> source != null ? source.getLocaleObject() : null;
+
+ @VisibleForTesting
+ static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
+ Resources res, InputMethodInfo imi) {
+ final LocaleList systemLocales = res.getConfiguration().getLocales();
+
+ synchronized (sCacheLock) {
+ // We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because
+ // it does not check if subtypes are also identical.
+ if (systemLocales.equals(sCachedSystemLocales) && sCachedInputMethodInfo == imi) {
+ return new ArrayList<>(sCachedResult);
+ }
+ }
+
+ // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesLockedImpl().
+ // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive
+ // LocaleList rather than Resource.
+ final ArrayList<InputMethodSubtype> result =
+ getImplicitlyApplicableSubtypesLockedImpl(res, imi);
+ synchronized (sCacheLock) {
+ // Both LocaleList and InputMethodInfo are immutable. No need to copy them here.
+ sCachedSystemLocales = systemLocales;
+ sCachedInputMethodInfo = imi;
+ sCachedResult = new ArrayList<>(result);
+ }
+ return result;
+ }
+
+ private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl(
+ Resources res, InputMethodInfo imi) {
+ final List<InputMethodSubtype> subtypes = getSubtypes(imi);
+ final LocaleList systemLocales = res.getConfiguration().getLocales();
+ final String systemLocale = systemLocales.get(0).toString();
+ if (TextUtils.isEmpty(systemLocale)) return new ArrayList<>();
+ final int numSubtypes = subtypes.size();
+
+ // Handle overridesImplicitlyEnabledSubtype mechanism.
+ final ArrayMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new ArrayMap<>();
+ for (int i = 0; i < numSubtypes; ++i) {
+ // scan overriding implicitly enabled subtypes.
+ final InputMethodSubtype subtype = subtypes.get(i);
+ if (subtype.overridesImplicitlyEnabledSubtype()) {
+ final String mode = subtype.getMode();
+ if (!applicableModeAndSubtypesMap.containsKey(mode)) {
+ applicableModeAndSubtypesMap.put(mode, subtype);
+ }
+ }
+ }
+ if (applicableModeAndSubtypesMap.size() > 0) {
+ return new ArrayList<>(applicableModeAndSubtypesMap.values());
+ }
+
+ final ArrayMap<String, ArrayList<InputMethodSubtype>> nonKeyboardSubtypesMap =
+ new ArrayMap<>();
+ final ArrayList<InputMethodSubtype> keyboardSubtypes = new ArrayList<>();
+
+ for (int i = 0; i < numSubtypes; ++i) {
+ final InputMethodSubtype subtype = subtypes.get(i);
+ final String mode = subtype.getMode();
+ if (SUBTYPE_MODE_KEYBOARD.equals(mode)) {
+ keyboardSubtypes.add(subtype);
+ } else {
+ if (!nonKeyboardSubtypesMap.containsKey(mode)) {
+ nonKeyboardSubtypesMap.put(mode, new ArrayList<>());
+ }
+ nonKeyboardSubtypesMap.get(mode).add(subtype);
+ }
+ }
+
+ final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<>();
+ LocaleUtils.filterByLanguage(keyboardSubtypes, sSubtypeToLocale, systemLocales,
+ applicableSubtypes);
+
+ if (!applicableSubtypes.isEmpty()) {
+ boolean hasAsciiCapableKeyboard = false;
+ final int numApplicationSubtypes = applicableSubtypes.size();
+ for (int i = 0; i < numApplicationSubtypes; ++i) {
+ final InputMethodSubtype subtype = applicableSubtypes.get(i);
+ if (subtype.isAsciiCapable()) {
+ hasAsciiCapableKeyboard = true;
+ break;
+ }
+ }
+ if (!hasAsciiCapableKeyboard) {
+ final int numKeyboardSubtypes = keyboardSubtypes.size();
+ for (int i = 0; i < numKeyboardSubtypes; ++i) {
+ final InputMethodSubtype subtype = keyboardSubtypes.get(i);
+ final String mode = subtype.getMode();
+ if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
+ TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
+ applicableSubtypes.add(subtype);
+ }
+ }
+ }
+ }
+
+ if (applicableSubtypes.isEmpty()) {
+ InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
+ res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
+ if (lastResortKeyboardSubtype != null) {
+ applicableSubtypes.add(lastResortKeyboardSubtype);
+ }
+ }
+
+ // For each non-keyboard mode, extract subtypes with system locales.
+ for (final ArrayList<InputMethodSubtype> subtypeList : nonKeyboardSubtypesMap.values()) {
+ LocaleUtils.filterByLanguage(subtypeList, sSubtypeToLocale, systemLocales,
+ applicableSubtypes);
+ }
+
+ return applicableSubtypes;
+ }
+
+ /**
+ * If there are no selected subtypes, tries finding the most applicable one according to the
+ * given locale.
+ * @param subtypes this function will search the most applicable subtype in subtypes
+ * @param mode subtypes will be filtered by mode
+ * @param locale subtypes will be filtered by locale
+ * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
+ * it will return the first subtype matched with mode
+ * @return the most applicable subtypeId
+ */
+ static InputMethodSubtype findLastResortApplicableSubtypeLocked(
+ Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
+ boolean canIgnoreLocaleAsLastResort) {
+ if (subtypes == null || subtypes.size() == 0) {
+ return null;
+ }
+ if (TextUtils.isEmpty(locale)) {
+ locale = res.getConfiguration().locale.toString();
+ }
+ final String language = LocaleUtils.getLanguageFromLocaleString(locale);
+ boolean partialMatchFound = false;
+ InputMethodSubtype applicableSubtype = null;
+ InputMethodSubtype firstMatchedModeSubtype = null;
+ final int N = subtypes.size();
+ for (int i = 0; i < N; ++i) {
+ InputMethodSubtype subtype = subtypes.get(i);
+ final String subtypeLocale = subtype.getLocale();
+ final String subtypeLanguage = LocaleUtils.getLanguageFromLocaleString(subtypeLocale);
+ // An applicable subtype should match "mode". If mode is null, mode will be ignored,
+ // and all subtypes with all modes can be candidates.
+ if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
+ if (firstMatchedModeSubtype == null) {
+ firstMatchedModeSubtype = subtype;
+ }
+ if (locale.equals(subtypeLocale)) {
+ // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
+ applicableSubtype = subtype;
+ break;
+ } else if (!partialMatchFound && language.equals(subtypeLanguage)) {
+ // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
+ applicableSubtype = subtype;
+ partialMatchFound = true;
+ }
+ }
+ }
+
+ if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
+ return firstMatchedModeSubtype;
+ }
+
+ // The first subtype applicable to the system locale will be defined as the most applicable
+ // subtype.
+ if (DEBUG) {
+ if (applicableSubtype != null) {
+ Slog.d(TAG, "Applicable InputMethodSubtype was found: "
+ + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
+ }
+ }
+ return applicableSubtype;
+ }
+}
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/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
index 881f8707fdd8..661161f05d23 100644
--- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
+++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java
@@ -33,6 +33,7 @@ import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.PermissionThread;
/**
* Class that handles one-time permissions for a user
@@ -79,7 +80,8 @@ public class OneTimePermissionUserManager {
mContext = context;
mActivityManager = context.getSystemService(ActivityManager.class);
mAlarmManager = context.getSystemService(AlarmManager.class);
- mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class);
+ mPermissionControllerManager = new PermissionControllerManager(
+ mContext, PermissionThread.getHandler());
mHandler = context.getMainThreadHandler();
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index ccd906646d60..7d4cfdff2bfa 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -122,6 +122,7 @@ import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.PermissionThread;
import com.android.server.ServiceThread;
import com.android.server.SystemConfig;
import com.android.server.Watchdog;
@@ -2004,7 +2005,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
Preconditions.checkArgumentNonNegative(userId, "userId");
CompletableFuture<byte[]> backup = new CompletableFuture<>();
mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
- mContext.getMainExecutor(), backup::complete);
+ PermissionThread.getExecutor(), backup::complete);
try {
return backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
@@ -2055,7 +2056,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
}
}
mPermissionControllerManager.applyStagedRuntimePermissionBackup(packageName,
- UserHandle.of(userId), mContext.getMainExecutor(), (hasMoreBackup) -> {
+ UserHandle.of(userId), PermissionThread.getExecutor(), (hasMoreBackup) -> {
if (hasMoreBackup) {
return;
}
@@ -4443,9 +4444,9 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
}
}
- mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
- mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class);
- }
+ mPermissionControllerManager = new PermissionControllerManager(
+ mContext, PermissionThread.getHandler());
+ mPermissionPolicyInternal = LocalServices.getService(PermissionPolicyInternal.class); }
private static String getVolumeUuidForPackage(AndroidPackage pkg) {
if (pkg == null) {
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index b56e1120f16a..9c9576984820 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -92,6 +92,7 @@ import com.android.internal.util.IntPair;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.PermissionThread;
import com.android.server.SystemService;
import com.android.server.notification.NotificationManagerInternal;
import com.android.server.pm.UserManagerInternal;
@@ -335,7 +336,7 @@ public final class PermissionPolicyService extends SystemService {
PermissionControllerManager manager = mPermControllerManagers.get(user);
if (manager == null) {
manager = new PermissionControllerManager(
- getUserContext(getContext(), user), FgThread.getHandler());
+ getUserContext(getContext(), user), PermissionThread.getHandler());
mPermControllerManagers.put(user, manager);
}
manager.updateUserSensitiveForApp(uid);
@@ -343,8 +344,9 @@ public final class PermissionPolicyService extends SystemService {
}, UserHandle.ALL, intentFilter, null, null);
PermissionControllerManager manager = new PermissionControllerManager(
- getUserContext(getContext(), Process.myUserHandle()), FgThread.getHandler());
- FgThread.getHandler().postDelayed(manager::updateUserSensitive,
+ getUserContext(getContext(), Process.myUserHandle()),
+ PermissionThread.getHandler());
+ PermissionThread.getHandler().postDelayed(manager::updateUserSensitive,
USER_SENSITIVE_UPDATE_DELAY_MS);
}
@@ -371,6 +373,11 @@ public final class PermissionPolicyService extends SystemService {
if (isStarted(changedUserId)) {
synchronized (mLock) {
if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) {
+ // TODO(b/165030092): migrate this to PermissionThread.getHandler().
+ // synchronizePackagePermissionsAndAppOpsForUser is a heavy operation.
+ // Dispatched on a PermissionThread, it interferes with user switch.
+ // FgThread is busy and schedules it after most of the switch is done.
+ // A possible solution is to delay the callback.
FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
PermissionPolicyService
::synchronizePackagePermissionsAndAppOpsForUser,
@@ -584,9 +591,9 @@ public final class PermissionPolicyService extends SystemService {
final PermissionControllerManager permissionControllerManager =
new PermissionControllerManager(
getUserContext(getContext(), UserHandle.of(userId)),
- FgThread.getHandler());
+ PermissionThread.getHandler());
permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions(
- FgThread.getExecutor(), successful -> {
+ PermissionThread.getExecutor(), successful -> {
if (successful) {
future.complete(null);
} else {
@@ -690,7 +697,7 @@ public final class PermissionPolicyService extends SystemService {
synchronized (mLock) {
if (!mIsUidSyncScheduled.get(uid)) {
mIsUidSyncScheduled.put(uid, true);
- FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
+ PermissionThread.getHandler().sendMessage(PooledLambda.obtainMessage(
PermissionPolicyService::resetAppOpPermissionsIfNotRequestedForUid,
this, uid));
}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 97a57e066fc7..b79ac6f68be2 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -64,7 +64,6 @@ public class KeyguardServiceDelegate {
reset();
}
boolean showing;
- boolean showingAndNotOccluded;
boolean inputRestricted;
volatile boolean occluded;
boolean secure;
@@ -83,7 +82,7 @@ public class KeyguardServiceDelegate {
// the event something checks before the service is actually started.
// KeyguardService itself should default to this state until the real state is known.
showing = true;
- showingAndNotOccluded = true;
+ occluded = false;
secure = true;
deviceHasKeyguard = true;
enabled = true;
@@ -148,7 +147,6 @@ public class KeyguardServiceDelegate {
Context.BIND_AUTO_CREATE, mHandler, UserHandle.SYSTEM)) {
Log.v(TAG, "*** Keyguard: can't bind to " + keyguardComponent);
mKeyguardState.showing = false;
- mKeyguardState.showingAndNotOccluded = false;
mKeyguardState.secure = false;
synchronized (mKeyguardState) {
// TODO: Fix synchronisation model in this class. The other state in this class
@@ -440,7 +438,6 @@ public class KeyguardServiceDelegate {
pw.println(prefix + TAG);
prefix += " ";
pw.println(prefix + "showing=" + mKeyguardState.showing);
- pw.println(prefix + "showingAndNotOccluded=" + mKeyguardState.showingAndNotOccluded);
pw.println(prefix + "inputRestricted=" + mKeyguardState.inputRestricted);
pw.println(prefix + "occluded=" + mKeyguardState.occluded);
pw.println(prefix + "secure=" + mKeyguardState.secure);
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index b6a413524c5c..452bdf409828 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -189,6 +189,7 @@ public class TestHarnessModeService extends SystemService {
if (adbManager.getAdbTempKeysFile() != null) {
writeBytesToFile(persistentData.mAdbTempKeys, adbManager.getAdbTempKeysFile().toPath());
}
+ adbManager.notifyKeyFilesUpdated();
}
private void configureUser() {
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 78b1c20ac4b2..d79837be3583 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -61,12 +61,11 @@ final class Vibration {
IGNORED_BACKGROUND,
IGNORED_UNKNOWN_VIBRATION,
IGNORED_UNSUPPORTED,
- IGNORED_FOR_ALARM,
IGNORED_FOR_EXTERNAL,
+ IGNORED_FOR_HIGHER_IMPORTANCE,
IGNORED_FOR_ONGOING,
IGNORED_FOR_POWER,
IGNORED_FOR_RINGER_MODE,
- IGNORED_FOR_RINGTONE,
IGNORED_FOR_SETTINGS,
IGNORED_SUPERSEDED,
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index f0911ca62027..5ac2f4f27452 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -713,14 +713,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
case IGNORED_ERROR_APP_OPS:
Slog.w(TAG, "Would be an error: vibrate from uid " + uid);
break;
- case IGNORED_FOR_ALARM:
+ case IGNORED_FOR_EXTERNAL:
if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
+ Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
}
break;
- case IGNORED_FOR_EXTERNAL:
+ case IGNORED_FOR_HIGHER_IMPORTANCE:
if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
+ Slog.d(TAG, "Ignoring incoming vibration in favor of ongoing vibration"
+ + " with higher importance");
}
break;
case IGNORED_FOR_ONGOING:
@@ -734,12 +735,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
+ attrs);
}
break;
- case IGNORED_FOR_RINGTONE:
- if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of ringtone vibration");
- }
- break;
-
default:
if (DEBUG) {
Slog.d(TAG, "Vibration for uid=" + uid + " and with attrs=" + attrs
@@ -812,20 +807,43 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return null;
}
- if (currentVibration.attrs.getUsage() == VibrationAttributes.USAGE_ALARM) {
- return Vibration.Status.IGNORED_FOR_ALARM;
- }
-
- if (currentVibration.attrs.getUsage() == VibrationAttributes.USAGE_RINGTONE) {
- return Vibration.Status.IGNORED_FOR_RINGTONE;
+ int currentUsage = currentVibration.attrs.getUsage();
+ int newUsage = vib.attrs.getUsage();
+ if (getVibrationImportance(currentUsage) > getVibrationImportance(newUsage)) {
+ // Current vibration has higher importance than this one and should not be cancelled.
+ return Vibration.Status.IGNORED_FOR_HIGHER_IMPORTANCE;
}
if (currentVibration.isRepeating()) {
+ // Current vibration is repeating, assume it's more important.
return Vibration.Status.IGNORED_FOR_ONGOING;
}
+
return null;
}
+ private static int getVibrationImportance(@VibrationAttributes.Usage int usage) {
+ switch (usage) {
+ case VibrationAttributes.USAGE_RINGTONE:
+ return 5;
+ case VibrationAttributes.USAGE_ALARM:
+ return 4;
+ case VibrationAttributes.USAGE_NOTIFICATION:
+ return 3;
+ case VibrationAttributes.USAGE_COMMUNICATION_REQUEST:
+ case VibrationAttributes.USAGE_ACCESSIBILITY:
+ return 2;
+ case VibrationAttributes.USAGE_HARDWARE_FEEDBACK:
+ case VibrationAttributes.USAGE_PHYSICAL_EMULATION:
+ return 1;
+ case VibrationAttributes.USAGE_MEDIA:
+ case VibrationAttributes.USAGE_TOUCH:
+ case VibrationAttributes.USAGE_UNKNOWN:
+ default:
+ return 0;
+ }
+ }
+
/**
* Check if given vibration should be ignored by this service.
*
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0350dfcd8230..b70f7a0fd213 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();
@@ -8174,7 +8182,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
resolvedBounds.set(containingBounds);
final float letterboxAspectRatioOverride =
- mWmService.mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+ mLetterboxUiController.getFixedOrientationLetterboxAspectRatio();
final float desiredAspectRatio =
letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
? letterboxAspectRatioOverride : computeAspectRatio(parentBounds);
@@ -8727,18 +8735,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/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index e7ce1b5a4c91..963345f2f49f 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;
@@ -737,14 +736,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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index afcb21a45106..68f60152ef95 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/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index f2d4d5427291..475dd17ef727 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -367,10 +367,15 @@ class DragState {
mDragWindowHandle.ownerUid = MY_UID;
mDragWindowHandle.scaleFactor = 1.0f;
- // Keep the default behavior of this window to be focusable, which allows the system
- // to consume keys when dragging is active. This can also be used to modify the drag
- // state on key press. For example, cancel drag on escape key.
- mDragWindowHandle.inputConfig = InputConfig.PREVENT_SPLITTING;
+ // InputConfig.PREVENT_SPLITTING: To keep the default behavior of this window to be
+ // focusable, which allows the system to consume keys when dragging is active. This can
+ // also be used to modify the drag state on key press. For example, cancel drag on
+ // escape key.
+ // InputConfig.TRUSTED_OVERLAY: To not block any touches while D&D ongoing and allowing
+ // touches to pass through to windows underneath. This allows user to interact with the
+ // UI to navigate while dragging.
+ mDragWindowHandle.inputConfig =
+ InputConfig.PREVENT_SPLITTING | InputConfig.TRUSTED_OVERLAY;
// The drag window cannot receive new touches.
mDragWindowHandle.touchableRegion.setEmpty();
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 2d227b66b3ce..08715b160b9a 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;
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index df9a87ea1ab0..f849d2886ba1 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -28,6 +28,7 @@ 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.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString;
import android.annotation.Nullable;
@@ -211,10 +212,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 +236,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);
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/SurfaceAnimationThread.java b/services/core/java/com/android/server/wm/SurfaceAnimationThread.java
index 1259ee901e4c..8ea715c4084e 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationThread.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationThread.java
@@ -40,7 +40,7 @@ public final class SurfaceAnimationThread extends ServiceThread {
sInstance = new SurfaceAnimationThread();
sInstance.start();
sInstance.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER);
- sHandler = new Handler(sInstance.getLooper());
+ sHandler = makeSharedHandler(sInstance.getLooper());
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9f11763c4780..9d5b9455da13 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -89,6 +89,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;
@@ -121,6 +122,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
+import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT_METHOD;
@@ -1438,7 +1440,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
@@ -1853,6 +1855,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);
@@ -2205,6 +2214,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,
@@ -2221,6 +2244,11 @@ public class WindowManagerService extends IWindowManager.Stub
if (win == null) {
return 0;
}
+
+ if (win.cancelAndRedraw()) {
+ result |= RELAYOUT_RES_CANCEL_AND_REDRAW;
+ }
+
final DisplayContent displayContent = win.getDisplayContent();
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
@@ -2523,6 +2551,11 @@ public class WindowManagerService extends IWindowManager.Stub
win.mLastSeqIdSentToRelayout = win.mSyncSeqId;
outSyncIdBundle.putInt("seqid", win.mSyncSeqId);
+ // Only mark mAlreadyRequestedSync if there's an explicit sync request, and not if
+ // we're syncing due to mDrawHandlers
+ if (win.mSyncState != SYNC_STATE_NONE) {
+ win.mAlreadyRequestedSync = true;
+ }
} else {
outSyncIdBundle.putInt("seqid", -1);
}
@@ -2635,29 +2668,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();
@@ -8762,6 +8772,41 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ /**
+ * Toggle active transaction tracing.
+ * Setting to true increases the buffer size for active debugging.
+ * Setting to false resets the buffer size and dumps the trace to file.
+ */
+ public void setActiveTransactionTracing(boolean active) {
+ if (!checkCallingPermission(
+ android.Manifest.permission.DUMP, "setActiveTransactionTracing()")) {
+ throw new SecurityException("Requires DUMP permission");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Parcel data = null;
+ try {
+ IBinder sf = ServiceManager.getService("SurfaceFlinger");
+ if (sf != null) {
+ data = Parcel.obtain();
+ data.writeInterfaceToken("android.ui.ISurfaceComposer");
+ data.writeInt(active ? 1 : 0);
+ sf.transact(/* TRANSACTION_TRACE_CONTROL_CODE */ 1041, data,
+ null, 0 /* flags */);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set transaction tracing");
+ } finally {
+ if (data != null) {
+ data.recycle();
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
public boolean mirrorDisplay(int displayId, SurfaceControl outSurfaceControl) {
if (!checkCallingPermission(READ_FRAME_BUFFER, "mirrorDisplay()")) {
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..6728e63d055f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -391,6 +391,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
int mSyncSeqId = 0;
int mLastSeqIdSentToRelayout = 0;
+ boolean mAlreadyRequestedSync;
/**
* {@code true} when the client was still drawing for sync when the sync-set was finished or
@@ -1247,7 +1248,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mSession.windowAddedLocked();
}
- boolean updateGlobalScale() {
+ void updateGlobalScale() {
if (hasCompatScale()) {
if (mOverrideScale != 1f) {
mGlobalScale = mToken.hasSizeCompatBounds()
@@ -1257,11 +1258,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mGlobalScale = mToken.getSizeCompatScale();
}
mInvGlobalScale = 1f / mGlobalScale;
- return true;
+ return;
}
mGlobalScale = mInvGlobalScale = 1f;
- return false;
}
/**
@@ -1514,23 +1514,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 +1586,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 +3841,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 +3898,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 +4420,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
pw.println(prefix + "Requested visibilities: " + visibilityString);
}
}
+
+ pw.println(prefix + "mAlreadyRequestedSync=" + mAlreadyRequestedSync);
}
@Override
@@ -5933,6 +5949,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) {
mClientWasDrawingForSync = true;
}
+ mAlreadyRequestedSync = false;
super.finishSync(outMergedTransaction, cancel);
}
@@ -6199,4 +6216,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@WindowTraceLogLevel int logLevel) {
dumpDebug(proto, fieldId, logLevel);
}
+
+ public boolean cancelAndRedraw() {
+ return mSyncState != SYNC_STATE_NONE && mAlreadyRequestedSync;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 0c69067ab131..222a96d88166 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -315,8 +315,6 @@ class ActiveAdmin {
public String mOrganizationId;
public String mEnrollmentSpecificId;
public boolean mAdminCanGrantSensorsPermissions;
- public boolean mPreferentialNetworkServiceEnabled =
- DevicePolicyManager.PREFERENTIAL_NETWORK_SERVICE_ENABLED_DEFAULT;
public List<PreferentialNetworkServiceConfig> mPreferentialNetworkServiceConfigs =
List.of(PreferentialNetworkServiceConfig.DEFAULT);
@@ -848,15 +846,15 @@ class ActiveAdmin {
} else if (TAG_ALWAYS_ON_VPN_LOCKDOWN.equals(tag)) {
mAlwaysOnVpnLockdown = parser.getAttributeBoolean(null, ATTR_VALUE, false);
} else if (TAG_PREFERENTIAL_NETWORK_SERVICE_ENABLED.equals(tag)) {
- mPreferentialNetworkServiceEnabled = parser.getAttributeBoolean(null, ATTR_VALUE,
+ boolean preferentialNetworkServiceEnabled = parser.getAttributeBoolean(null,
+ ATTR_VALUE,
DevicePolicyManager.PREFERENTIAL_NETWORK_SERVICE_ENABLED_DEFAULT);
- if (mPreferentialNetworkServiceEnabled) {
+ if (preferentialNetworkServiceEnabled) {
PreferentialNetworkServiceConfig.Builder configBuilder =
new PreferentialNetworkServiceConfig.Builder();
- configBuilder.setEnabled(mPreferentialNetworkServiceEnabled);
+ configBuilder.setEnabled(preferentialNetworkServiceEnabled);
configBuilder.setNetworkId(NET_ENTERPRISE_ID_1);
mPreferentialNetworkServiceConfigs = List.of(configBuilder.build());
- mPreferentialNetworkServiceEnabled = false;
}
} else if (TAG_COMMON_CRITERIA_MODE.equals(tag)) {
mCommonCriteriaMode = parser.getAttributeBoolean(null, ATTR_VALUE, false);
@@ -1274,9 +1272,6 @@ class ActiveAdmin {
pw.print("mAlwaysOnVpnLockdown=");
pw.println(mAlwaysOnVpnLockdown);
- pw.print("mPreferentialNetworkServiceEnabled=");
- pw.println(mPreferentialNetworkServiceEnabled);
-
pw.print("mCommonCriteriaMode=");
pw.println(mCommonCriteriaMode);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 76da21824494..3402262e34dd 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -409,7 +409,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/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
index 5db8867658be..de27d773504e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -204,7 +204,7 @@ public class SettingsToPropertiesMapperTest {
@Test
public void testUpdatePropertiesFromSettings_PropertyAndSettingNotPresent() {
- // Test that empty property will not not be set if setting is not set
+ // Test that empty property will not be set if setting is not set
mTestMapper.updatePropertiesFromSettings();
String propValue = mSystemSettingsMap.get("TestProperty");
Assert.assertNull("Property should not be set if setting is null", propValue);
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
index b36aa0617be5..e87dd4b423b2 100644
--- a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
@@ -36,8 +36,6 @@ import android.util.Log;
import androidx.test.InstrumentationRegistry;
-import com.android.server.FgThread;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -48,6 +46,11 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
@@ -88,6 +91,7 @@ public final class AdbDebuggingManagerTest {
private long mOriginalAllowedConnectionTime;
private File mAdbKeyXmlFile;
private File mAdbKeyFile;
+ private FakeTicker mFakeTicker;
@Before
public void setUp() throws Exception {
@@ -96,14 +100,25 @@ public final class AdbDebuggingManagerTest {
if (mAdbKeyFile.exists()) {
mAdbKeyFile.delete();
}
- mManager = new AdbDebuggingManager(mContext, ADB_CONFIRM_COMPONENT, mAdbKeyFile);
mAdbKeyXmlFile = new File(mContext.getFilesDir(), "test_adb_keys.xml");
if (mAdbKeyXmlFile.exists()) {
mAdbKeyXmlFile.delete();
}
+
+ mFakeTicker = new FakeTicker();
+ // Set the ticker time to October 22, 2008 (the day the T-Mobile G1 was released)
+ mFakeTicker.advance(1224658800L);
+
mThread = new AdbDebuggingThreadTest();
- mKeyStore = mManager.new AdbKeyStore(mAdbKeyXmlFile);
- mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper(), mThread, mKeyStore);
+ mManager = new AdbDebuggingManager(
+ mContext, ADB_CONFIRM_COMPONENT, mAdbKeyFile, mAdbKeyXmlFile, mThread, mFakeTicker);
+
+ mHandler = mManager.mHandler;
+ mThread.setHandler(mHandler);
+
+ mHandler.initKeyStore();
+ mKeyStore = mHandler.mAdbKeyStore;
+
mOriginalAllowedConnectionTime = mKeyStore.getAllowedConnectionTime();
mBlockingQueue = new ArrayBlockingQueue<>(1);
}
@@ -122,7 +137,7 @@ public final class AdbDebuggingManagerTest {
private void setAllowedConnectionTime(long connectionTime) {
Settings.Global.putLong(mContext.getContentResolver(),
Settings.Global.ADB_ALLOWED_CONNECTION_TIME, connectionTime);
- };
+ }
@Test
public void testAllowNewKeyOnce() throws Exception {
@@ -158,20 +173,15 @@ public final class AdbDebuggingManagerTest {
// Allow a connection from a new key with the 'Always allow' option selected.
runAdbTest(TEST_KEY_1, true, true, false);
- // Get the last connection time for the currently connected key to verify that it is updated
- // after the disconnect.
- long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
-
- // Sleep for a small amount of time to ensure a difference can be observed in the last
- // connection time after a disconnect.
- Thread.sleep(10);
+ // Advance the clock by 10ms to ensure there's a difference
+ mFakeTicker.advance(10 * 1_000_000);
// Send the disconnect message for the currently connected key to trigger an update of the
// last connection time.
disconnectKey(TEST_KEY_1);
- assertNotEquals(
+ assertEquals(
"The last connection time was not updated after the disconnect",
- lastConnectionTime,
+ mFakeTicker.currentTimeMillis(),
mKeyStore.getLastConnectionTime(TEST_KEY_1));
}
@@ -244,8 +254,8 @@ public final class AdbDebuggingManagerTest {
// Get the current last connection time for comparison after the scheduled job is run
long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- // Sleep a small amount of time to ensure that the updated connection time changes
- Thread.sleep(10);
+ // Advance a small amount of time to ensure that the updated connection time changes
+ mFakeTicker.advance(10);
// Send a message to the handler to update the last connection time for the active key
updateKeyStore();
@@ -269,13 +279,13 @@ public final class AdbDebuggingManagerTest {
persistKeyStore();
assertTrue(
"The key with the 'Always allow' option selected was not persisted in the keystore",
- mManager.new AdbKeyStore(mAdbKeyXmlFile).isKeyAuthorized(TEST_KEY_1));
+ mManager.new AdbKeyStore().isKeyAuthorized(TEST_KEY_1));
// Get the current last connection time to ensure it is updated in the persisted keystore.
long lastConnectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- // Sleep a small amount of time to ensure the last connection time is updated.
- Thread.sleep(10);
+ // Advance a small amount of time to ensure the last connection time is updated.
+ mFakeTicker.advance(10);
// Send a message to the handler to update the last connection time for the active key.
updateKeyStore();
@@ -286,7 +296,7 @@ public final class AdbDebuggingManagerTest {
assertNotEquals(
"The last connection time in the key file was not updated after the update "
+ "connection time message", lastConnectionTime,
- mManager.new AdbKeyStore(mAdbKeyXmlFile).getLastConnectionTime(TEST_KEY_1));
+ mManager.new AdbKeyStore().getLastConnectionTime(TEST_KEY_1));
// Verify that the key is in the adb_keys file
assertTrue("The key was not in the adb_keys file after persisting the keystore",
isKeyInFile(TEST_KEY_1, mAdbKeyFile));
@@ -327,8 +337,8 @@ public final class AdbDebuggingManagerTest {
// Set the allowed window to a small value to ensure the time is beyond the allowed window.
setAllowedConnectionTime(1);
- // Sleep for a small amount of time to exceed the allowed window.
- Thread.sleep(10);
+ // Advance a small amount of time to exceed the allowed window.
+ mFakeTicker.advance(10);
// The AdbKeyStore has a method to get the time of the next key expiration to ensure the
// scheduled job runs at the time of the next expiration or after 24 hours, whichever occurs
@@ -478,9 +488,12 @@ public final class AdbDebuggingManagerTest {
// Set the current expiration time to a minute from expiration and verify this new value is
// returned.
final long newExpirationTime = 60000;
- mKeyStore.setLastConnectionTime(TEST_KEY_1,
- System.currentTimeMillis() - Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME
- + newExpirationTime, true);
+ mKeyStore.setLastConnectionTime(
+ TEST_KEY_1,
+ mFakeTicker.currentTimeMillis()
+ - Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME
+ + newExpirationTime,
+ true);
expirationTime = mKeyStore.getNextExpirationTime();
if (Math.abs(expirationTime - newExpirationTime) > epsilon) {
fail("The expiration time for a key about to expire, " + expirationTime
@@ -525,7 +538,7 @@ public final class AdbDebuggingManagerTest {
// Get the last connection time for the key to verify that it is updated when the connected
// key message is sent.
long connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
mHandler.obtainMessage(AdbDebuggingManager.AdbDebuggingHandler.MESSAGE_ADB_CONNECTED_KEY,
TEST_KEY_1).sendToTarget();
flushHandlerQueue();
@@ -536,7 +549,7 @@ public final class AdbDebuggingManagerTest {
// Verify that the scheduled job updates the connection time of the key.
connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
updateKeyStore();
assertNotEquals(
"The connection time for the key must be updated when the update keystore message"
@@ -545,7 +558,7 @@ public final class AdbDebuggingManagerTest {
// Verify that the connection time is updated when the key is disconnected.
connectionTime = mKeyStore.getLastConnectionTime(TEST_KEY_1);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
disconnectKey(TEST_KEY_1);
assertNotEquals(
"The connection time for the key must be updated when the disconnected message is"
@@ -628,11 +641,11 @@ public final class AdbDebuggingManagerTest {
setAllowedConnectionTime(Settings.Global.DEFAULT_ADB_ALLOWED_CONNECTION_TIME);
// The untracked keys should be added to the keystore as part of the constructor.
- AdbDebuggingManager.AdbKeyStore adbKeyStore = mManager.new AdbKeyStore(mAdbKeyXmlFile);
+ AdbDebuggingManager.AdbKeyStore adbKeyStore = mManager.new AdbKeyStore();
// Verify that the connection time for each test key is within a small value of the current
// time.
- long time = System.currentTimeMillis();
+ long time = mFakeTicker.currentTimeMillis();
for (String key : testKeys) {
long connectionTime = adbKeyStore.getLastConnectionTime(key);
if (Math.abs(time - connectionTime) > epsilon) {
@@ -651,11 +664,11 @@ public final class AdbDebuggingManagerTest {
runAdbTest(TEST_KEY_1, true, true, false);
runAdbTest(TEST_KEY_2, true, true, false);
- // Sleep a small amount of time to ensure the connection time is updated by the scheduled
+ // Advance a small amount of time to ensure the connection time is updated by the scheduled
// job.
long connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1);
long connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
updateKeyStore();
assertNotEquals(
"The connection time for test key 1 must be updated after the scheduled job runs",
@@ -669,7 +682,7 @@ public final class AdbDebuggingManagerTest {
disconnectKey(TEST_KEY_2);
connectionTime1 = mKeyStore.getLastConnectionTime(TEST_KEY_1);
connectionTime2 = mKeyStore.getLastConnectionTime(TEST_KEY_2);
- Thread.sleep(10);
+ mFakeTicker.advance(10);
updateKeyStore();
assertNotEquals(
"The connection time for test key 1 must be updated after another key is "
@@ -686,8 +699,6 @@ public final class AdbDebuggingManagerTest {
// to clear the adb authorizations when adb is disabled after a boot a NullPointerException
// was thrown as deleteKeyStore is invoked against the key store. This test ensures the
// key store can be successfully cleared when adb is disabled.
- mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper());
-
clearKeyStore();
}
@@ -723,6 +734,9 @@ public final class AdbDebuggingManagerTest {
// Now remove one of the keys and make sure the other key is still there
mKeyStore.removeKey(TEST_KEY_1);
+ // Wait for the handler queue to receive the MESSAGE_ADB_PERSIST_KEYSTORE
+ flushHandlerQueue();
+
assertFalse("The key was still in the adb_keys file after removing the key",
isKeyInFile(TEST_KEY_1, mAdbKeyFile));
assertTrue("The key was not in the adb_keys file after removing a different key",
@@ -730,6 +744,95 @@ public final class AdbDebuggingManagerTest {
}
@Test
+ public void testAdbKeyStore_addDuplicateKey_doesNotAddDuplicateToAdbKeyFile() throws Exception {
+ setAllowedConnectionTime(0);
+
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+
+ assertEquals("adb_keys contains duplicate keys", 1, adbKeyFileKeys(mAdbKeyFile).size());
+ }
+
+ @Test
+ public void testAdbKeyStore_adbTempKeysFile_readsLastConnectionTimeFromXml() throws Exception {
+ long insertTime = mFakeTicker.currentTimeMillis();
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+
+ mFakeTicker.advance(10);
+ AdbDebuggingManager.AdbKeyStore newKeyStore = mManager.new AdbKeyStore();
+
+ assertEquals(
+ "KeyStore not populated from the XML file.",
+ insertTime,
+ newKeyStore.getLastConnectionTime(TEST_KEY_1));
+ }
+
+ @Test
+ public void test_notifyKeyFilesUpdated_filesDeletedRemovesPreviouslyAddedKey()
+ throws Exception {
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+
+ Files.delete(mAdbKeyXmlFile.toPath());
+ Files.delete(mAdbKeyFile.toPath());
+
+ mManager.notifyKeyFilesUpdated();
+ flushHandlerQueue();
+
+ assertFalse(
+ "Key is authorized after reloading deleted key files. Was state preserved?",
+ mKeyStore.isKeyAuthorized(TEST_KEY_1));
+ }
+
+ @Test
+ public void test_notifyKeyFilesUpdated_newKeyIsAuthorized() throws Exception {
+ runAdbTest(TEST_KEY_1, true, true, false);
+ persistKeyStore();
+
+ // Back up the existing key files
+ Path tempXmlFile = Files.createTempFile("adbKeyXmlFile", ".tmp");
+ Path tempAdbKeysFile = Files.createTempFile("adb_keys", ".tmp");
+ Files.copy(mAdbKeyXmlFile.toPath(), tempXmlFile, StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(mAdbKeyFile.toPath(), tempAdbKeysFile, StandardCopyOption.REPLACE_EXISTING);
+
+ // Delete the existing key files
+ Files.delete(mAdbKeyXmlFile.toPath());
+ Files.delete(mAdbKeyFile.toPath());
+
+ // Notify the manager that adb key files have changed.
+ mManager.notifyKeyFilesUpdated();
+ flushHandlerQueue();
+
+ // Copy the files back
+ Files.copy(tempXmlFile, mAdbKeyXmlFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ Files.copy(tempAdbKeysFile, mAdbKeyFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+
+ // Tell the manager that the key files have changed.
+ mManager.notifyKeyFilesUpdated();
+ flushHandlerQueue();
+
+ assertTrue(
+ "Key is not authorized after reloading key files.",
+ mKeyStore.isKeyAuthorized(TEST_KEY_1));
+ }
+
+ @Test
+ public void testAdbKeyStore_adbWifiConnect_storesBssidWhenAlwaysAllow() throws Exception {
+ String trustedNetwork = "My Network";
+ mKeyStore.addTrustedNetwork(trustedNetwork);
+ persistKeyStore();
+
+ AdbDebuggingManager.AdbKeyStore newKeyStore = mManager.new AdbKeyStore();
+
+ assertTrue(
+ "Persisted trusted network not found in new keystore instance.",
+ newKeyStore.isTrustedNetwork(trustedNetwork));
+ }
+
+ @Test
public void testIsValidMdnsServiceName() {
// Longer than 15 characters
assertFalse(isValidMdnsServiceName("abcd1234abcd1234"));
@@ -1030,28 +1133,27 @@ public final class AdbDebuggingManagerTest {
if (key == null) {
return false;
}
+ return adbKeyFileKeys(keyFile).contains(key);
+ }
+
+ private static List<String> adbKeyFileKeys(File keyFile) throws Exception {
+ List<String> keys = new ArrayList<>();
if (keyFile.exists()) {
try (BufferedReader in = new BufferedReader(new FileReader(keyFile))) {
String currKey;
while ((currKey = in.readLine()) != null) {
- if (key.equals(currKey)) {
- return true;
- }
+ keys.add(currKey);
}
}
}
- return false;
+ return keys;
}
/**
* Helper class that extends AdbDebuggingThread to receive the response from AdbDebuggingManager
* indicating whether the key should be allowed to connect.
*/
- class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread {
- AdbDebuggingThreadTest() {
- mManager.super();
- }
-
+ private class AdbDebuggingThreadTest extends AdbDebuggingManager.AdbDebuggingThread {
@Override
public void sendResponse(String msg) {
TestResult result = new TestResult(TestResult.RESULT_RESPONSE_RECEIVED, msg);
@@ -1091,4 +1193,17 @@ public final class AdbDebuggingManagerTest {
return "{mReturnCode = " + mReturnCode + ", mMessage = " + mMessage + "}";
}
}
+
+ private static class FakeTicker implements AdbDebuggingManager.Ticker {
+ private long mCurrentTime;
+
+ private void advance(long milliseconds) {
+ mCurrentTime += milliseconds;
+ }
+
+ @Override
+ public long currentTimeMillis() {
+ return mCurrentTime;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/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/hdmi/BaseAbsoluteVolumeControlTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
index e06877f9144e..8ff87e35f933 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
@@ -212,9 +212,7 @@ public abstract class BaseAbsoluteVolumeControlTest {
}
protected int getLogicalAddress() {
- synchronized (mHdmiCecLocalDevice.mLock) {
- return mHdmiCecLocalDevice.getDeviceInfo().getLogicalAddress();
- }
+ return mHdmiCecLocalDevice.getDeviceInfo().getLogicalAddress();
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index d7fef90456ab..eb7a76182054 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -147,10 +147,9 @@ public class DeviceSelectActionFromPlaybackTest {
// The addresses depend on local device's LA.
// This help the tests to pass with every local device LA.
- synchronized (mHdmiCecLocalDevicePlayback.mLock) {
- mPlaybackLogicalAddress1 =
- mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress();
- }
+ mPlaybackLogicalAddress1 =
+ mHdmiCecLocalDevicePlayback.getDeviceInfo().getLogicalAddress();
+
mPlaybackLogicalAddress2 = mPlaybackLogicalAddress1 == ADDR_PLAYBACK_2
? ADDR_PLAYBACK_1 : ADDR_PLAYBACK_2;
mPlaybackLogicalAddress3 = mPlaybackLogicalAddress1 == ADDR_PLAYBACK_3
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index 0cba10669c85..367f41d938d3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -132,9 +132,7 @@ public class HdmiCecControllerTest {
mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
mTestLooper.dispatchAll();
- synchronized (playbackDevice.mLock) {
- mPlaybackLogicalAddress = playbackDevice.getDeviceInfo().getLogicalAddress();
- }
+ mPlaybackLogicalAddress = playbackDevice.getDeviceInfo().getLogicalAddress();
mTestLooper.dispatchAll();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 6266571d33d4..674e47168392 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -669,6 +669,52 @@ public class HdmiControlServiceTest {
}
@Test
+ public void initCec_statusListener_CecEnabled_CecAvailable_TvTransientToOn() {
+ HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportPowerStatus =
+ HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_TV,
+ mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(),
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON);
+ mNativeWrapper.onCecMessage(reportPowerStatus);
+ mTestLooper.dispatchAll();
+
+ assertThat(hdmiControlStatusCallback.mCecEnabled).isTrue();
+ assertThat(hdmiControlStatusCallback.mCecAvailable).isTrue();
+ }
+
+ @Test
+ public void initCec_statusListener_CecEnabled_CecAvailable_TvTransientToStandby() {
+ HdmiControlStatusCallback hdmiControlStatusCallback = new HdmiControlStatusCallback();
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+ mTestLooper.dispatchAll();
+
+ mHdmiControlServiceSpy.addHdmiControlStatusChangeListener(hdmiControlStatusCallback);
+ mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportPowerStatus =
+ HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_TV,
+ mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(),
+ HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY);
+ mNativeWrapper.onCecMessage(reportPowerStatus);
+ mTestLooper.dispatchAll();
+
+ assertThat(hdmiControlStatusCallback.mCecEnabled).isTrue();
+ assertThat(hdmiControlStatusCallback.mCecAvailable).isTrue();
+ }
+
+ @Test
public void handleCecCommand_errorParameter_returnsAbortInvalidOperand() {
// Validity ERROR_PARAMETER. Taken from HdmiCecMessageValidatorTest#isValid_menuStatus
HdmiCecMessage message = HdmiUtils.buildMessage("80:8D:03");
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 3228e82b566b..8b314cd88878 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -132,9 +132,7 @@ public class RequestSadActionTest {
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
mNativeWrapper.setPhysicalAddress(0x0000);
mTestLooper.dispatchAll();
- synchronized (mHdmiCecLocalDeviceTv.mLock) {
- mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
- }
+ mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
mNativeWrapper.clearResultMessages();
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
index 087e407e314c..dadf81571e30 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
@@ -112,9 +112,7 @@ public class SetAudioVolumeLevelDiscoveryActionTest {
mHdmiControlServiceSpy.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mTestLooper.dispatchAll();
- synchronized (mPlaybackDevice.mLock) {
- mPlaybackLogicalAddress = mPlaybackDevice.getDeviceInfo().getLogicalAddress();
- }
+ mPlaybackLogicalAddress = mPlaybackDevice.getDeviceInfo().getLogicalAddress();
// Setup specific to these tests
mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index 6f1268e5de24..cc6f2cc5ba3e 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -275,7 +275,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_EN_US), imi);
assertEquals(1, result.size());
verifyEquality(autoSubtype, result.get(0));
@@ -299,7 +299,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_EN_US), imi);
assertEquals(2, result.size());
verifyEquality(nonAutoEnUS, result.get(0));
@@ -323,7 +323,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_EN_GB), imi);
assertEquals(2, result.size());
verifyEquality(nonAutoEnGB, result.get(0));
@@ -348,7 +348,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_FR), imi);
assertEquals(2, result.size());
verifyEquality(nonAutoFrCA, result.get(0));
@@ -369,7 +369,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_FR_CA), imi);
assertEquals(2, result.size());
verifyEquality(nonAutoFrCA, result.get(0));
@@ -391,7 +391,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_JA_JP), imi);
assertEquals(3, result.size());
verifyEquality(nonAutoJa, result.get(0));
@@ -413,7 +413,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_JA_JP), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoHi, result.get(0));
@@ -430,7 +430,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_JA_JP), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoEnUS, result.get(0));
@@ -447,7 +447,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_JA_JP), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoEnUS, result.get(0));
@@ -469,7 +469,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(Locale.forLanguageTag("sr-Latn-RS")), imi);
assertEquals(2, result.size());
assertThat(nonAutoSrLatn, is(in(result)));
@@ -489,7 +489,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(Locale.forLanguageTag("sr-Cyrl-RS")), imi);
assertEquals(2, result.size());
assertThat(nonAutoSrCyrl, is(in(result)));
@@ -515,7 +515,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(
Locale.forLanguageTag("sr-Latn-RS-x-android"),
Locale.forLanguageTag("ja-JP"),
@@ -542,7 +542,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_FIL_PH), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoFil, result.get(0));
@@ -560,7 +560,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_FI), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoJa, result.get(0));
@@ -576,7 +576,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_IN), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoIn, result.get(0));
@@ -590,7 +590,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_ID), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoIn, result.get(0));
@@ -604,7 +604,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_IN), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoId, result.get(0));
@@ -618,7 +618,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_ID), imi);
assertEquals(1, result.size());
verifyEquality(nonAutoId, result.get(0));
@@ -640,7 +640,7 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
final ArrayList<InputMethodSubtype> result =
- InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
getResourcesForLocales(LOCALE_FR, LOCALE_EN_US, LOCALE_JA_JP), imi);
assertThat(nonAutoFrCA, is(in(result)));
assertThat(nonAutoEnUS, is(in(result)));
@@ -680,26 +680,26 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN, !CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN, CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
SUBTYPE_MODE_VOICE));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
SUBTYPE_MODE_VOICE));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, !CHECK_COUNTRY,
SUBTYPE_MODE_ANY));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_US, CHECK_COUNTRY,
SUBTYPE_MODE_ANY));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_GB, !CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_GB, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_EN_GB, CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_EN_GB, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
}
@@ -711,22 +711,22 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin",
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
}
@@ -738,22 +738,22 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin",
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FIL_PH, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertFalse(InputMethodUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY,
+ assertFalse(SubtypeUtils.containsSubtypeOf(imi, LOCALE_FI_FI, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
}
@@ -766,13 +766,13 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin",
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
}
@@ -785,13 +785,13 @@ public class InputMethodUtilsTest {
"com.android.apps.inputmethod.latin",
"com.android.apps.inputmethod.latin", "FakeLatinIme", !IS_AUX, IS_DEFAULT,
subtypes);
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_IN, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, !CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
- assertTrue(InputMethodUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY,
+ assertTrue(SubtypeUtils.containsSubtypeOf(imi, LOCALE_ID, CHECK_COUNTRY,
SUBTYPE_MODE_KEYBOARD));
}
}
@@ -805,19 +805,19 @@ public class InputMethodUtilsTest {
{
final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
methodMap.put(systemIme.getId(), systemIme);
- assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, null, ""));
+ assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, null, ""));
}
// Returns null when the config value is empty.
{
final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
methodMap.put(systemIme.getId(), systemIme);
- assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, "", ""));
+ assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap, "", ""));
}
// Returns null when the configured package doesn't have an IME.
{
- assertNull(InputMethodUtils.chooseSystemVoiceIme(new ArrayMap<>(),
+ assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(new ArrayMap<>(),
systemIme.getPackageName(), ""));
}
@@ -825,7 +825,7 @@ public class InputMethodUtilsTest {
{
final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
methodMap.put(systemIme.getId(), systemIme);
- assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(methodMap,
systemIme.getPackageName(), null));
}
@@ -833,13 +833,13 @@ public class InputMethodUtilsTest {
{
final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
methodMap.put(systemIme.getId(), systemIme);
- assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(methodMap,
systemIme.getPackageName(), ""));
}
// Returns null when the current default isn't found.
{
- assertNull(InputMethodUtils.chooseSystemVoiceIme(new ArrayMap<>(),
+ assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(new ArrayMap<>(),
systemIme.getPackageName(), systemIme.getId()));
}
@@ -850,8 +850,8 @@ public class InputMethodUtilsTest {
final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
methodMap.put(systemIme.getId(), systemIme);
methodMap.put(secondIme.getId(), secondIme);
- assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, systemIme.getPackageName(),
- ""));
+ assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap,
+ systemIme.getPackageName(), ""));
}
// Returns the current one when the current default and config point to the same package.
@@ -861,7 +861,7 @@ public class InputMethodUtilsTest {
final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
methodMap.put(systemIme.getId(), systemIme);
methodMap.put(secondIme.getId(), secondIme);
- assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ assertEquals(systemIme, InputMethodInfoUtils.chooseSystemVoiceIme(methodMap,
systemIme.getPackageName(), systemIme.getId()));
}
@@ -871,7 +871,7 @@ public class InputMethodUtilsTest {
final InputMethodInfo nonSystemIme = createFakeInputMethodInfo("NonSystemIme",
"fake.voice0", false /* isSystem */);
methodMap.put(nonSystemIme.getId(), nonSystemIme);
- assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap,
nonSystemIme.getPackageName(), nonSystemIme.getId()));
}
@@ -882,7 +882,7 @@ public class InputMethodUtilsTest {
"FakeDefaultAutoVoiceIme", "fake.voice0", false /* isSystem */);
methodMap.put(systemIme.getId(), systemIme);
methodMap.put(nonSystemIme.getId(), nonSystemIme);
- assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ assertNull(InputMethodInfoUtils.chooseSystemVoiceIme(methodMap,
nonSystemIme.getPackageName(), ""));
}
}
@@ -891,7 +891,7 @@ public class InputMethodUtilsTest {
final Locale systemLocale, String... expectedImeNames) {
final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
final String[] actualImeNames = getPackageNames(
- InputMethodUtils.getDefaultEnabledImes(context, preinstalledImes));
+ InputMethodInfoUtils.getDefaultEnabledImes(context, preinstalledImes));
assertEquals(expectedImeNames.length, actualImeNames.length);
for (int i = 0; i < expectedImeNames.length; ++i) {
assertEquals(expectedImeNames[i], actualImeNames[i]);
@@ -902,7 +902,7 @@ public class InputMethodUtilsTest {
final Locale systemLocale, String... expectedImeNames) {
final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
final String[] actualImeNames = getPackageNames(
- InputMethodUtils.getDefaultEnabledImes(context, preinstalledImes,
+ InputMethodInfoUtils.getDefaultEnabledImes(context, preinstalledImes,
true /* onlyMinimum */));
assertEquals(expectedImeNames.length, actualImeNames.length);
for (int i = 0; i < expectedImeNames.length; ++i) {
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..9de9582af6b5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -779,7 +779,7 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
}
}, 0, 0));
activity.updateOptionsLocked(opts);
@@ -1996,7 +1996,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/SystemServiceTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
index 53c2a5b8967d..486486869d05 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServiceTestsBase.java
@@ -47,10 +47,6 @@ class SystemServiceTestsBase {
mLockRule.waitForLocked(mSystemServicesTestRule::waitUntilWindowAnimatorIdle);
}
- void cleanupWindowManagerHandlers() {
- mLockRule.waitForLocked(mSystemServicesTestRule::cleanupWindowManagerHandlers);
- }
-
boolean waitHandlerIdle(Handler handler) {
return waitHandlerIdle(handler, 0 /* timeout */);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 859eba600af5..ab7e8eab28c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -370,8 +370,6 @@ public class SystemServicesTestRule implements TestRule {
// This makes sure the posted messages without delay are processed, e.g.
// DisplayPolicy#release, WindowManagerService#setAnimationScale.
waitUntilWindowManagerHandlersIdle();
- // Clear all posted messages with delay, so they don't be executed at unexpected times.
- cleanupWindowManagerHandlers();
// Needs to explicitly dispose current static threads because there could be messages
// scheduled at a later time, and all mocks are invalid when it's executed.
DisplayThread.dispose();
@@ -460,18 +458,6 @@ public class SystemServicesTestRule implements TestRule {
return proc;
}
- void cleanupWindowManagerHandlers() {
- final WindowManagerService wm = getWindowManagerService();
- if (wm == null) {
- return;
- }
- wm.mH.removeCallbacksAndMessages(null);
- wm.mAnimationHandler.removeCallbacksAndMessages(null);
- // This is a different handler object than the wm.mAnimationHandler above.
- AnimationThread.getHandler().removeCallbacksAndMessages(null);
- SurfaceAnimationThread.getHandler().removeCallbacksAndMessages(null);
- }
-
void waitUntilWindowManagerHandlersIdle() {
final WindowManagerService wm = getWindowManagerService();
if (wm == null) {
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..e09a94f3bcf9 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;
@@ -281,7 +282,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());
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
index 75531d172761..a4de8f2994dc 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
@@ -22,7 +22,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas;
* An audio class-specific Input Terminal interface.
* see audio10.pdf section 4.3.2.1
*/
-public final class Usb10ACInputTerminal extends UsbACTerminal {
+public final class Usb10ACInputTerminal extends UsbACTerminal implements UsbAudioChannelCluster {
private static final String TAG = "Usb10ACInputTerminal";
private byte mNrChannels; // 7:1 1 Channel (0x01)
@@ -36,14 +36,17 @@ public final class Usb10ACInputTerminal extends UsbACTerminal {
super(length, type, subtype, subclass);
}
- public byte getNrChannels() {
+ @Override
+ public byte getChannelCount() {
return mNrChannels;
}
+ @Override
public int getChannelConfig() {
return mChannelConfig;
}
+ @Override
public byte getChannelNames() {
return mChannelNames;
}
@@ -69,9 +72,7 @@ public final class Usb10ACInputTerminal extends UsbACTerminal {
super.report(canvas);
canvas.openList();
- canvas.writeListItem("Associated Terminal: "
- + ReportCanvas.getHexString(getAssocTerminal()));
- canvas.writeListItem("" + getNrChannels() + " Chans. Config: "
+ canvas.writeListItem("" + getChannelCount() + " Chans. Config: "
+ ReportCanvas.getHexString(getChannelConfig()));
canvas.closeList();
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
index c7634ba7dc63..e89b8f50b698 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
@@ -22,7 +22,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas;
* An audio class-specific Mixer Interface.
* see audio10.pdf section 4.3.2.3
*/
-public final class Usb10ACMixerUnit extends UsbACMixerUnit {
+public final class Usb10ACMixerUnit extends UsbACMixerUnit implements UsbAudioChannelCluster {
private static final String TAG = "Usb10ACMixerUnit";
private int mChannelConfig; // Spatial location of output channels
@@ -34,11 +34,18 @@ public final class Usb10ACMixerUnit extends UsbACMixerUnit {
super(length, type, subtype, subClass);
}
+ @Override
+ public byte getChannelCount() {
+ return mNumOutputs;
+ }
+
+ @Override
public int getChannelConfig() {
return mChannelConfig;
}
- public byte getChanNameID() {
+ @Override
+ public byte getChannelNames() {
return mChanNameID;
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
index ee1b32c1b544..c9c10ce20b13 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
@@ -22,7 +22,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas;
* An audio class-specific Input Terminal interface.
* see Audio20.pdf section 3.13.2 Input Terminal
*/
-public final class Usb20ACInputTerminal extends UsbACTerminal {
+public final class Usb20ACInputTerminal extends UsbACTerminal implements UsbAudioChannelCluster {
private static final String TAG = "Usb20ACInputTerminal";
// See Audio20.pdf - Table 4-9
@@ -47,14 +47,21 @@ public final class Usb20ACInputTerminal extends UsbACTerminal {
return mClkSourceID;
}
- public byte getNumChannels() {
+ @Override
+ public byte getChannelCount() {
return mNumChannels;
}
- public int getChanConfig() {
+ @Override
+ public int getChannelConfig() {
return mChanConfig;
}
+ @Override
+ public byte getChannelNames() {
+ return mChanNames;
+ }
+
public int getControls() {
return mControls;
}
@@ -79,8 +86,8 @@ public final class Usb20ACInputTerminal extends UsbACTerminal {
canvas.openList();
canvas.writeListItem("Clock Source: " + getClkSourceID());
- canvas.writeListItem("" + getNumChannels() + " Channels. Config: "
- + ReportCanvas.getHexString(getChanConfig()));
+ canvas.writeListItem("" + getChannelCount() + " Channels. Config: "
+ + ReportCanvas.getHexString(getChannelConfig()));
canvas.closeList();
}
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
index ab965856bb5d..be932ccd0ffd 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
@@ -20,7 +20,7 @@ package com.android.server.usb.descriptors;
* An audio class-specific Mixer Unit interface.
* see Audio20.pdf section 4.7.2.6 Mixer Unit Descriptor
*/
-public final class Usb20ACMixerUnit extends UsbACMixerUnit {
+public final class Usb20ACMixerUnit extends UsbACMixerUnit implements UsbAudioChannelCluster {
private static final String TAG = "Usb20ACMixerUnit";
private int mChanConfig; // 6+p:4 Describes the spatial location of the
@@ -38,6 +38,21 @@ public final class Usb20ACMixerUnit extends UsbACMixerUnit {
}
@Override
+ public byte getChannelCount() {
+ return mNumOutputs;
+ }
+
+ @Override
+ public int getChannelConfig() {
+ return mChanConfig;
+ }
+
+ @Override
+ public byte getChannelNames() {
+ return mChanNames;
+ }
+
+ @Override
public int parseRawDescriptors(ByteStream stream) {
super.parseRawDescriptors(stream);
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
index 20a97af49bc5..9c5b100d16a8 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
@@ -71,6 +71,7 @@ public final class Usb20ACOutputTerminal extends UsbACTerminal {
super.report(canvas);
canvas.openList();
+ canvas.writeListItem("Source ID:" + getSourceID());
canvas.writeListItem("Clock Source ID: " + getClkSourceID());
canvas.writeListItem("Controls: " + ReportCanvas.getHexString(getControls()));
canvas.writeListItem("Terminal Name ID: " + getTerminalID());
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java
index de2073882e3a..211ac6821470 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java
@@ -21,7 +21,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas;
* Audio20.pdf - 4.9.2 Class-Specific AS Interface Descriptor
* 16 bytes
*/
-public final class Usb20ASGeneral extends UsbACInterface {
+public final class Usb20ASGeneral extends UsbACInterface implements UsbAudioChannelCluster {
private static final String TAG = "Usb20ASGeneral";
// Audio20.pdf - Table 4-27
@@ -61,14 +61,17 @@ public final class Usb20ASGeneral extends UsbACInterface {
return mFormats;
}
- public byte getNumChannels() {
+ @Override
+ public byte getChannelCount() {
return mNumChannels;
}
+ @Override
public int getChannelConfig() {
return mChannelConfig;
}
+ @Override
public byte getChannelNames() {
return mChannelNames;
}
@@ -96,7 +99,7 @@ public final class Usb20ASGeneral extends UsbACInterface {
canvas.writeListItem("Controls: " + ReportCanvas.getHexString(getControls()));
canvas.writeListItem("Format Type: " + ReportCanvas.getHexString(getFormatType()));
canvas.writeListItem("Formats: " + ReportCanvas.getHexString(getFormats()));
- canvas.writeListItem("Num Channels: " + getNumChannels());
+ canvas.writeListItem("Channel Count: " + getChannelCount());
canvas.writeListItem("Channel Config: " + ReportCanvas.getHexString(getChannelConfig()));
canvas.writeListItem("Channel Names String ID: " + getChannelNames());
canvas.closeList();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
index 36139d6c6900..819e73df955b 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
@@ -66,6 +66,8 @@ public abstract class UsbACTerminal extends UsbACInterface {
canvas.writeListItem("Type: " + ReportCanvas.getHexString(terminalType) + ": "
+ UsbStrings.getTerminalName(terminalType));
canvas.writeListItem("ID: " + ReportCanvas.getHexString(getTerminalID()));
+ canvas.writeListItem("Associated terminal: "
+ + ReportCanvas.getHexString(getAssocTerminal()));
canvas.closeList();
}
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbAudioChannelCluster.java b/services/usb/java/com/android/server/usb/descriptors/UsbAudioChannelCluster.java
new file mode 100644
index 000000000000..218ba6735cd0
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbAudioChannelCluster.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb.descriptors;
+
+/**
+ * @hide
+ * Group of logical audio channels that carry tightly related synchronous audio information.
+ * See Audio10.pdf section 3.7.2.3 Audio Channel Cluster format and Audio20.pdf section 3.13.1
+ * audio channel cluster.
+ */
+public interface UsbAudioChannelCluster {
+ /**
+ * @return logical channels in the cluster.
+ */
+ byte getChannelCount();
+
+ /**
+ * @return a bit field that indicates which spatial locations are present in the cluster.
+ */
+ int getChannelConfig();
+
+ /**
+ * @return index to a string descriptor that describes the spatial location of the first
+ * non-predefined logical channel in the cluster.
+ */
+ byte getChannelNames();
+}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index cd6ea681db07..f13fcd8d81a4 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -832,31 +832,47 @@ public final class UsbDescriptorParser {
return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER;
}
+ // TODO: Up/Downmix process descriptor is not yet parsed, which may affect the result here.
+ private int getMaximumChannelCount() {
+ int maxChannelCount = 0;
+ for (UsbDescriptor descriptor : mDescriptors) {
+ if (descriptor instanceof UsbAudioChannelCluster) {
+ maxChannelCount = Math.max(maxChannelCount,
+ ((UsbAudioChannelCluster) descriptor).getChannelCount());
+ }
+ }
+ return maxChannelCount;
+ }
+
/**
* @hide
*/
- public float getOutputHeadsetProbability() {
+ public float getOutputHeadsetLikelihood() {
if (hasMIDIInterface()) {
return 0.0f;
}
- float probability = 0.0f;
+ float likelihood = 0.0f;
ArrayList<UsbDescriptor> acDescriptors;
// Look for a "speaker"
boolean hasSpeaker = false;
+ boolean hasAssociatedInputTerminal = false;
+ boolean hasHeadphoneOrHeadset = false;
acDescriptors =
getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL,
UsbACInterface.AUDIO_AUDIOCONTROL);
for (UsbDescriptor descriptor : acDescriptors) {
if (descriptor instanceof UsbACTerminal) {
UsbACTerminal outDescr = (UsbACTerminal) descriptor;
- if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER
- || outDescr.getTerminalType()
- == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES
- || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) {
+ if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER) {
hasSpeaker = true;
- break;
+ if (outDescr.getAssocTerminal() != 0x0) {
+ hasAssociatedInputTerminal = true;
+ }
+ } else if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES
+ || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) {
+ hasHeadphoneOrHeadset = true;
}
} else {
Log.w(TAG, "Undefined Audio Output terminal l: " + descriptor.getLength()
@@ -864,15 +880,27 @@ public final class UsbDescriptorParser {
}
}
- if (hasSpeaker) {
- probability += 0.75f;
+ if (hasHeadphoneOrHeadset) {
+ likelihood += 0.75f;
+ } else if (hasSpeaker) {
+ // The device only reports output terminal as speaker. Try to figure out if the device
+ // is a headset or not by checking if it has associated input terminal and if multiple
+ // channels are supported or not.
+ likelihood += 0.5f;
+ if (hasAssociatedInputTerminal) {
+ likelihood += 0.25f;
+ }
+ if (getMaximumChannelCount() > 2) {
+ // When multiple channels are supported, it is less likely to be a headset.
+ likelihood -= 0.25f;
+ }
}
- if (hasSpeaker && hasHIDInterface()) {
- probability += 0.25f;
+ if ((hasHeadphoneOrHeadset || hasSpeaker) && hasHIDInterface()) {
+ likelihood += 0.25f;
}
- return probability;
+ return likelihood;
}
/**
@@ -882,7 +910,7 @@ public final class UsbDescriptorParser {
* to count on the peripheral being a headset.
*/
public boolean isOutputHeadset() {
- return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER;
+ return getOutputHeadsetLikelihood() >= OUT_HEADSET_TRIGGER;
}
/**
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/telephony/java/android/telephony/AccessNetworkUtils.java b/telephony/java/android/telephony/AccessNetworkUtils.java
index b0f207cd9ed3..b5d97abdd3eb 100644
--- a/telephony/java/android/telephony/AccessNetworkUtils.java
+++ b/telephony/java/android/telephony/AccessNetworkUtils.java
@@ -4,8 +4,8 @@ import static android.telephony.ServiceState.DUPLEX_MODE_FDD;
import static android.telephony.ServiceState.DUPLEX_MODE_TDD;
import static android.telephony.ServiceState.DUPLEX_MODE_UNKNOWN;
-import android.telephony.AccessNetworkConstants.EutranBand;
import android.telephony.AccessNetworkConstants.EutranBandArfcnFrequency;
+import android.telephony.AccessNetworkConstants.EutranBand;
import android.telephony.AccessNetworkConstants.GeranBand;
import android.telephony.AccessNetworkConstants.GeranBandArfcnFrequency;
import android.telephony.AccessNetworkConstants.NgranArfcnFrequency;
@@ -13,6 +13,7 @@ import android.telephony.AccessNetworkConstants.NgranBands;
import android.telephony.AccessNetworkConstants.UtranBand;
import android.telephony.AccessNetworkConstants.UtranBandArfcnFrequency;
import android.telephony.ServiceState.DuplexMode;
+import android.util.Log;
import java.util.Arrays;
import java.util.HashSet;
@@ -231,110 +232,6 @@ public class AccessNetworkUtils {
}
/**
- * Gets the NR Operating band for a given downlink NRARFCN.
- *
- * <p>See 3GPP TS 38.104 Table 5.2-1 NR operating bands in FR1 and
- * Table 5.2-2 NR operating bands in FR2
- *
- * @param nrarfcn The downlink NRARFCN
- * @return Operating band number, or {@link #INVALID_BAND} if no corresponding band exists
- */
- public static int getOperatingBandForNrarfcn(int nrarfcn) {
- if (nrarfcn >= 2110 && nrarfcn <= 2170) {
- return NgranBands.BAND_1;
- } else if (nrarfcn >= 1930 && nrarfcn <= 1990) {
- return NgranBands.BAND_2;
- } else if (nrarfcn >= 1805 && nrarfcn <= 1880) {
- return NgranBands.BAND_3;
- } else if (nrarfcn >= 869 && nrarfcn <= 894) {
- return NgranBands.BAND_5;
- } else if (nrarfcn >= 2620 && nrarfcn <= 2690) {
- return NgranBands.BAND_7;
- } else if (nrarfcn >= 925 && nrarfcn <= 960) {
- return NgranBands.BAND_8;
- } else if (nrarfcn >= 729 && nrarfcn <= 746) {
- return NgranBands.BAND_12;
- } else if (nrarfcn >= 758 && nrarfcn <= 768) {
- return NgranBands.BAND_14;
- } else if (nrarfcn >= 860 && nrarfcn <= 875) {
- return NgranBands.BAND_18;
- } else if (nrarfcn >= 791 && nrarfcn <= 821) {
- return NgranBands.BAND_20;
- } else if (nrarfcn >= 1930 && nrarfcn <= 1995) {
- return NgranBands.BAND_25;
- } else if (nrarfcn >= 859 && nrarfcn <= 894) {
- return NgranBands.BAND_26;
- } else if (nrarfcn >= 758 && nrarfcn <= 803) {
- return NgranBands.BAND_28;
- } else if (nrarfcn >= 717 && nrarfcn <= 728) {
- return NgranBands.BAND_29;
- } else if (nrarfcn >= 2350 && nrarfcn <= 2360) {
- return NgranBands.BAND_30;
- } else if (nrarfcn >= 2010 && nrarfcn <= 2025) {
- return NgranBands.BAND_34;
- } else if (nrarfcn >= 2570 && nrarfcn <= 2620) {
- return NgranBands.BAND_38;
- } else if (nrarfcn >= 1880 && nrarfcn <= 1920) {
- return NgranBands.BAND_39;
- } else if (nrarfcn >= 2300 && nrarfcn <= 2400) {
- return NgranBands.BAND_40;
- } else if (nrarfcn >= 2496 && nrarfcn <= 2690) {
- return NgranBands.BAND_41;
- } else if (nrarfcn >= 5150 && nrarfcn <= 5925) {
- return NgranBands.BAND_46;
- } else if (nrarfcn >= 3550 && nrarfcn <= 3700) {
- return NgranBands.BAND_48;
- } else if (nrarfcn >= 1432 && nrarfcn <= 1517) {
- return NgranBands.BAND_50;
- } else if (nrarfcn >= 1427 && nrarfcn <= 1432) {
- return NgranBands.BAND_51;
- } else if (nrarfcn >= 2483 && nrarfcn <= 2495) {
- return NgranBands.BAND_53;
- } else if (nrarfcn >= 2110 && nrarfcn <= 2200) {
- return NgranBands.BAND_65; // BAND_66 has the same channels
- } else if (nrarfcn >= 1995 && nrarfcn <= 2020) {
- return NgranBands.BAND_70;
- } else if (nrarfcn >= 617 && nrarfcn <= 652) {
- return NgranBands.BAND_71;
- } else if (nrarfcn >= 1475 && nrarfcn <= 1518) {
- return NgranBands.BAND_74;
- } else if (nrarfcn >= 1432 && nrarfcn <= 1517) {
- return NgranBands.BAND_75;
- } else if (nrarfcn >= 1427 && nrarfcn <= 1432) {
- return NgranBands.BAND_76;
- } else if (nrarfcn >= 3300 && nrarfcn <= 4200) {
- return NgranBands.BAND_77;
- } else if (nrarfcn >= 3300 && nrarfcn <= 3800) {
- return NgranBands.BAND_78;
- } else if (nrarfcn >= 4400 && nrarfcn <= 5000) {
- return NgranBands.BAND_79;
- } else if (nrarfcn >= 2496 && nrarfcn <= 2690) {
- return NgranBands.BAND_90;
- } else if (nrarfcn >= 1427 && nrarfcn <= 1432) {
- return NgranBands.BAND_91;
- } else if (nrarfcn >= 1427 && nrarfcn <= 1432) {
- return NgranBands.BAND_92;
- } else if (nrarfcn >= 1432 && nrarfcn <= 1517) {
- return NgranBands.BAND_93;
- } else if (nrarfcn >= 1427 && nrarfcn <= 1432) {
- return NgranBands.BAND_94;
- } else if (nrarfcn >= 1432 && nrarfcn <= 1517) {
- return NgranBands.BAND_94;
- } else if (nrarfcn >= 5925 && nrarfcn <= 7125) {
- return NgranBands.BAND_96;
- } else if (nrarfcn >= 26500 && nrarfcn <= 29500) {
- return NgranBands.BAND_257;
- } else if (nrarfcn >= 24250 && nrarfcn <= 27500) {
- return NgranBands.BAND_258;
- } else if (nrarfcn >= 37000 && nrarfcn <= 40000) {
- return NgranBands.BAND_260;
- } else if (nrarfcn >= 27500 && nrarfcn <= 28350) {
- return NgranBands.BAND_261;
- }
- return INVALID_BAND;
- }
-
- /**
* Gets the GERAN Operating band for a given ARFCN.
*
* <p>See 3GPP TS 45.005 clause 2 for calculation.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d1729f886f6e..432d08725e87 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17069,4 +17069,35 @@ public class TelephonyManager {
}
return false;
}
+
+ /**
+ * Fetches the EFPSISMSC value from the SIM that contains the Public Service Identity
+ * of the SM-SC (either a SIP URI or tel URI), the value is common for both appType
+ * {@link #APPTYPE_ISIM} and {@link #APPTYPE_SIM}.
+ * The EFPSISMSC value is used by the ME to submit SMS over IP as defined in 24.341 [55].
+ *
+ * @param appType ICC Application type {@link #APPTYPE_ISIM} or {@link #APPTYPE_USIM}
+ * @return SIP URI or tel URI of the Public Service Identity of the SM-SC
+ * @throws SecurityException if the caller does not have the required permission/privileges
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ public String getSmscIdentity(int appType) {
+ try {
+ IPhoneSubInfo info = getSubscriberInfoService();
+ if (info == null) {
+ Rlog.e(TAG, "getSmscIdentity(): IPhoneSubInfo instance is NULL");
+ return null;
+ }
+ /** Fetches the SIM PSISMSC params based on subId and appType */
+ return info.getSmscIdentity(getSubId(), appType);
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "getSmscIdentity(): RemoteException: " + ex.getMessage());
+ } catch (NullPointerException ex) {
+ Rlog.e(TAG, "getSmscIdentity(): NullPointerException: " + ex.getMessage());
+ }
+ return null;
+ }
}
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index ce2017bb9a35..78335fd54917 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -218,4 +218,18 @@ interface IPhoneSubInfo {
*/
String getIccSimChallengeResponse(int subId, int appType, int authType, String data,
String callingPackage, String callingFeatureId);
+
+ /**
+ * Fetches the EFPSISMSC value from the SIM that contains the Public Service Identity
+ * of the SM-SC (either a SIP URI or tel URI), the value is common for both appType
+ * {@link #APPTYPE_ISIM} and {@link #APPTYPE_SIM}.
+ * The EFPSISMSC value is used by the ME to submit SMS over IP as defined in 24.341 [55].
+ *
+ * @param appType ICC Application type {@link #APPTYPE_ISIM} or {@link #APPTYPE_USIM}
+ * @return SIP URI or tel URI of the Public Service Identity of the SM-SC
+ * @throws SecurityException if the caller does not have the required permission/privileges
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)")
+ String getSmscIdentity(int subId, int appType);
}
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)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 23baac29b6c1..eee7cf3acbe6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -16,14 +16,16 @@
package com.android.server.wm.flicker.launch
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
-import android.platform.test.annotations.FlakyTest
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.navBarLayerPositionEnd
+import com.android.server.wm.flicker.statusBarLayerPositionEnd
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,8 +45,8 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
@Postsubmit
-open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter)
- : OpenAppFromNotificationCold(testSpec) {
+open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter) :
+ OpenAppFromNotificationCold(testSpec) {
override val openingNotificationsFromLockScreen = true
@@ -85,6 +87,62 @@ open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter)
@Test
override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+ /**
+ * Checks the position of the navigation bar at the start and end of the transition
+ *
+ * Differently from the normal usage of this assertion, check only the final state of the
+ * transition because the display is off at the start and the NavBar is never visible
+ */
+ @Postsubmit
+ @Test
+ override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerPositionEnd()
+
+ /**
+ * Checks the position of the status bar at the start and end of the transition
+ *
+ * Differently from the normal usage of this assertion, check only the final state of the
+ * transition because the display is off at the start and the NavBar is never visible
+ */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerPositionEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
companion object {
/**
* Creates the test configurations.
@@ -95,8 +153,8 @@ open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter)
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
- return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance()
+ return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(repetitions = 3)
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
index 49d8ea628585..b156eb1a0933 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt
@@ -16,14 +16,16 @@
package com.android.server.wm.flicker.launch
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
-import android.platform.test.annotations.FlakyTest
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.navBarLayerPositionEnd
+import com.android.server.wm.flicker.statusBarLayerPositionEnd
import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.FixMethodOrder
import org.junit.Test
@@ -44,8 +46,8 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
@Postsubmit
-open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter)
- : OpenAppFromNotificationWarm(testSpec) {
+open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter) :
+ OpenAppFromNotificationWarm(testSpec) {
override val openingNotificationsFromLockScreen = true
@@ -111,6 +113,67 @@ open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter)
override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+ /**
+ * Checks the position of the navigation bar at the start and end of the transition
+ *
+ * Differently from the normal usage of this assertion, check only the final state of the
+ * transition because the display is off at the start and the NavBar is never visible
+ */
+ @Postsubmit
+ @Test
+ override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerPositionEnd()
+
+ /**
+ * Checks the position of the status bar at the start and end of the transition
+ *
+ * Differently from the normal usage of this assertion, check only the final state of the
+ * transition because the display is off at the start and the NavBar is never visible
+ */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerPositionEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
companion object {
/**
* Creates the test configurations.
@@ -121,8 +184,8 @@ open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter)
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
- return com.android.server.wm.flicker.FlickerTestParameterFactory.getInstance()
+ return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(repetitions = 3)
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
index 950f52ab57e1..5c86a4278b8c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
@@ -16,9 +16,9 @@
package com.android.server.wm.flicker.launch
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
-import android.platform.test.annotations.FlakyTest
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
@@ -26,6 +26,8 @@ import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.navBarLayerPositionEnd
+import com.android.server.wm.flicker.statusBarLayerPositionEnd
import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.FixMethodOrder
import org.junit.Test
@@ -45,8 +47,8 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
@Postsubmit
-class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParameter)
- : OpenAppFromLockNotificationCold(testSpec) {
+class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParameter) :
+ OpenAppFromLockNotificationCold(testSpec) {
private val showWhenLockedApp: ShowWhenLockedAppHelper =
ShowWhenLockedAppHelper(instrumentation)
@@ -109,6 +111,73 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet
@Test
override fun entireScreenCovered() = super.entireScreenCovered()
+ /**
+ * Checks the position of the navigation bar at the start and end of the transition
+ *
+ * Differently from the normal usage of this assertion, check only the final state of the
+ * transition because the display is off at the start and the NavBar is never visible
+ */
+ @Postsubmit
+ @Test
+ override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerPositionEnd()
+
+ /**
+ * Checks the position of the status bar at the start and end of the transition
+ *
+ * Differently from the normal usage of this assertion, check only the final state of the
+ * transition because the display is off at the start and the NavBar is never visible
+ */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerPositionEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appWindowBecomesTopWindow() = super.appWindowBecomesTopWindow()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun appWindowBecomesVisible() = super.appWindowBecomesVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
/**
* Creates the test configurations.
@@ -123,4 +192,4 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet
.getConfigNonRotationTests(repetitions = 3)
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index 2d00f3f92dcc..844311259a06 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -16,11 +16,11 @@
package com.android.server.wm.flicker.launch
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
import android.view.WindowInsets
import android.view.WindowManager
-import android.platform.test.annotations.FlakyTest
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.launcher3.tapl.LauncherInstrumentation
@@ -53,8 +53,8 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
@Postsubmit
-open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter)
- : OpenAppTransition(testSpec) {
+open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) :
+ OpenAppTransition(testSpec) {
protected val taplInstrumentation = LauncherInstrumentation()
override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
@@ -180,6 +180,32 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter)
super.appWindowBecomesTopWindow()
}
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() = super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
companion object {
/**
* Creates the test configurations.
@@ -194,4 +220,4 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter)
.getConfigNonRotationTests(repetitions = 3)
}
}
-} \ No newline at end of file
+}
diff --git a/tools/localedata/OWNERS b/tools/localedata/OWNERS
new file mode 100644
index 000000000000..2501679784d6
--- /dev/null
+++ b/tools/localedata/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 24949
+include platform/external/icu:/OWNERS