summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java7
-rw-r--r--api/Android.bp31
-rw-r--r--api/api.go26
-rw-r--r--cmds/idmap2/idmap2d/Idmap2Service.cpp6
-rw-r--r--cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl1
-rw-r--r--cmds/idmap2/include/idmap2/FabricatedOverlay.h4
-rw-r--r--cmds/idmap2/include/idmap2/ResourceUtils.h1
-rw-r--r--cmds/idmap2/libidmap2/FabricatedOverlay.cpp11
-rw-r--r--core/api/current.txt19
-rw-r--r--core/api/system-current.txt1
-rw-r--r--core/java/android/app/AppOpsManager.java22
-rw-r--r--core/java/android/app/TEST_MAPPING3
-rw-r--r--core/java/android/app/admin/SecurityLog.java4
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java185
-rw-r--r--core/java/android/companion/CompanionDeviceService.java42
-rw-r--r--core/java/android/companion/CompanionException.java29
-rw-r--r--core/java/android/companion/ICompanionDeviceManager.aidl8
-rw-r--r--core/java/android/companion/ISystemDataTransferCallback.aidl24
-rw-r--r--core/java/android/content/Context.java2
-rw-r--r--core/java/android/content/TEST_MAPPING3
-rw-r--r--core/java/android/content/om/FabricatedOverlay.java21
-rw-r--r--core/java/android/content/pm/parsing/ApkLiteParseUtils.java5
-rw-r--r--core/java/android/database/sqlite/SQLiteConnectionPool.java24
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java10
-rw-r--r--core/java/android/hardware/camera2/params/Capability.java62
-rw-r--r--core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java83
-rw-r--r--core/java/android/hardware/hdmi/HdmiPlaybackClient.java6
-rw-r--r--core/java/android/hardware/radio/OWNERS5
-rw-r--r--core/java/android/hardware/radio/ProgramList.java44
-rw-r--r--core/java/android/inputmethodservice/IInputMethodWrapper.java16
-rw-r--r--core/java/android/os/PowerManager.java7
-rw-r--r--core/java/android/os/TEST_MAPPING12
-rw-r--r--core/java/android/os/VibrationAttributes.java3
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java22
-rw-r--r--core/java/android/view/IRemoteAnimationRunner.aidl2
-rw-r--r--core/java/android/view/TEST_MAPPING3
-rw-r--r--core/java/android/view/ViewRootImpl.java32
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java30
-rw-r--r--core/java/android/widget/AbsListView.java88
-rw-r--r--core/java/android/widget/HorizontalScrollView.java82
-rw-r--r--core/java/android/widget/OverScroller.java4
-rw-r--r--core/java/android/widget/ScrollView.java78
-rw-r--r--core/java/android/window/SizeConfigurationBuckets.java17
-rw-r--r--core/java/com/android/internal/app/IBatteryStats.aidl7
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java6
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java12
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl2
-rw-r--r--core/jni/android_graphics_BLASTBufferQueue.cpp21
-rw-r--r--core/res/AndroidManifest.xml18
-rw-r--r--core/res/res/values/strings.xml5
-rw-r--r--core/res/res/xml/sms_short_codes.xml6
-rw-r--r--core/tests/BroadcastRadioTests/OWNERS4
-rw-r--r--core/tests/companiontests/src/android/companion/CompanionTestRunner.java1
-rw-r--r--core/tests/companiontests/src/android/companion/SystemDataTransportTest.java239
-rw-r--r--core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java30
-rw-r--r--core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java18
-rw-r--r--core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java256
-rw-r--r--core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java15
-rw-r--r--data/etc/services.core.protolog.json42
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java263
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java51
-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/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java27
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java116
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java54
-rw-r--r--libs/WindowManager/Shell/res/values-television/config.xml9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java2
-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/splitscreen/StageCoordinator.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java6
-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/bubble/BaseBubbleScreen.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt15
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt36
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt32
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt37
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt5
-rw-r--r--libs/input/SpriteController.cpp32
-rw-r--r--libs/input/SpriteController.h2
-rw-r--r--libs/protoutil/Android.bp3
-rw-r--r--media/java/android/media/projection/IMediaProjection.aidl14
-rw-r--r--media/java/android/media/projection/MediaProjection.java10
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppManager.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java3
-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/fuelgauge/BatteryStatus.java29
-rw-r--r--packages/StatementService/OWNERS2
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt4
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/CollectV1Worker.kt31
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt22
-rw-r--r--packages/SystemUI/Android.bp1
-rw-r--r--packages/SystemUI/TEST_MAPPING11
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt43
-rw-r--r--packages/SystemUI/compose/gallery/Android.bp1
-rw-r--r--packages/SystemUI/compose/gallery/AndroidManifest.xml18
-rw-r--r--packages/SystemUI/compose/gallery/app/AndroidManifest.xml37
-rw-r--r--packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt5
-rw-r--r--packages/SystemUI/compose/testing/Android.bp43
-rw-r--r--packages/SystemUI/compose/testing/AndroidManifest.xml25
-rw-r--r--packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt74
-rw-r--r--packages/SystemUI/res/layout/media_ttt_chip_receiver.xml5
-rw-r--r--packages/SystemUI/res/values/dimens.xml1
-rw-r--r--packages/SystemUI/screenshot/Android.bp48
-rw-r--r--packages/SystemUI/screenshot/AndroidManifest.xml28
-rw-r--r--packages/SystemUI/screenshot/res/values/themes.xml25
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java193
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt22
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestRule.kt206
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestSpec.kt78
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt80
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt31
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java9
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java8
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java69
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java132
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt144
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java24
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java141
-rw-r--r--services/core/java/com/android/server/Watchdog.java6
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java8
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java51
-rw-r--r--services/core/java/com/android/server/am/ContentProviderHelper.java66
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java11
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java128
-rw-r--r--services/core/java/com/android/server/biometrics/AuthSession.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java84
-rw-r--r--services/core/java/com/android/server/broadcastradio/OWNERS4
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java115
-rwxr-xr-xservices/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java1
-rw-r--r--services/core/java/com/android/server/hdmi/OneTouchPlayAction.java20
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java332
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java73
-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/location/provider/LocationProviderManager.java14
-rw-r--r--services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java92
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java12
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java20
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerShellCommand.java17
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java3
-rw-r--r--services/core/java/com/android/server/pm/ResolveIntentHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java5
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java86
-rw-r--r--services/core/java/com/android/server/power/SystemPropertiesWrapper.java15
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java57
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java43
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java13
-rw-r--r--services/core/java/com/android/server/wm/ContentRecordingController.java1
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java18
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java3
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java18
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java108
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java4
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java5
-rw-r--r--services/core/java/com/android/server/wm/Transition.java32
-rw-r--r--services/core/java/com/android/server/wm/TransitionTracer.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java62
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java60
-rw-r--r--services/core/jni/Android.bp2
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java13
-rw-r--r--services/net/Android.bp24
-rw-r--r--services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java26
-rw-r--r--services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/AndroidManifest-sharedUser.xml7
-rw-r--r--services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/src/com/android/appenumeration/testapp/DummyActivity.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java43
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java101
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java40
-rw-r--r--services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java132
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java59
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java35
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java12
-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/DisplayContentTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java73
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java110
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java31
-rw-r--r--telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl14
-rw-r--r--tests/ApkVerityTest/Android.bp4
-rw-r--r--tests/ApkVerityTest/AndroidTest.xml10
-rw-r--r--tests/ApkVerityTest/block_device_writer/Android.bp20
-rw-r--r--tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java5
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt7
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt10
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt29
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt25
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt30
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt58
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt16
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt9
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt9
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt14
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt5
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt11
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt16
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt10
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt19
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt19
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt12
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt18
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt12
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt44
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt39
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt47
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt28
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt179
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt7
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt21
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt74
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWarm.kt79
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt87
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt25
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt14
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt53
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt39
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt7
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt27
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt13
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt20
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt36
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt39
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt43
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt4
-rw-r--r--tests/RollbackTest/SampleRollbackApp/Android.bp32
-rw-r--r--tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml31
-rw-r--r--tests/RollbackTest/SampleRollbackApp/res/layout/activity_main.xml37
-rw-r--r--tests/RollbackTest/SampleRollbackApp/res/layout/listitem_rollbackinfo.xml41
-rw-r--r--tests/RollbackTest/SampleRollbackApp/res/values/strings.xml20
-rw-r--r--tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java159
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt44
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt6
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt64
-rw-r--r--tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt679
299 files changed, 8067 insertions, 2586 deletions
diff --git a/Android.bp b/Android.bp
index d69e51c6e46f..eae0d73f449f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -359,6 +359,7 @@ java_defaults {
"contacts-provider-platform-compat-config",
],
libs: [
+ "androidx.annotation_annotation",
"app-compat-annotations",
"ext",
"framework-updatable-stubs-module_libs_api",
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
index a27e16a46a04..279681bc0d15 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
@@ -51,7 +51,7 @@ public class MessageDigestPerfTest {
@Parameterized.Parameter(0)
public Algorithm mAlgorithm;
- public String mProvider = "AndroidSSL";
+ public String mProvider = "AndroidOpenSSL";
private static final int DATA_SIZE = 8192;
private static final byte[] DATA = new byte[DATA_SIZE];
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 0de0a1cf9c8e..d6b246a9e2e3 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -112,6 +112,7 @@ import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.LongArrayQueue;
@@ -2299,7 +2300,11 @@ public class AlarmManagerService extends SystemService {
+ " reached for uid: " + UserHandle.formatUid(callingUid)
+ ", callingPackage: " + callingPackage;
Slog.w(TAG, errorMsg);
- throw new IllegalStateException(errorMsg);
+ if (callingUid != Process.SYSTEM_UID) {
+ throw new IllegalStateException(errorMsg);
+ } else {
+ EventLog.writeEvent(0x534e4554, "234441463", -1, errorMsg);
+ }
}
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, interval, operation,
directReceiver, listenerTag, flags, workSource, alarmClock, callingUid,
diff --git a/api/Android.bp b/api/Android.bp
index 7729a7f75d53..e9930e3a3b5b 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -194,6 +194,37 @@ genrule {
"$(location :frameworks-base-api-module-lib-current.txt)",
}
+// This produces the same annotations.zip as framework-doc-stubs, but by using
+// outputs from individual modules instead of all the source code.
+genrule_defaults {
+ name: "sdk-annotations-defaults",
+ out: ["annotations.zip"],
+ tools: [
+ "merge_annotation_zips",
+ "soong_zip",
+ ],
+ cmd: "$(location merge_annotation_zips) $(genDir)/out $(in) && " +
+ "$(location soong_zip) -o $(out) -C $(genDir)/out -D $(genDir)/out",
+}
+
+genrule {
+ name: "sdk-annotations.zip",
+ defaults: ["sdk-annotations-defaults"],
+ srcs: [
+ ":android-non-updatable-doc-stubs{.annotations.zip}",
+ ":all-modules-public-annotations",
+ ],
+}
+
+genrule {
+ name: "sdk-annotations-system.zip",
+ defaults: ["sdk-annotations-defaults"],
+ srcs: [
+ ":android-non-updatable-doc-stubs-system{.annotations.zip}",
+ ":all-modules-system-annotations",
+ ],
+}
+
genrule {
name: "combined-removed-dex",
visibility: [
diff --git a/api/api.go b/api/api.go
index ca0fc28cdb9d..f15804156bdf 100644
--- a/api/api.go
+++ b/api/api.go
@@ -148,17 +148,18 @@ func createMergedStubsSrcjar(ctx android.LoadHookContext, modules []string) {
ctx.CreateModule(genrule.GenRuleFactory, &props)
}
-// This produces the same annotations.zip as framework-doc-stubs, but by using
-// outputs from individual modules instead of all the source code.
-func createMergedAnnotations(ctx android.LoadHookContext, modules []string) {
- props := genruleProps{}
- props.Name = proptools.StringPtr("sdk-annotations.zip")
- props.Tools = []string{"merge_annotation_zips", "soong_zip"}
- props.Out = []string{"annotations.zip"}
- props.Cmd = proptools.StringPtr("$(location merge_annotation_zips) $(genDir)/out $(in) && " +
- "$(location soong_zip) -o $(out) -C $(genDir)/out -D $(genDir)/out")
- props.Srcs = append([]string{":android-non-updatable-doc-stubs{.annotations.zip}"}, createSrcs(modules, "{.public.annotations.zip}")...)
- ctx.CreateModule(genrule.GenRuleFactory, &props)
+func createMergedPublicAnnotationsFilegroup(ctx android.LoadHookContext, modules []string) {
+ props := fgProps{}
+ props.Name = proptools.StringPtr("all-modules-public-annotations")
+ props.Srcs = createSrcs(modules, "{.public.annotations.zip}")
+ ctx.CreateModule(android.FileGroupFactory, &props)
+}
+
+func createMergedSystemAnnotationsFilegroup(ctx android.LoadHookContext, modules []string) {
+ props := fgProps{}
+ props.Name = proptools.StringPtr("all-modules-system-annotations")
+ props.Srcs = createSrcs(modules, "{.system.annotations.zip}")
+ ctx.CreateModule(android.FileGroupFactory, &props)
}
func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) {
@@ -291,7 +292,8 @@ func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) {
createMergedFrameworkModuleLibStubs(ctx, bootclasspath)
createMergedFrameworkImpl(ctx, bootclasspath)
- createMergedAnnotations(ctx, bootclasspath)
+ createMergedPublicAnnotationsFilegroup(ctx, bootclasspath)
+ createMergedSystemAnnotationsFilegroup(ctx, bootclasspath)
createFilteredApiVersions(ctx, bootclasspath)
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 1b2d905aec0a..073d987f5dad 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -234,7 +234,11 @@ Status Idmap2Service::createFabricatedOverlay(
}
for (const auto& res : overlay.entries) {
- builder.SetResourceValue(res.resourceName, res.dataType, res.data);
+ if (res.dataType == Res_value::TYPE_STRING) {
+ builder.SetResourceValue(res.resourceName, res.dataType, res.stringData.value());
+ } else {
+ builder.SetResourceValue(res.resourceName, res.dataType, res.data);
+ }
}
// Generate the file path of the fabricated overlay and ensure it does not collide with an
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index 6c2af274ae32..a6824da8c424 100644
--- a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -23,4 +23,5 @@ parcelable FabricatedOverlayInternalEntry {
@utf8InCpp String resourceName;
int dataType;
int data;
+ @nullable @utf8InCpp String stringData;
} \ No newline at end of file
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index 375671881e5f..65916d7ebf49 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -41,6 +41,9 @@ struct FabricatedOverlay {
Builder& SetResourceValue(const std::string& resource_name, uint8_t data_type,
uint32_t data_value);
+ Builder& SetResourceValue(const std::string& resource_name, uint8_t data_type,
+ const std::string& data_string_value);
+
WARN_UNUSED Result<FabricatedOverlay> Build();
private:
@@ -48,6 +51,7 @@ struct FabricatedOverlay {
std::string resource_name;
DataType data_type;
DataValue data_value;
+ std::string data_string_value;
};
std::string package_name_;
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index a0202dfee473..414aa064ada7 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -40,6 +40,7 @@ using DataValue = uint32_t; // Res_value::data
struct TargetValue {
DataType data_type;
DataValue data_value;
+ std::string data_string_value;
};
namespace utils {
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 8352dbb7b619..4d49674efce3 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -65,7 +65,13 @@ FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetOverlayable(const std
FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
const std::string& resource_name, uint8_t data_type, uint32_t data_value) {
- entries_.emplace_back(Entry{resource_name, data_type, data_value});
+ entries_.emplace_back(Entry{resource_name, data_type, data_value, ""});
+ return *this;
+}
+
+FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
+ const std::string& resource_name, uint8_t data_type, const std::string& data_string_value) {
+ entries_.emplace_back(Entry{resource_name, data_type, 0, data_string_value});
return *this;
}
@@ -111,7 +117,8 @@ Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
entry = type->second.insert(std::make_pair(entry_name.to_string(), TargetValue())).first;
}
- entry->second = TargetValue{res_entry.data_type, res_entry.data_value};
+ entry->second = TargetValue{
+ res_entry.data_type, res_entry.data_value, res_entry.data_string_value};
}
pb::FabricatedOverlay overlay_pb;
diff --git a/core/api/current.txt b/core/api/current.txt
index b4f75d4fb193..10957ff8bb58 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -78,6 +78,7 @@ package android {
field public static final String CHANGE_WIFI_MULTICAST_STATE = "android.permission.CHANGE_WIFI_MULTICAST_STATE";
field public static final String CHANGE_WIFI_STATE = "android.permission.CHANGE_WIFI_STATE";
field public static final String CLEAR_APP_CACHE = "android.permission.CLEAR_APP_CACHE";
+ field public static final String CONFIGURE_WIFI_DISPLAY = "android.permission.CONFIGURE_WIFI_DISPLAY";
field public static final String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
field public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES";
field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
@@ -191,6 +192,7 @@ package android {
field public static final String SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE = "android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE";
field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
field public static final String TRANSMIT_IR = "android.permission.TRANSMIT_IR";
+ field public static final String TURN_SCREEN_ON = "android.permission.TURN_SCREEN_ON";
field public static final String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT";
field public static final String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
field public static final String UPDATE_PACKAGES_WITHOUT_USER_ACTION = "android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION";
@@ -9008,7 +9010,8 @@ package android.companion {
method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
method public void requestNotificationAccess(android.content.ComponentName);
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
- method public void startSystemDataTransfer(int) throws android.companion.DeviceNotAssociatedException;
+ method @Deprecated public void startSystemDataTransfer(int) throws android.companion.DeviceNotAssociatedException;
+ method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
@@ -9034,6 +9037,9 @@ package android.companion {
field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
}
+ public class CompanionException extends java.lang.RuntimeException {
+ }
+
public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable {
}
@@ -18154,7 +18160,7 @@ package android.hardware.camera2.params {
}
public final class Capability {
- ctor public Capability(int, int, int, float, float);
+ ctor public Capability(int, @NonNull android.util.Size, @NonNull android.util.Range<java.lang.Float>);
method @NonNull public android.util.Size getMaxStreamingSize();
method public int getMode();
method @NonNull public android.util.Range<java.lang.Float> getZoomRatioRange();
@@ -18169,12 +18175,17 @@ package android.hardware.camera2.params {
}
public final class DeviceStateSensorOrientationMap {
- ctor public DeviceStateSensorOrientationMap(@NonNull long[]);
method public int getSensorOrientation(long);
field public static final long FOLDED = 4L; // 0x4L
field public static final long NORMAL = 0L; // 0x0L
}
+ public static final class DeviceStateSensorOrientationMap.Builder {
+ ctor public DeviceStateSensorOrientationMap.Builder();
+ method @NonNull public android.hardware.camera2.params.DeviceStateSensorOrientationMap.Builder addOrientationForState(long, long);
+ method @NonNull public android.hardware.camera2.params.DeviceStateSensorOrientationMap build();
+ }
+
public final class DynamicRangeProfiles {
ctor public DynamicRangeProfiles(@NonNull long[]);
method @NonNull public java.util.Set<java.lang.Long> getProfileCaptureRequestConstraints(long);
@@ -31900,7 +31911,7 @@ package android.os {
method public android.os.PowerManager.WakeLock newWakeLock(int, String);
method @RequiresPermission(android.Manifest.permission.REBOOT) public void reboot(@Nullable String);
method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
- field @Deprecated public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
+ field @Deprecated @RequiresPermission(value=android.Manifest.permission.TURN_SCREEN_ON, conditional=true) public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
field public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
field public static final String ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED = "android.os.action.LOW_POWER_STANDBY_ENABLED_CHANGED";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index e1ce5bd71423..8a0acd509727 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -100,7 +100,6 @@ package android {
field public static final String COMPANION_APPROVE_WIFI_CONNECTIONS = "android.permission.COMPANION_APPROVE_WIFI_CONNECTIONS";
field public static final String CONFIGURE_DISPLAY_BRIGHTNESS = "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
field public static final String CONFIGURE_INTERACT_ACROSS_PROFILES = "android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES";
- field public static final String CONFIGURE_WIFI_DISPLAY = "android.permission.CONFIGURE_WIFI_DISPLAY";
field @Deprecated public static final String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL";
field public static final String CONNECTIVITY_USE_RESTRICTED_NETWORKS = "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
field public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index cb64173b7809..125f5e4e1a21 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1906,6 +1906,7 @@ public class AppOpsManager {
OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER,
OP_SCHEDULE_EXACT_ALARM,
OP_MANAGE_MEDIA,
+ OP_TURN_SCREEN_ON,
};
/**
@@ -2362,7 +2363,7 @@ public class AppOpsManager {
null,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
- null, // no permission for turning the screen on
+ Manifest.permission.TURN_SCREEN_ON,
Manifest.permission.GET_ACCOUNTS,
null, // no permission for running in background
null, // no permission for changing accessibility volume
@@ -2491,7 +2492,7 @@ public class AppOpsManager {
null, // MOCK_LOCATION
null, // READ_EXTERNAL_STORAGE
null, // WRITE_EXTERNAL_STORAGE
- null, // TURN_ON_SCREEN
+ null, // TURN_SCREEN_ON
null, // GET_ACCOUNTS
null, // RUN_IN_BACKGROUND
UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME
@@ -2619,7 +2620,7 @@ public class AppOpsManager {
null, // MOCK_LOCATION
null, // READ_EXTERNAL_STORAGE
null, // WRITE_EXTERNAL_STORAGE
- null, // TURN_ON_SCREEN
+ null, // TURN_SCREEN_ON
null, // GET_ACCOUNTS
null, // RUN_IN_BACKGROUND
null, // AUDIO_ACCESSIBILITY_VOLUME
@@ -2746,7 +2747,7 @@ public class AppOpsManager {
AppOpsManager.MODE_ERRORED, // MOCK_LOCATION
AppOpsManager.MODE_ALLOWED, // READ_EXTERNAL_STORAGE
AppOpsManager.MODE_ALLOWED, // WRITE_EXTERNAL_STORAGE
- AppOpsManager.MODE_ALLOWED, // TURN_SCREEN_ON
+ AppOpsManager.MODE_ERRORED, // TURN_SCREEN_ON
AppOpsManager.MODE_ALLOWED, // GET_ACCOUNTS
AppOpsManager.MODE_ALLOWED, // RUN_IN_BACKGROUND
AppOpsManager.MODE_ALLOWED, // AUDIO_ACCESSIBILITY_VOLUME
@@ -7563,10 +7564,15 @@ public class AppOpsManager {
@SystemApi
@RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
public @NonNull List<AppOpsManager.PackageOps> getPackagesForOps(@Nullable String[] ops) {
- final int opCount = ops.length;
- final int[] opCodes = new int[opCount];
- for (int i = 0; i < opCount; i++) {
- opCodes[i] = sOpStrToOp.get(ops[i]);
+ final int[] opCodes;
+ if (ops != null) {
+ final int opCount = ops.length;
+ opCodes = new int[opCount];
+ for (int i = 0; i < opCount; i++) {
+ opCodes[i] = sOpStrToOp.get(ops[i]);
+ }
+ } else {
+ opCodes = null;
}
final List<AppOpsManager.PackageOps> result = getPackagesForOps(opCodes);
return (result != null) ? result : Collections.emptyList();
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/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index b90408d4395e..904db5f86c02 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -518,7 +518,7 @@ public class SecurityLog {
/**
* Indicates that an event occurred as the device attempted to connect to
- * a WiFi network. The log entry contains the following information about the
+ * a managed WiFi network. The log entry contains the following information about the
* event, encapsulated in an {@link Object} array and accessible via
* {@link SecurityEvent#getData()}:
* <li> [0] Last 2 octets of the network BSSID ({@code String}, in the form "xx:xx:xx:xx:AA:BB")
@@ -530,7 +530,7 @@ public class SecurityLog {
public static final int TAG_WIFI_CONNECTION = SecurityLogTags.SECURITY_WIFI_CONNECTION;
/**
- * Indicates that the device disconnects from a connected WiFi network.
+ * Indicates that the device disconnects from a managed WiFi network.
* The log entry contains the following information about the
* event, encapsulated in an {@link Object} array and accessible via
* {@link SecurityEvent#getData()}:
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 14c671f32c21..1b51faf3d429 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -38,15 +38,23 @@ import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.net.MacAddress;
import android.os.Handler;
+import android.os.OutcomeReceiver;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.util.ExceptionUtils;
import android.util.Log;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;
+import libcore.io.IoUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -273,6 +281,9 @@ public final class CompanionDeviceManager {
@GuardedBy("mListeners")
private final ArrayList<OnAssociationsChangedListenerProxy> mListeners = new ArrayList<>();
+ @GuardedBy("mTransports")
+ private final SparseArray<Transport> mTransports = new SparseArray<>();
+
/** @hide */
public CompanionDeviceManager(
@Nullable ICompanionDeviceManager service, @NonNull Context context) {
@@ -800,6 +811,36 @@ public final class CompanionDeviceManager {
}
}
+ /** {@hide} */
+ public final void attachSystemDataTransport(int associationId, @NonNull InputStream in,
+ @NonNull OutputStream out) throws DeviceNotAssociatedException {
+ synchronized (mTransports) {
+ if (mTransports.contains(associationId)) {
+ detachSystemDataTransport(associationId);
+ }
+
+ try {
+ final Transport transport = new Transport(associationId, in, out);
+ mTransports.put(associationId, transport);
+ transport.start();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to attach transport", e);
+ }
+ }
+ }
+
+ /** {@hide} */
+ public final void detachSystemDataTransport(int associationId)
+ throws DeviceNotAssociatedException {
+ synchronized (mTransports) {
+ final Transport transport = mTransports.get(associationId);
+ if (transport != null) {
+ mTransports.delete(associationId);
+ transport.stop();
+ }
+ }
+ }
+
/**
* Associates given device with given app for the given user directly, without UI prompt.
*
@@ -924,12 +965,44 @@ public final class CompanionDeviceManager {
* @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
* of the companion device recorded by CompanionDeviceManager
* @throws DeviceNotAssociatedException Exception if the companion device is not associated
+ *
+ * @deprecated Use {@link #startSystemDataTransfer(int, Executor, OutcomeReceiver)} instead.
*/
+ @Deprecated
@UserHandleAware
public void startSystemDataTransfer(int associationId) throws DeviceNotAssociatedException {
try {
mService.startSystemDataTransfer(mContext.getOpPackageName(), mContext.getUserId(),
- associationId);
+ associationId, null);
+ } catch (RemoteException e) {
+ ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Start system data transfer which has been previously approved by the user.
+ *
+ * <p>Before calling this method, the app needs to make sure there's a communication channel
+ * between two devices, and has prompted user consent dialogs built by one of these methods:
+ * {@link #buildPermissionTransferUserConsentIntent(int)}.
+ * The transfer may fail if the communication channel is disconnected during the transfer.</p>
+ *
+ * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
+ * of the companion device recorded by CompanionDeviceManager
+ * @param executor The executor which will be used to invoke the result callback.
+ * @param result The callback to notify the app of the result of the system data transfer.
+ * @throws DeviceNotAssociatedException Exception if the companion device is not associated
+ */
+ @UserHandleAware
+ public void startSystemDataTransfer(
+ int associationId,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, CompanionException> result)
+ throws DeviceNotAssociatedException {
+ try {
+ mService.startSystemDataTransfer(mContext.getOpPackageName(), mContext.getUserId(),
+ associationId, new SystemDataTransferCallbackProxy(executor, result));
} catch (RemoteException e) {
ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
throw e.rethrowFromSystemServer();
@@ -1004,4 +1077,114 @@ public final class CompanionDeviceManager {
mExecutor.execute(() -> mListener.onAssociationsChanged(associations));
}
}
+
+ private static class SystemDataTransferCallbackProxy extends ISystemDataTransferCallback.Stub {
+ private final Executor mExecutor;
+ private final OutcomeReceiver<Void, CompanionException> mCallback;
+
+ private SystemDataTransferCallbackProxy(Executor executor,
+ OutcomeReceiver<Void, CompanionException> callback) {
+ mExecutor = executor;
+ mCallback = callback;
+ }
+
+ @Override
+ public void onResult() {
+ mExecutor.execute(() -> mCallback.onResult(null));
+ }
+
+ @Override
+ public void onError(String error) {
+ mExecutor.execute(() -> mCallback.onError(new CompanionException(error)));
+ }
+ }
+
+ /**
+ * Representation of an active system data transport.
+ * <p>
+ * Internally uses two threads to shuttle bidirectional data between a
+ * remote device and a {@code socketpair} that the system is listening to.
+ * This design ensures that data payloads are transported efficiently
+ * without adding Binder traffic contention.
+ */
+ private class Transport {
+ private final int mAssociationId;
+ private final InputStream mRemoteIn;
+ private final OutputStream mRemoteOut;
+
+ private InputStream mLocalIn;
+ private OutputStream mLocalOut;
+
+ private volatile boolean mStopped;
+
+ public Transport(int associationId, InputStream remoteIn, OutputStream remoteOut) {
+ mAssociationId = associationId;
+ mRemoteIn = remoteIn;
+ mRemoteOut = remoteOut;
+ }
+
+ public void start() throws IOException {
+ final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair();
+ mLocalIn = new ParcelFileDescriptor.AutoCloseInputStream(pair[0]);
+ mLocalOut = new ParcelFileDescriptor.AutoCloseOutputStream(pair[0]);
+
+ try {
+ mService.attachSystemDataTransport(mContext.getPackageName(),
+ mContext.getUserId(), mAssociationId, pair[1]);
+ } catch (RemoteException e) {
+ throw new IOException("Failed to configure transport", e);
+ }
+
+ new Thread(() -> {
+ try {
+ copyWithFlushing(mLocalIn, mRemoteOut);
+ } catch (IOException e) {
+ if (!mStopped) {
+ Log.w(LOG_TAG, "Trouble during outgoing transport", e);
+ stop();
+ }
+ }
+ }).start();
+ new Thread(() -> {
+ try {
+ copyWithFlushing(mRemoteIn, mLocalOut);
+ } catch (IOException e) {
+ if (!mStopped) {
+ Log.w(LOG_TAG, "Trouble during incoming transport", e);
+ stop();
+ }
+ }
+ }).start();
+ }
+
+ public void stop() {
+ mStopped = true;
+
+ IoUtils.closeQuietly(mRemoteIn);
+ IoUtils.closeQuietly(mRemoteOut);
+ IoUtils.closeQuietly(mLocalIn);
+ IoUtils.closeQuietly(mLocalOut);
+
+ try {
+ mService.detachSystemDataTransport(mContext.getPackageName(),
+ mContext.getUserId(), mAssociationId);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "Failed to detach transport", e);
+ }
+ }
+
+ /**
+ * Copy all data from the first stream to the second stream, flushing
+ * after every write to ensure that we quickly deliver all pending data.
+ */
+ private void copyWithFlushing(@NonNull InputStream in, @NonNull OutputStream out)
+ throws IOException {
+ byte[] buffer = new byte[8192];
+ int c;
+ while ((c = in.read(buffer)) != -1) {
+ out.write(buffer, 0, c);
+ out.flush();
+ }
+ }
+ }
}
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 791fc2aaa2cb..83d2713ea114 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -23,11 +23,14 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.app.Service;
+import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -201,6 +204,45 @@ public abstract class CompanionDeviceService extends Service {
}
/**
+ * Attach the given bidirectional communication streams to be used for
+ * transporting system data between associated devices.
+ * <p>
+ * The companion service providing these streams is responsible for ensuring
+ * that all data is transported accurately and in-order between the two
+ * devices, including any fragmentation and re-assembly when carried over a
+ * size-limited transport.
+ * <p>
+ * As an example, it's valid to provide streams obtained from a
+ * {@link BluetoothSocket} to this method, since {@link BluetoothSocket}
+ * meets the API contract described above.
+ *
+ * @param associationId id of the associated device
+ * @param in already connected stream of data incoming from remote
+ * associated device
+ * @param out already connected stream of data outgoing to remote associated
+ * device
+ * @hide
+ */
+ public final void attachSystemDataTransport(int associationId, @NonNull InputStream in,
+ @NonNull OutputStream out) throws DeviceNotAssociatedException {
+ getSystemService(CompanionDeviceManager.class)
+ .attachSystemDataTransport(associationId, in, out);
+ }
+
+ /**
+ * Detach any bidirectional communication streams previously configured
+ * through {@link #attachSystemDataTransport}.
+ *
+ * @param associationId id of the associated device
+ * @hide
+ */
+ public final void detachSystemDataTransport(int associationId)
+ throws DeviceNotAssociatedException {
+ getSystemService(CompanionDeviceManager.class)
+ .detachSystemDataTransport(associationId);
+ }
+
+ /**
* Called by system whenever a device associated with this app is connected.
*
* @param associationInfo A record for the companion device.
diff --git a/core/java/android/companion/CompanionException.java b/core/java/android/companion/CompanionException.java
new file mode 100644
index 000000000000..fb78e8df446e
--- /dev/null
+++ b/core/java/android/companion/CompanionException.java
@@ -0,0 +1,29 @@
+/*
+ * 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.companion;
+
+import android.annotation.NonNull;
+
+/**
+ * {@code CompanionException} can be thrown during the companion system data transfer process.
+ */
+public class CompanionException extends RuntimeException {
+ /** @hide */
+ public CompanionException(@NonNull String message) {
+ super(message);
+ }
+}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 085fd1b4c388..42f908396799 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -19,6 +19,7 @@ package android.companion;
import android.app.PendingIntent;
import android.companion.IAssociationRequestCallback;
import android.companion.IOnAssociationsChangedListener;
+import android.companion.ISystemDataTransferCallback;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.content.ComponentName;
@@ -75,5 +76,10 @@ interface ICompanionDeviceManager {
PendingIntent buildPermissionTransferUserConsentIntent(String callingPackage, int userId,
int associationId);
- void startSystemDataTransfer(String packageName, int userId, int associationId);
+ void startSystemDataTransfer(String packageName, int userId, int associationId,
+ in ISystemDataTransferCallback callback);
+
+ void attachSystemDataTransport(String packageName, int userId, int associationId, in ParcelFileDescriptor fd);
+
+ void detachSystemDataTransport(String packageName, int userId, int associationId);
}
diff --git a/core/java/android/companion/ISystemDataTransferCallback.aidl b/core/java/android/companion/ISystemDataTransferCallback.aidl
new file mode 100644
index 000000000000..1ae5376942e2
--- /dev/null
+++ b/core/java/android/companion/ISystemDataTransferCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing per missions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+/** @hide */
+interface ISystemDataTransferCallback {
+ oneway void onResult();
+
+ oneway void onError(String error);
+} \ No newline at end of file
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fce23cf6819a..97da2daf6e59 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3990,7 +3990,7 @@ public abstract class Context {
* sockets and networks.
* <dt> {@link #WIFI_SERVICE} ("wifi")
* <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of Wi-Fi
- * connectivity. On releases before NYC, it should only be obtained from an application
+ * connectivity. On releases before Android 7, it should only be obtained from an application
* context, and not from any other derived context to avoid memory leaks within the calling
* process.
* <dt> {@link #WIFI_AWARE_SERVICE} ("wifiaware")
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/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index d62b47b34dcb..5efc1f9f7cca 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -88,7 +88,7 @@ public class FabricatedOverlay {
}
/**
- * Sets the value of
+ * Sets the value of the fabricated overlay
*
* @param resourceName name of the target resource to overlay (in the form
* [package]:type/entry)
@@ -106,6 +106,25 @@ public class FabricatedOverlay {
return this;
}
+ /**
+ * Sets the value of the fabricated overlay
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param dataType the data type of the new value
+ * @param value the string representing the new value
+ *
+ * @see android.util.TypedValue#type
+ */
+ public Builder setResourceValue(@NonNull String resourceName, int dataType, String value) {
+ final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+ entry.resourceName = resourceName;
+ entry.dataType = dataType;
+ entry.stringData = value;
+ mEntries.add(entry);
+ return this;
+ }
+
/** Builds an immutable fabricated overlay. */
public FabricatedOverlay build() {
final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 20a4fdf658c6..10d6f2d6d04b 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -542,14 +542,17 @@ public class ApkLiteParseUtils {
int minVer = DEFAULT_MIN_SDK_VERSION;
String minCode = null;
+ boolean minAssigned = false;
int targetVer = DEFAULT_TARGET_SDK_VERSION;
String targetCode = null;
if (!TextUtils.isEmpty(minSdkVersionString)) {
try {
minVer = Integer.parseInt(minSdkVersionString);
+ minAssigned = true;
} catch (NumberFormatException ignored) {
minCode = minSdkVersionString;
+ minAssigned = !TextUtils.isEmpty(minCode);
}
}
@@ -558,7 +561,7 @@ public class ApkLiteParseUtils {
targetVer = Integer.parseInt(targetSdkVersionString);
} catch (NumberFormatException ignored) {
targetCode = targetSdkVersionString;
- if (minCode == null) {
+ if (!minAssigned) {
minCode = targetCode;
}
}
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 216c9c26424d..db0cac3eb9c7 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -170,8 +170,8 @@ public final class SQLiteConnectionPool implements Closeable {
// If timeout is set, setup idle connection handler
// In case of MAX_VALUE - idle connections are never closed
if (mConfiguration.idleConnectionTimeoutMs != Long.MAX_VALUE) {
- setupIdleConnectionHandler(Looper.getMainLooper(),
- mConfiguration.idleConnectionTimeoutMs);
+ setupIdleConnectionHandler(
+ Looper.getMainLooper(), mConfiguration.idleConnectionTimeoutMs, null);
}
}
@@ -425,7 +425,7 @@ public final class SQLiteConnectionPool implements Closeable {
mAvailablePrimaryConnection = connection;
}
wakeConnectionWaitersLocked();
- } else if (mAvailableNonPrimaryConnections.size() >= mMaxConnectionPoolSize - 1) {
+ } else if (mAvailableNonPrimaryConnections.size() >= mMaxConnectionPoolSize) {
closeConnectionAndLogExceptionsLocked(connection);
} else {
if (recycleConnectionLocked(connection, status)) {
@@ -456,6 +456,11 @@ public final class SQLiteConnectionPool implements Closeable {
return true;
}
+ @VisibleForTesting
+ public boolean hasAnyAvailableNonPrimaryConnection() {
+ return mAvailableNonPrimaryConnections.size() > 0;
+ }
+
/**
* Returns true if the session should yield the connection due to
* contention over available database connections.
@@ -1061,9 +1066,11 @@ public final class SQLiteConnectionPool implements Closeable {
* Set up the handler based on the provided looper and timeout.
*/
@VisibleForTesting
- public void setupIdleConnectionHandler(Looper looper, long timeoutMs) {
+ public void setupIdleConnectionHandler(
+ Looper looper, long timeoutMs, Runnable onAllConnectionsIdle) {
synchronized (mLock) {
- mIdleConnectionHandler = new IdleConnectionHandler(looper, timeoutMs);
+ mIdleConnectionHandler =
+ new IdleConnectionHandler(looper, timeoutMs, onAllConnectionsIdle);
}
}
@@ -1228,10 +1235,12 @@ public final class SQLiteConnectionPool implements Closeable {
private class IdleConnectionHandler extends Handler {
private final long mTimeout;
+ private final Runnable mOnAllConnectionsIdle;
- IdleConnectionHandler(Looper looper, long timeout) {
+ IdleConnectionHandler(Looper looper, long timeout, Runnable onAllConnectionsIdle) {
super(looper);
mTimeout = timeout;
+ this.mOnAllConnectionsIdle = onAllConnectionsIdle;
}
@Override
@@ -1247,6 +1256,9 @@ public final class SQLiteConnectionPool implements Closeable {
+ " after " + mTimeout);
}
}
+ if (mOnAllConnectionsIdle != null) {
+ mOnAllConnectionsIdle.run();
+ }
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 468e6041eb73..ee12df547291 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -88,8 +88,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Map;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -1734,12 +1734,12 @@ public class CameraMetadataNative implements Parcelable {
int height = maxSizes[3 * i + 2];
if (mode != CameraMetadata.CONTROL_EXTENDED_SCENE_MODE_DISABLED
&& j < numExtendedSceneModeZoomRanges) {
- capabilities[i] = new Capability(mode, width, height, zoomRanges[2 * j],
- zoomRanges[2 * j + 1]);
+ capabilities[i] = new Capability(mode, new Size(width, height),
+ new Range<Float>(zoomRanges[2 * j], zoomRanges[2 * j + 1]));
j++;
} else {
- capabilities[i] = new Capability(mode, width, height, modeOffMinZoomRatio,
- modeOffMaxZoomRatio);
+ capabilities[i] = new Capability(mode, new Size(width, height),
+ new Range<Float>(modeOffMinZoomRatio, modeOffMaxZoomRatio));
}
}
diff --git a/core/java/android/hardware/camera2/params/Capability.java b/core/java/android/hardware/camera2/params/Capability.java
index fd71c82817f5..a015f720241f 100644
--- a/core/java/android/hardware/camera2/params/Capability.java
+++ b/core/java/android/hardware/camera2/params/Capability.java
@@ -40,10 +40,8 @@ public final class Capability {
public static final int COUNT = 3;
private final int mMode;
- private final int mMaxStreamingWidth;
- private final int mMaxStreamingHeight;
- private final float mMinZoomRatio;
- private final float mMaxZoomRatio;
+ private final Size mMaxStreamingSize;
+ private final Range<Float> mZoomRatioRange;
/**
* Create a new Capability object.
@@ -57,29 +55,30 @@ public final class Capability {
* objects during normal use of the camera API.</p>
*
* @param mode supported mode for a camera capability.
- * @param maxStreamingWidth The width of the maximum streaming size for this mode
- * @param maxStreamingHeight The height of the maximum streaming size for this mode
- * @param minZoomRatio the minimum zoom ratio this mode supports
- * @param maxZoomRatio the maximum zoom ratio this mode supports
+ * @param maxStreamingSize The maximum streaming size for this mode
+ * @param zoomRatioRange the minimum/maximum zoom ratio this mode supports
*
- * @throws IllegalArgumentException if any of the argument is not valid
+ * @throws IllegalArgumentException if any of the arguments are not valid
*/
- public Capability(int mode, int maxStreamingWidth, int maxStreamingHeight,
- float minZoomRatio, float maxZoomRatio) {
+ public Capability(int mode, @NonNull Size maxStreamingSize,
+ @NonNull Range<Float> zoomRatioRange) {
mMode = mode;
- mMaxStreamingWidth = checkArgumentNonnegative(maxStreamingWidth,
- "maxStreamingWidth must be nonnegative");
- mMaxStreamingHeight = checkArgumentNonnegative(maxStreamingHeight,
- "maxStreamingHeight must be nonnegative");
+ checkArgumentNonnegative(maxStreamingSize.getWidth(),
+ "maxStreamingSize.getWidth() must be nonnegative");
+ checkArgumentNonnegative(maxStreamingSize.getHeight(),
+ "maxStreamingSize.getHeight() must be nonnegative");
+ mMaxStreamingSize = maxStreamingSize;
- if (minZoomRatio > maxZoomRatio) {
- throw new IllegalArgumentException("minZoomRatio " + minZoomRatio
- + " is greater than maxZoomRatio " + maxZoomRatio);
+ if (zoomRatioRange.getLower() > zoomRatioRange.getUpper()) {
+ throw new IllegalArgumentException("zoomRatioRange.getLower() "
+ + zoomRatioRange.getLower() + " is greater than zoomRatioRange.getUpper() "
+ + zoomRatioRange.getUpper());
}
- mMinZoomRatio = checkArgumentPositive(minZoomRatio,
- "minZoomRatio must be positive");
- mMaxZoomRatio = checkArgumentPositive(maxZoomRatio,
- "maxZoomRatio must be positive");
+ checkArgumentPositive(zoomRatioRange.getLower(),
+ "zoomRatioRange.getLower() must be positive");
+ checkArgumentPositive(zoomRatioRange.getUpper(),
+ "zoomRatioRange.getUpper() must be positive");
+ mZoomRatioRange = zoomRatioRange;
}
/**
@@ -100,7 +99,7 @@ public final class Capability {
* @return a new {@link Size} with non-negative width and height
*/
public @NonNull Size getMaxStreamingSize() {
- return new Size(mMaxStreamingWidth, mMaxStreamingHeight);
+ return mMaxStreamingSize;
}
/**
@@ -109,7 +108,7 @@ public final class Capability {
* @return The supported zoom ratio range supported by this capability
*/
public @NonNull Range<Float> getZoomRatioRange() {
- return new Range<Float>(mMinZoomRatio, mMaxZoomRatio);
+ return mZoomRatioRange;
}
@@ -132,10 +131,8 @@ public final class Capability {
if (obj instanceof Capability) {
final Capability other = (Capability) obj;
return (mMode == other.mMode
- && mMaxStreamingWidth == other.mMaxStreamingWidth
- && mMaxStreamingHeight == other.mMaxStreamingHeight
- && mMinZoomRatio == other.mMinZoomRatio
- && mMaxZoomRatio == other.mMaxZoomRatio);
+ && mMaxStreamingSize.equals(other.mMaxStreamingSize)
+ && mZoomRatioRange.equals(other.mZoomRatioRange));
}
return false;
}
@@ -145,8 +142,9 @@ public final class Capability {
*/
@Override
public int hashCode() {
- return HashCodeHelpers.hashCode(mMode, mMaxStreamingWidth, mMaxStreamingHeight,
- mMinZoomRatio, mMaxZoomRatio);
+ return HashCodeHelpers.hashCode(mMode, mMaxStreamingSize.getWidth(),
+ mMaxStreamingSize.getHeight(), mZoomRatioRange.getLower(),
+ mZoomRatioRange.getUpper());
}
/**
@@ -158,7 +156,7 @@ public final class Capability {
@Override
public String toString() {
return String.format("(mode:%d, maxStreamingSize:%d x %d, zoomRatio: %f-%f)",
- mMode, mMaxStreamingWidth, mMaxStreamingHeight, mMinZoomRatio,
- mMaxZoomRatio);
+ mMode, mMaxStreamingSize.getWidth(), mMaxStreamingSize.getHeight(),
+ mZoomRatioRange.getLower(), mZoomRatioRange.getUpper());
}
}
diff --git a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
index c4c8eb9ef6f0..b9a327b5b61d 100644
--- a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
+++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
@@ -18,11 +18,13 @@ package android.hardware.camera2.params;
import android.annotation.LongDef;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.utils.HashCodeHelpers;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
@@ -68,24 +70,20 @@ public final class DeviceStateSensorOrientationMap {
FOLDED })
public @interface DeviceState {};
- private final HashMap<Long, Integer> mDeviceStateOrientationMap = new HashMap<>();
+ private final HashMap<Long, Integer> mDeviceStateOrientationMap;
/**
* Create a new immutable DeviceStateOrientationMap instance.
*
- * <p>The array is a list of pairs of elements (angle, deviceState):</p>
+ * <p>The array is a list of pairs of elements (deviceState, angle):</p>
*
- * <code>[angle0, state0, angle1, state1,..., angleN, stateN]</code>
+ * <code>[state0, angle0, state1, angle1,..., stateN, angleN]</code>
*
* <p>Each pair describes the camera sensor orientation when the device is in the
* matching deviceState. The angle is in degrees, and must be a multiple of 90.</p>
*
* <p>This constructor takes over the array; do not write to the array afterwards.</p>
*
- * <p>This constructor is public to allow for easier application testing by
- * creating custom object instances. It's not necessary to construct these
- * objects during normal use of the camera API.</p>
- *
* @param elements
* An array of elements describing the map
*
@@ -94,9 +92,12 @@ public final class DeviceStateSensorOrientationMap {
* invalid element values
* @throws NullPointerException
* if {@code elements} is {@code null}
+ *
+ * @hide
*/
public DeviceStateSensorOrientationMap(@NonNull final long[] elements) {
mElements = Objects.requireNonNull(elements, "elements must not be null");
+ mDeviceStateOrientationMap = new HashMap<>();
if ((elements.length % 2) != 0) {
throw new IllegalArgumentException("Device state sensor orientation map length " +
elements.length + " is not even!");
@@ -113,6 +114,20 @@ public final class DeviceStateSensorOrientationMap {
}
/**
+ * Used by the Builder only.
+ *
+ * @hide
+ */
+ private DeviceStateSensorOrientationMap(@NonNull final ArrayList<Long> elements,
+ @NonNull final HashMap<Long, Integer> deviceStateOrientationMap) {
+ mElements = new long[elements.size()];
+ for (int i = 0; i < elements.size(); i++) {
+ mElements[i] = elements.get(i);
+ }
+ mDeviceStateOrientationMap = deviceStateOrientationMap;
+ }
+
+ /**
* Return the logical camera sensor orientation given a specific device fold state.
*
* @param deviceState Device fold state
@@ -163,4 +178,58 @@ public final class DeviceStateSensorOrientationMap {
}
private final long[] mElements;
+
+ /**
+ * Builds a DeviceStateSensorOrientationMap object.
+ *
+ * <p>This builder is public to allow for easier application testing by
+ * creating custom object instances. It's not necessary to construct these
+ * objects during normal use of the camera API.</p>
+ */
+ public static final class Builder {
+ public Builder() {
+ // Empty
+ }
+
+ /**
+ * Add a sensor orientation for a given device state.
+ *
+ * <p>Each pair of deviceState and angle describes the camera sensor orientation when the
+ * device is in the matching deviceState. The angle is in degrees, and must be a multiple
+ * of 90.</p>
+ *
+ * @param deviceState The deviceState.
+ * @param angle The orientation angle in degrees.
+ * @return This builder.
+ *
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ public @NonNull Builder addOrientationForState(long deviceState, long angle) {
+ if (angle % 90 != 0) {
+ throw new IllegalArgumentException("Sensor orientation not divisible by 90: "
+ + angle);
+ }
+ mDeviceStateOrientationMap.put(deviceState, Math.toIntExact(angle));
+ mElements.add(deviceState);
+ mElements.add(angle);
+ return this;
+ }
+
+ /**
+ * Returns an instance of <code>DeviceStateSensorOrientationMap</code> created from the
+ * fields set on this builder.
+ *
+ * @return A DeviceStateSensorOrientationMap.
+ */
+ public @NonNull DeviceStateSensorOrientationMap build() {
+ if (mElements.size() == 0) {
+ throw new IllegalStateException("Cannot build a DeviceStateSensorOrientationMap"
+ + " with zero elements.");
+ }
+ return new DeviceStateSensorOrientationMap(mElements, mDeviceStateOrientationMap);
+ }
+
+ private final ArrayList<Long> mElements = new ArrayList<>();
+ private final HashMap<Long, Integer> mDeviceStateOrientationMap = new HashMap<>();
+ }
}
diff --git a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
index d06bc1d2053d..3e41d63b0365 100644
--- a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
+++ b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
@@ -122,6 +122,9 @@ public final class HdmiPlaybackClient extends HdmiClient {
}
private IHdmiControlCallback getCallbackWrapper(final OneTouchPlayCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("OneTouchPlayCallback cannot be null.");
+ }
return new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int result) {
@@ -131,6 +134,9 @@ public final class HdmiPlaybackClient extends HdmiClient {
}
private IHdmiControlCallback getCallbackWrapper(final DisplayStatusCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("DisplayStatusCallback cannot be null.");
+ }
return new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int status) {
diff --git a/core/java/android/hardware/radio/OWNERS b/core/java/android/hardware/radio/OWNERS
index ea4421eae96a..d2bdd643b0a2 100644
--- a/core/java/android/hardware/radio/OWNERS
+++ b/core/java/android/hardware/radio/OWNERS
@@ -1,2 +1,3 @@
-twasilczyk@google.com
-randolphs@google.com
+xuweilin@google.com
+oscarazu@google.com
+keunyoung@google.com
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index 3a042a5dee4d..e8e4fc988937 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -26,7 +26,6 @@ import android.os.Parcelable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -173,38 +172,63 @@ public final class ProgramList implements AutoCloseable {
}
}
- void apply(@NonNull Chunk chunk) {
+ void apply(Chunk chunk) {
+ List<ProgramSelector.Identifier> removedList = new ArrayList<>();
+ List<ProgramSelector.Identifier> changedList = new ArrayList<>();
+ List<ProgramList.ListCallback> listCallbacksCopied;
+ List<OnCompleteListener> onCompleteListenersCopied = new ArrayList<>();
synchronized (mLock) {
if (mIsClosed) return;
mIsComplete = false;
+ listCallbacksCopied = new ArrayList<>(mListCallbacks);
if (chunk.isPurge()) {
- new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id));
+ for (ProgramSelector.Identifier id : mPrograms.keySet()) {
+ removeLocked(id, removedList);
+ }
}
- chunk.getRemoved().stream().forEach(id -> removeLocked(id));
- chunk.getModified().stream().forEach(info -> putLocked(info));
+ chunk.getRemoved().stream().forEach(id -> removeLocked(id, removedList));
+ chunk.getModified().stream().forEach(info -> putLocked(info, changedList));
if (chunk.isComplete()) {
mIsComplete = true;
- mOnCompleteListeners.forEach(cb -> cb.onComplete());
+ onCompleteListenersCopied = new ArrayList<>(mOnCompleteListeners);
+ }
+ }
+
+ for (int i = 0; i < removedList.size(); i++) {
+ for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
+ listCallbacksCopied.get(cbIndex).onItemRemoved(removedList.get(i));
+ }
+ }
+ for (int i = 0; i < changedList.size(); i++) {
+ for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
+ listCallbacksCopied.get(cbIndex).onItemChanged(changedList.get(i));
+ }
+ }
+ if (chunk.isComplete()) {
+ for (int cbIndex = 0; cbIndex < onCompleteListenersCopied.size(); cbIndex++) {
+ onCompleteListenersCopied.get(cbIndex).onComplete();
}
}
}
- private void putLocked(@NonNull RadioManager.ProgramInfo value) {
+ private void putLocked(RadioManager.ProgramInfo value,
+ List<ProgramSelector.Identifier> changedIdentifierList) {
ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
mPrograms.put(Objects.requireNonNull(key), value);
ProgramSelector.Identifier sel = value.getSelector().getPrimaryId();
- mListCallbacks.forEach(cb -> cb.onItemChanged(sel));
+ changedIdentifierList.add(sel);
}
- private void removeLocked(@NonNull ProgramSelector.Identifier key) {
+ private void removeLocked(ProgramSelector.Identifier key,
+ List<ProgramSelector.Identifier> removedIdentifierList) {
RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
if (removed == null) return;
ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId();
- mListCallbacks.forEach(cb -> cb.onItemRemoved(sel));
+ removedIdentifierList.add(sel);
}
/**
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index b47e92d09989..8f241722a445 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -195,18 +195,13 @@ 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) args.arg2;
+ final InputConnection ic = (InputConnection) args.arg2;
final EditorInfo info = (EditorInfo) args.arg3;
final ImeOnBackInvokedDispatcher imeDispatcher =
(ImeOnBackInvokedDispatcher) args.arg4;
- final CancellationGroup cancellationGroup = (CancellationGroup) args.arg5;
final boolean restarting = args.argi1 == 1;
@InputMethodNavButtonFlags
final int navButtonFlags = args.argi2;
- final InputConnection ic = remoteIc != null
- ? new RemoteInputConnection(mTarget, remoteIc, cancellationGroup)
- : null;
- info.makeCompatible(mTargetSdkVersion);
inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken,
navButtonFlags, imeDispatcher);
args.recycle();
@@ -358,14 +353,19 @@ class IInputMethodWrapper extends IInputMethod.Stub
Log.e(TAG, "startInput must be called after bindInput.");
mCancellationGroup = new CancellationGroup();
}
+
+ editorInfo.makeCompatible(mTargetSdkVersion);
+
+ final InputConnection ic = inputConnection == null ? null
+ : new RemoteInputConnection(mTarget, inputConnection, mCancellationGroup);
+
final SomeArgs args = SomeArgs.obtain();
args.arg1 = startInputToken;
- args.arg2 = inputConnection;
+ args.arg2 = ic;
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));
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 13ca2c34b27e..5123a9f45176 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -183,6 +183,9 @@ public final class PowerManager {
/**
* Wake lock flag: Turn the screen on when the wake lock is acquired.
* <p>
+ * This flag requires {@link android.Manifest.permission#TURN_SCREEN_ON} for apps targeting
+ * Android version {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and higher.
+ * </p><p>
* Normally wake locks don't actually wake the device, they just cause the screen to remain on
* once it's already on. This flag will cause the device to wake up when the wake lock is
* acquired.
@@ -195,10 +198,10 @@ public final class PowerManager {
*
* @deprecated Most applications should use {@link android.R.attr#turnScreenOn} or
* {@link android.app.Activity#setTurnScreenOn(boolean)} instead, as this prevents the previous
- * foreground app from being resumed first when the screen turns on. Note that this flag may
- * require a permission in the future.
+ * foreground app from being resumed first when the screen turns on.
*/
@Deprecated
+ @RequiresPermission(value = android.Manifest.permission.TURN_SCREEN_ON, conditional = true)
public static final int ACQUIRE_CAUSES_WAKEUP = 0x10000000;
/**
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index e02c45ddf5a8..3a5666283ae2 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -58,6 +58,18 @@
},
{
"file_patterns": [
+ "Parcel\\.java",
+ "[^/]*Bundle[^/]*\\.java"
+ ],
+ "name": "FrameworksMockingCoreTests",
+ "options": [
+ { "include-filter": "android.os.BundleRecyclingTest"},
+ { "exclude-annotation": "androidx.test.filters.FlakyTest" },
+ { "exclude-annotation": "org.junit.Ignore" }
+ ]
+ },
+ {
+ "file_patterns": [
"BatteryUsageStats[^/]*\\.java",
"PowerComponents\\.java",
"[^/]*BatteryConsumer[^/]*\\.java"
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index 5bed32cb0438..06930bb32090 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -152,6 +152,9 @@ public final class VibrationAttributes implements Parcelable {
/**
* Flag requesting vibration effect to be played even under limited interruptions.
+ *
+ * <p>Only privileged apps can ignore user settings that limit interruptions, and this
+ * flag will be ignored otherwise.
*/
public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 0x1;
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index b783f6b8fd51..7d17093b2707 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -16,6 +16,7 @@
package android.service.voice;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import android.annotation.CallbackExecutor;
@@ -38,6 +39,7 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.Region;
+import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -1043,15 +1045,33 @@ public class VoiceInteractionSession implements KeyEvent.Callback, ComponentCall
}
public VoiceInteractionSession(Context context, Handler handler) {
- mContext = context;
mHandlerCaller = new HandlerCaller(context, handler.getLooper(),
mCallbacks, true);
+ mContext = createWindowContextIfNeeded(context);
}
public Context getContext() {
return mContext;
}
+ private Context createWindowContextIfNeeded(Context context) {
+ try {
+ if (!context.isUiContext()) {
+ DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+ if (displayManager != null) {
+ return context.createWindowContext(
+ displayManager.getDisplay(DEFAULT_DISPLAY),
+ WindowManager.LayoutParams.TYPE_VOICE_INTERACTION,
+ /* options= */ null);
+ }
+ }
+ return context;
+ } catch (RuntimeException e) {
+ Log.w(TAG, "Fail to createWindowContext, Exception = " + e);
+ return context;
+ }
+ }
+
void addRequest(Request req) {
synchronized (this) {
mActiveRequests.put(req.mInterface.asBinder(), req);
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/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 bc7da13b66db..657c0b7801b5 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -805,6 +805,7 @@ public final class ViewRootImpl implements ViewParent,
private final ViewRootRectTracker mUnrestrictedKeepClearRectsTracker =
new ViewRootRectTracker(v -> v.collectUnrestrictedPreferKeepClearRects());
private boolean mHasPendingKeepClearAreaChange;
+ private Rect mKeepClearAccessibilityFocusRect;
private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection;
@@ -4865,13 +4866,27 @@ public final class ViewRootImpl implements ViewParent,
mHandler.sendEmptyMessage(MSG_KEEP_CLEAR_RECTS_CHANGED);
}
- void keepClearRectsChanged() {
+ private void updateKeepClearForAccessibilityFocusRect() {
+ if (mViewConfiguration.isPreferKeepClearForFocusEnabled()) {
+ if (mKeepClearAccessibilityFocusRect == null) {
+ mKeepClearAccessibilityFocusRect = new Rect();
+ }
+ boolean hasAccessibilityFocus =
+ getAccessibilityFocusedRect(mKeepClearAccessibilityFocusRect);
+ if (!hasAccessibilityFocus) {
+ mKeepClearAccessibilityFocusRect.setEmpty();
+ }
+ mHandler.obtainMessage(MSG_KEEP_CLEAR_RECTS_CHANGED, 1, 0).sendToTarget();
+ }
+ }
+
+ void keepClearRectsChanged(boolean accessibilityFocusRectChanged) {
boolean restrictedKeepClearRectsChanged = mKeepClearRectsTracker.computeChanges();
boolean unrestrictedKeepClearRectsChanged =
mUnrestrictedKeepClearRectsTracker.computeChanges();
- if ((restrictedKeepClearRectsChanged || unrestrictedKeepClearRectsChanged)
- && mView != null) {
+ if ((restrictedKeepClearRectsChanged || unrestrictedKeepClearRectsChanged
+ || accessibilityFocusRectChanged) && mView != null) {
mHasPendingKeepClearAreaChange = true;
// Only report keep clear areas immediately if they have not been reported recently
if (!mHandler.hasMessages(MSG_REPORT_KEEP_CLEAR_RECTS)) {
@@ -4888,10 +4903,16 @@ public final class ViewRootImpl implements ViewParent,
}
mHasPendingKeepClearAreaChange = false;
- final List<Rect> restrictedKeepClearRects = mKeepClearRectsTracker.getLastComputedRects();
+ List<Rect> restrictedKeepClearRects = mKeepClearRectsTracker.getLastComputedRects();
final List<Rect> unrestrictedKeepClearRects =
mUnrestrictedKeepClearRectsTracker.getLastComputedRects();
+ if (mKeepClearAccessibilityFocusRect != null
+ && !mKeepClearAccessibilityFocusRect.isEmpty()) {
+ restrictedKeepClearRects = new ArrayList<>(restrictedKeepClearRects);
+ restrictedKeepClearRects.add(mKeepClearAccessibilityFocusRect);
+ }
+
try {
mWindowSession.reportKeepClearAreasChanged(mWindow, restrictedKeepClearRects,
unrestrictedKeepClearRects);
@@ -5091,6 +5112,7 @@ public final class ViewRootImpl implements ViewParent,
// Set the new focus host and node.
mAccessibilityFocusedHost = view;
mAccessibilityFocusedVirtualView = node;
+ updateKeepClearForAccessibilityFocusRect();
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.invalidateRoot();
@@ -5679,7 +5701,7 @@ public final class ViewRootImpl implements ViewParent,
systemGestureExclusionChanged();
} break;
case MSG_KEEP_CLEAR_RECTS_CHANGED: {
- keepClearRectsChanged();
+ keepClearRectsChanged(/* accessibilityFocusRectChanged= */ msg.arg1 == 1);
} break;
case MSG_REPORT_KEEP_CLEAR_RECTS: {
reportKeepClearAreasChanged();
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 50f3e0cfbcf3..a9fe34a9c0ff 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1421,14 +1421,38 @@ public final class InputMethodManager {
}
/**
- * Returns {@code true} if currently selected IME supports Stylus handwriting.
+ * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled.
* If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
* called and Stylus touch should continue as normal touch input.
* @see #startStylusHandwriting(View)
*/
public boolean isStylusHandwritingAvailable() {
+ return isStylusHandwritingAvailableAsUser(UserHandle.myUserId());
+ }
+
+ /**
+ * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled for
+ * the given userId.
+ * If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
+ * called and Stylus touch should continue as normal touch input.
+ * @see #startStylusHandwriting(View)
+ * @param userId user ID to query.
+ * @hide
+ */
+ public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+ final Context fallbackContext = ActivityThread.currentApplication();
+ if (fallbackContext == null) {
+ return false;
+ }
+ if (Settings.Global.getInt(fallbackContext.getContentResolver(),
+ Settings.Global.STYLUS_HANDWRITING_ENABLED, 0) == 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Stylus handwriting is not enabled in settings.");
+ }
+ return false;
+ }
try {
- return mService.isStylusHandwritingAvailable();
+ return mService.isStylusHandwritingAvailableAsUser(userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1645,6 +1669,7 @@ public final class InputMethodManager {
mCurId = null;
mCurMethod = null; // for @UnsupportedAppUsage
mCurrentInputMethodSession = null;
+ mIsInputMethodSuppressingSpellChecker = false;
}
/**
@@ -1707,7 +1732,6 @@ public final class InputMethodManager {
@GuardedBy("mH")
void finishInputLocked() {
mVirtualDisplayToScreenMatrix = null;
- mIsInputMethodSuppressingSpellChecker = false;
setNextServedViewLocked(null);
if (getServedViewLocked() != null) {
if (DEBUG) {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 184e7bca963b..1cb96b15b8ef 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -242,6 +242,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public static final int CHOICE_MODE_MULTIPLE_MODAL = 3;
/**
+ * When flinging the stretch towards scrolling content, it should destretch quicker than the
+ * fling would normally do. The visual effect of flinging the stretch looks strange as little
+ * appears to happen at first and then when the stretch disappears, the content starts
+ * scrolling quickly.
+ */
+ private static final float FLING_DESTRETCH_FACTOR = 4f;
+
+ /**
* The thread that created this view.
*/
private final Thread mOwnerThread;
@@ -4216,9 +4224,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// fling further.
boolean flingVelocity = Math.abs(initialVelocity) > mMinimumVelocity;
if (flingVelocity && !mEdgeGlowTop.isFinished()) {
- mEdgeGlowTop.onAbsorb(initialVelocity);
+ if (shouldAbsorb(mEdgeGlowTop, initialVelocity)) {
+ mEdgeGlowTop.onAbsorb(initialVelocity);
+ } else {
+ if (mFlingRunnable == null) {
+ mFlingRunnable = new FlingRunnable();
+ }
+ mFlingRunnable.start(-initialVelocity);
+ }
} else if (flingVelocity && !mEdgeGlowBottom.isFinished()) {
- mEdgeGlowBottom.onAbsorb(-initialVelocity);
+ if (shouldAbsorb(mEdgeGlowBottom, -initialVelocity)) {
+ mEdgeGlowBottom.onAbsorb(-initialVelocity);
+ } else {
+ if (mFlingRunnable == null) {
+ mFlingRunnable = new FlingRunnable();
+ }
+ mFlingRunnable.start(-initialVelocity);
+ }
} else if (flingVelocity
&& !((mFirstPosition == 0
&& firstChildTop == contentTop - mOverscrollDistance)
@@ -4301,6 +4323,60 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ /**
+ * Returns true if edgeEffect should call onAbsorb() with veclocity or false if it should
+ * animate with a fling. It will animate with a fling if the velocity will remove the
+ * EdgeEffect through its normal operation.
+ *
+ * @param edgeEffect The EdgeEffect that might absorb the velocity.
+ * @param velocity The velocity of the fling motion
+ * @return true if the velocity should be absorbed or false if it should be flung.
+ */
+ private boolean shouldAbsorb(EdgeEffect edgeEffect, int velocity) {
+ if (velocity > 0) {
+ return true;
+ }
+ float distance = edgeEffect.getDistance() * getHeight();
+
+ // This is flinging without the spring, so let's see if it will fling past the overscroll
+ if (mFlingRunnable == null) {
+ mFlingRunnable = new FlingRunnable();
+ }
+ float flingDistance = mFlingRunnable.getSplineFlingDistance(-velocity);
+
+ return flingDistance < distance;
+ }
+
+ /**
+ * Used by consumeFlingInHorizontalStretch() and consumeFlinInVerticalStretch() for
+ * consuming deltas from EdgeEffects
+ * @param unconsumed The unconsumed delta that the EdgeEffets may consume
+ * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
+ */
+ private int consumeFlingInStretch(int unconsumed) {
+ if (unconsumed < 0 && mEdgeGlowTop != null && mEdgeGlowTop.getDistance() != 0f) {
+ int size = getHeight();
+ float deltaDistance = unconsumed * FLING_DESTRETCH_FACTOR / size;
+ int consumed = Math.round(size / FLING_DESTRETCH_FACTOR
+ * mEdgeGlowTop.onPullDistance(deltaDistance, 0.5f));
+ if (consumed != unconsumed) {
+ mEdgeGlowTop.finish();
+ }
+ return unconsumed - consumed;
+ }
+ if (unconsumed > 0 && mEdgeGlowBottom != null && mEdgeGlowBottom.getDistance() != 0f) {
+ int size = getHeight();
+ float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
+ int consumed = Math.round(-size / FLING_DESTRETCH_FACTOR
+ * mEdgeGlowBottom.onPullDistance(deltaDistance, 0.5f));
+ if (consumed != unconsumed) {
+ mEdgeGlowBottom.finish();
+ }
+ return unconsumed - consumed;
+ }
+ return unconsumed;
+ }
+
private boolean shouldDisplayEdgeEffects() {
return getOverScrollMode() != OVER_SCROLL_NEVER;
}
@@ -4783,6 +4859,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mScroller = new OverScroller(getContext());
}
+ float getSplineFlingDistance(int velocity) {
+ return (float) mScroller.getSplineFlingDistance(velocity);
+ }
+
// Use AbsListView#fling(int) instead
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
void start(int initialVelocity) {
@@ -4905,6 +4985,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
if (mItemCount == 0 || getChildCount() == 0) {
+ mEdgeGlowBottom.onRelease();
+ mEdgeGlowTop.onRelease();
endFling();
return;
}
@@ -4915,7 +4997,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// Flip sign to convert finger direction to list items direction
// (e.g. finger moving down means list is moving towards the top)
- int delta = mLastFlingY - y;
+ int delta = consumeFlingInStretch(mLastFlingY - y);
// Pretend that each frame of a fling scroll is a touch scroll
if (delta > 0) {
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 2dbfd7e5b2e2..1d6778b8a4a9 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -77,6 +77,14 @@ public class HorizontalScrollView extends FrameLayout {
private static final String TAG = "HorizontalScrollView";
+ /**
+ * When flinging the stretch towards scrolling content, it should destretch quicker than the
+ * fling would normally do. The visual effect of flinging the stretch looks strange as little
+ * appears to happen at first and then when the stretch disappears, the content starts
+ * scrolling quickly.
+ */
+ private static final float FLING_DESTRETCH_FACTOR = 4f;
+
private long mLastScroll;
private final Rect mTempRect = new Rect();
@@ -1456,18 +1464,19 @@ public class HorizontalScrollView extends FrameLayout {
int oldY = mScrollY;
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
+ int deltaX = consumeFlingInStretch(x - oldX);
- if (oldX != x || oldY != y) {
+ if (deltaX != 0 || oldY != y) {
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
- overScrollBy(x - oldX, y - oldY, oldX, oldY, range, 0,
+ overScrollBy(deltaX, y - oldY, oldX, oldY, range, 0,
mOverflingDistance, 0, false);
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
- if (canOverscroll) {
+ if (canOverscroll && deltaX != 0) {
if (x < 0 && oldX >= 0) {
mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
} else if (x > range && oldX <= range) {
@@ -1483,6 +1492,36 @@ public class HorizontalScrollView extends FrameLayout {
}
/**
+ * Used by consumeFlingInHorizontalStretch() and consumeFlinInVerticalStretch() for
+ * consuming deltas from EdgeEffects
+ * @param unconsumed The unconsumed delta that the EdgeEffets may consume
+ * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
+ */
+ private int consumeFlingInStretch(int unconsumed) {
+ if (unconsumed > 0 && mEdgeGlowLeft != null && mEdgeGlowLeft.getDistance() != 0f) {
+ int size = getWidth();
+ float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
+ int consumed = Math.round(-size / FLING_DESTRETCH_FACTOR
+ * mEdgeGlowLeft.onPullDistance(deltaDistance, 0.5f));
+ if (consumed != unconsumed) {
+ mEdgeGlowLeft.finish();
+ }
+ return unconsumed - consumed;
+ }
+ if (unconsumed < 0 && mEdgeGlowRight != null && mEdgeGlowRight.getDistance() != 0f) {
+ int size = getWidth();
+ float deltaDistance = unconsumed * FLING_DESTRETCH_FACTOR / size;
+ int consumed = Math.round(size / FLING_DESTRETCH_FACTOR
+ * mEdgeGlowRight.onPullDistance(deltaDistance, 0.5f));
+ if (consumed != unconsumed) {
+ mEdgeGlowRight.finish();
+ }
+ return unconsumed - consumed;
+ }
+ return unconsumed;
+ }
+
+ /**
* Scrolls the view to the given child.
*
* @param child the View to scroll to
@@ -1746,11 +1785,23 @@ public class HorizontalScrollView extends FrameLayout {
int maxScroll = Math.max(0, right - width);
+ boolean shouldFling = false;
if (mScrollX == 0 && !mEdgeGlowLeft.isFinished()) {
- mEdgeGlowLeft.onAbsorb(-velocityX);
+ if (shouldAbsorb(mEdgeGlowLeft, -velocityX)) {
+ mEdgeGlowLeft.onAbsorb(-velocityX);
+ } else {
+ shouldFling = true;
+ }
} else if (mScrollX == maxScroll && !mEdgeGlowRight.isFinished()) {
- mEdgeGlowRight.onAbsorb(velocityX);
+ if (shouldAbsorb(mEdgeGlowRight, velocityX)) {
+ mEdgeGlowRight.onAbsorb(velocityX);
+ } else {
+ shouldFling = true;
+ }
} else {
+ shouldFling = true;
+ }
+ if (shouldFling) {
mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
maxScroll, 0, 0, width / 2, 0);
@@ -1774,6 +1825,27 @@ public class HorizontalScrollView extends FrameLayout {
}
/**
+ * Returns true if edgeEffect should call onAbsorb() with veclocity or false if it should
+ * animate with a fling. It will animate with a fling if the velocity will remove the
+ * EdgeEffect through its normal operation.
+ *
+ * @param edgeEffect The EdgeEffect that might absorb the velocity.
+ * @param velocity The velocity of the fling motion
+ * @return true if the velocity should be absorbed or false if it should be flung.
+ */
+ private boolean shouldAbsorb(EdgeEffect edgeEffect, int velocity) {
+ if (velocity > 0) {
+ return true;
+ }
+ float distance = edgeEffect.getDistance() * getWidth();
+
+ // This is flinging without the spring, so let's see if it will fling past the overscroll
+ float flingDistance = (float) mScroller.getSplineFlingDistance(-velocity);
+
+ return flingDistance < distance;
+ }
+
+ /**
* {@inheritDoc}
*
* <p>This version also clamps the scrolling to the bounds of our child.
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index 1683878cd8b2..d00fc1c89d8f 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -527,6 +527,10 @@ public class OverScroller {
Math.signum(yvel) == Math.signum(dy);
}
+ double getSplineFlingDistance(int velocity) {
+ return mScrollerY.getSplineFlingDistance(velocity);
+ }
+
static class SplineOverScroller {
// Initial position
private int mStart;
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 2acd50c9e169..84b6f65ffea6 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -85,6 +85,14 @@ public class ScrollView extends FrameLayout {
private static final String TAG = "ScrollView";
+ /**
+ * When flinging the stretch towards scrolling content, it should destretch quicker than the
+ * fling would normally do. The visual effect of flinging the stretch looks strange as little
+ * appears to happen at first and then when the stretch disappears, the content starts
+ * scrolling quickly.
+ */
+ private static final float FLING_DESTRETCH_FACTOR = 4f;
+
@UnsupportedAppUsage
private long mLastScroll;
@@ -1488,18 +1496,19 @@ public class ScrollView extends FrameLayout {
int oldY = mScrollY;
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
+ int deltaY = consumeFlingInStretch(y - oldY);
- if (oldX != x || oldY != y) {
+ if (oldX != x || deltaY != 0) {
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
- overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
+ overScrollBy(x - oldX, deltaY, oldX, oldY, 0, range,
0, mOverflingDistance, false);
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
- if (canOverscroll) {
+ if (canOverscroll && deltaY != 0) {
if (y < 0 && oldY >= 0) {
mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
} else if (y > range && oldY <= range) {
@@ -1521,6 +1530,36 @@ public class ScrollView extends FrameLayout {
}
/**
+ * Used by consumeFlingInHorizontalStretch() and consumeFlinInVerticalStretch() for
+ * consuming deltas from EdgeEffects
+ * @param unconsumed The unconsumed delta that the EdgeEffets may consume
+ * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
+ */
+ private int consumeFlingInStretch(int unconsumed) {
+ if (unconsumed > 0 && mEdgeGlowTop != null && mEdgeGlowTop.getDistance() != 0f) {
+ int size = getHeight();
+ float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
+ int consumed = Math.round(-size / FLING_DESTRETCH_FACTOR
+ * mEdgeGlowTop.onPullDistance(deltaDistance, 0.5f));
+ if (consumed != unconsumed) {
+ mEdgeGlowTop.finish();
+ }
+ return unconsumed - consumed;
+ }
+ if (unconsumed < 0 && mEdgeGlowBottom != null && mEdgeGlowBottom.getDistance() != 0f) {
+ int size = getHeight();
+ float deltaDistance = unconsumed * FLING_DESTRETCH_FACTOR / size;
+ int consumed = Math.round(size / FLING_DESTRETCH_FACTOR
+ * mEdgeGlowBottom.onPullDistance(deltaDistance, 0.5f));
+ if (consumed != unconsumed) {
+ mEdgeGlowBottom.finish();
+ }
+ return unconsumed - consumed;
+ }
+ return unconsumed;
+ }
+
+ /**
* Scrolls the view to the given child.
*
* @param child the View to scroll to
@@ -1803,14 +1842,43 @@ public class ScrollView extends FrameLayout {
fling(velocityY);
} else if (!consumed) {
if (!mEdgeGlowTop.isFinished()) {
- mEdgeGlowTop.onAbsorb(-velocityY);
+ if (shouldAbsorb(mEdgeGlowTop, -velocityY)) {
+ mEdgeGlowTop.onAbsorb(-velocityY);
+ } else {
+ fling(velocityY);
+ }
} else if (!mEdgeGlowBottom.isFinished()) {
- mEdgeGlowBottom.onAbsorb(velocityY);
+ if (shouldAbsorb(mEdgeGlowBottom, velocityY)) {
+ mEdgeGlowBottom.onAbsorb(velocityY);
+ } else {
+ fling(velocityY);
+ }
}
}
}
}
+ /**
+ * Returns true if edgeEffect should call onAbsorb() with veclocity or false if it should
+ * animate with a fling. It will animate with a fling if the velocity will remove the
+ * EdgeEffect through its normal operation.
+ *
+ * @param edgeEffect The EdgeEffect that might absorb the velocity.
+ * @param velocity The velocity of the fling motion
+ * @return true if the velocity should be absorbed or false if it should be flung.
+ */
+ private boolean shouldAbsorb(EdgeEffect edgeEffect, int velocity) {
+ if (velocity > 0) {
+ return true;
+ }
+ float distance = edgeEffect.getDistance() * getHeight();
+
+ // This is flinging without the spring, so let's see if it will fling past the overscroll
+ float flingDistance = (float) mScroller.getSplineFlingDistance(-velocity);
+
+ return flingDistance < distance;
+ }
+
@UnsupportedAppUsage
private void endDrag() {
mIsBeingDragged = false;
diff --git a/core/java/android/window/SizeConfigurationBuckets.java b/core/java/android/window/SizeConfigurationBuckets.java
index f474f0a76cc6..998bec0892ae 100644
--- a/core/java/android/window/SizeConfigurationBuckets.java
+++ b/core/java/android/window/SizeConfigurationBuckets.java
@@ -104,24 +104,15 @@ public final class SizeConfigurationBuckets implements Parcelable {
/**
* Get the changes between two configurations but don't count changes in sizes if they don't
* cross boundaries that are important to the app.
- *
- * This is a static helper to deal with null `buckets`. When no buckets have been specified,
- * this actually filters out all 3 size-configs. This is legacy behavior.
*/
public static int filterDiff(int diff, @NonNull Configuration oldConfig,
@NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets buckets) {
- final boolean nonSizeLayoutFieldsUnchanged =
- areNonSizeLayoutFieldsUnchanged(oldConfig.screenLayout, newConfig.screenLayout);
if (buckets == null) {
- // Only unflip CONFIG_SCREEN_LAYOUT if non-size-related attributes of screen layout do
- // not change.
- if (nonSizeLayoutFieldsUnchanged) {
- return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
- | CONFIG_SCREEN_LAYOUT);
- } else {
- return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE);
- }
+ return diff;
}
+
+ final boolean nonSizeLayoutFieldsUnchanged =
+ areNonSizeLayoutFieldsUnchanged(oldConfig.screenLayout, newConfig.screenLayout);
if ((diff & CONFIG_SCREEN_SIZE) != 0) {
final boolean crosses = buckets.crossesHorizontalSizeThreshold(oldConfig.screenWidthDp,
newConfig.screenWidthDp)
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index c2b69717bc37..3732ea5abaa5 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -68,13 +68,6 @@ interface IBatteryStats {
@EnforcePermission("BATTERY_STATS")
List<BatteryUsageStats> getBatteryUsageStats(in List<BatteryUsageStatsQuery> queries);
- @UnsupportedAppUsage
- @EnforcePermission("BATTERY_STATS")
- byte[] getStatistics();
-
- @EnforcePermission("BATTERY_STATS")
- ParcelFileDescriptor getStatisticsStream(boolean updateAll);
-
// Return true if we see the battery as currently charging.
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
@RequiresNoPermission
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index cbaac5f33eb4..44997b4a9c30 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -548,6 +548,12 @@ public final class SystemUiDeviceConfigFlags {
*/
public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
+ /**
+ * (boolean) Whether the task manager should show a stop button if the app is allowlisted
+ * by the user.
+ */
+ public static final String TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS =
+ "show_stop_button_for_user_allowlisted_apps";
/**
* (boolean) Whether the clipboard overlay is enabled.
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 52fd7fec932e..22340c6b0c55 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -75,6 +75,8 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
@@ -206,6 +208,8 @@ public class InteractionJankMonitor {
public static final int CUJ_SETTINGS_TOGGLE = 57;
public static final int CUJ_SHADE_DIALOG_OPEN = 58;
public static final int CUJ_USER_DIALOG_OPEN = 59;
+ public static final int CUJ_TASKBAR_EXPAND = 60;
+ public static final int CUJ_TASKBAR_COLLAPSE = 61;
private static final int NO_STATSD_LOGGING = -1;
@@ -274,6 +278,8 @@ public class InteractionJankMonitor {
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE,
};
private static volatile InteractionJankMonitor sInstance;
@@ -354,6 +360,8 @@ public class InteractionJankMonitor {
CUJ_SETTINGS_TOGGLE,
CUJ_SHADE_DIALOG_OPEN,
CUJ_USER_DIALOG_OPEN,
+ CUJ_TASKBAR_EXPAND,
+ CUJ_TASKBAR_COLLAPSE
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -792,6 +800,10 @@ public class InteractionJankMonitor {
return "SHADE_DIALOG_OPEN";
case CUJ_USER_DIALOG_OPEN:
return "USER_DIALOG_OPEN";
+ case CUJ_TASKBAR_EXPAND:
+ return "TASKBAR_EXPAND";
+ case CUJ_TASKBAR_COLLAPSE:
+ return "TASKBAR_COLLAPSE";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 508e4450d0f9..d550fef968a8 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -91,5 +91,5 @@ interface IInputMethodManager {
/** Start Stylus handwriting session **/
void startStylusHandwriting(in IInputMethodClient client);
/** Returns {@code true} if currently selected IME supports Stylus handwriting. */
- boolean isStylusHandwritingAvailable();
+ boolean isStylusHandwritingAvailableAsUser(int userId);
}
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 4af28ea24361..1520ea5c6831 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -41,7 +41,12 @@ struct {
static JNIEnv* getenv(JavaVM* vm) {
JNIEnv* env;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ auto result = vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
+ if (result == JNI_EDETACHED) {
+ if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
+ }
+ } else if (result != JNI_OK) {
LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
}
return env;
@@ -60,28 +65,22 @@ public:
}
~TransactionHangCallbackWrapper() {
- if (mTransactionHangObject) {
- getenv()->DeleteGlobalRef(mTransactionHangObject);
+ if (mTransactionHangObject != nullptr) {
+ getenv(mVm)->DeleteGlobalRef(mTransactionHangObject);
mTransactionHangObject = nullptr;
}
}
void onTransactionHang(bool isGpuHang) {
if (mTransactionHangObject) {
- getenv()->CallVoidMethod(mTransactionHangObject,
- gTransactionHangCallback.onTransactionHang, isGpuHang);
+ getenv(mVm)->CallVoidMethod(mTransactionHangObject,
+ gTransactionHangCallback.onTransactionHang, isGpuHang);
}
}
private:
JavaVM* mVm;
jobject mTransactionHangObject;
-
- JNIEnv* getenv() {
- JNIEnv* env;
- mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
- return env;
- }
};
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d7aa174ddfef..c2fcd1d0612a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1702,7 +1702,7 @@
android:description="@string/permdesc_useBiometric"
android:protectionLevel="normal" />
- <!-- ======================================================================= -->
+ <!-- ====================================================================== -->
<!-- Permissions for posting notifications -->
<!-- ====================================================================== -->
<eat-comment />
@@ -2468,6 +2468,15 @@
android:description="@string/permdesc_transmitIr"
android:protectionLevel="normal" />
+ <!-- Allows an app to turn on the screen on, e.g. with
+ {@link android.os.PowerManager#ACQUIRE_CAUSES_WAKEUP}.
+ <p>Intended to only be used by home automation apps.
+ -->
+ <permission android:name="android.permission.TURN_SCREEN_ON"
+ android:label="@string/permlab_turnScreenOn"
+ android:description="@string/permdesc_turnScreenOn"
+ android:protectionLevel="normal|appop" />
+
<!-- ==================================================== -->
<!-- Permissions related to changing audio settings -->
<!-- ==================================================== -->
@@ -4858,11 +4867,10 @@
<permission android:name="android.permission.DISABLE_INPUT_DEVICE"
android:protectionLevel="signature" />
- <!-- Allows an application to configure and connect to Wifi displays
- @hide
- @SystemApi -->
+ <!-- Allows an application to configure and connect to Wifi displays -->
<permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|knownSigner"
+ android:knownCerts="@array/wifi_known_signers" />
<!-- Allows an application to control low-level features of Wifi displays
such as opening an RTSP socket. This permission should only be used
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5ddfce0846ed..aef71e435bc6 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1619,6 +1619,11 @@
<string name="permdesc_postNotification">Allows the app to show notifications</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permlab_turnScreenOn">turn on the screen</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permdesc_turnScreenOn">Allows the app to turn on the screen.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
<string name="permlab_useBiometric">use biometric hardware</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
<string name="permdesc_useBiometric">Allows the app to use biometric hardware for authentication</string>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 08135e82e7bd..df3ae0e37547 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -61,7 +61,7 @@
<shortcode country="bg" pattern="\\d{4,5}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}|1988|1490" />
<!-- Bahrain: 1-5 digits (standard system default, not country specific) -->
- <shortcode country="bh" pattern="\\d{1,5}" free="81181" />
+ <shortcode country="bh" pattern="\\d{1,5}" free="81181|85999" />
<!-- Brazil: 1-5 digits (standard system default, not country specific) -->
<shortcode country="br" pattern="\\d{1,5}" free="6000[012]\\d|876|5500|9963|4141|8000" />
@@ -83,7 +83,7 @@
<shortcode country="cn" premium="1066.*" free="1065.*" />
<!-- Colombia: 1-6 digits (not confirmed) -->
- <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739" />
+ <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517" />
<!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
@@ -190,7 +190,7 @@
<shortcode country="mk" pattern="\\d{1,6}" free="129005|122" />
<!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
- <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963" />
+ <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101" />
<!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
<shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" />
diff --git a/core/tests/BroadcastRadioTests/OWNERS b/core/tests/BroadcastRadioTests/OWNERS
index 3e360e7e992c..d2bdd643b0a2 100644
--- a/core/tests/BroadcastRadioTests/OWNERS
+++ b/core/tests/BroadcastRadioTests/OWNERS
@@ -1,3 +1,3 @@
-keunyoung@google.com
+xuweilin@google.com
oscarazu@google.com
-twasilczyk@google.com
+keunyoung@google.com
diff --git a/core/tests/companiontests/src/android/companion/CompanionTestRunner.java b/core/tests/companiontests/src/android/companion/CompanionTestRunner.java
index caa2c685accc..3c59e7d716b0 100644
--- a/core/tests/companiontests/src/android/companion/CompanionTestRunner.java
+++ b/core/tests/companiontests/src/android/companion/CompanionTestRunner.java
@@ -33,6 +33,7 @@ public class CompanionTestRunner extends InstrumentationTestRunner {
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
suite.addTestSuite(BluetoothDeviceFilterUtilsTest.class);
+ suite.addTestSuite(SystemDataTransportTest.class);
return suite;
}
diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
new file mode 100644
index 000000000000..be04b6c91a8a
--- /dev/null
+++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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.companion;
+
+import android.os.SystemClock;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.android.internal.util.HexDump;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Random;
+
+public class SystemDataTransportTest extends InstrumentationTestCase {
+ private static final String TAG = "SystemDataTransportTest";
+
+ private static final int COMMAND_INVALID = 0xF00DCAFE;
+ private static final int COMMAND_PING_V0 = 0x50490000;
+ private static final int COMMAND_PONG_V0 = 0x504F0000;
+
+ private CompanionDeviceManager mCdm;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mCdm = getInstrumentation().getTargetContext()
+ .getSystemService(CompanionDeviceManager.class);
+ }
+
+ public void testPingHandRolled() {
+ // NOTE: These packets are explicitly hand-rolled to verify wire format;
+ // the remainder of the tests are fine using generated packets
+
+ // PING v0 with payload "HELLO WORLD!"
+ final byte[] input = new byte[] {
+ 0x50, 0x49, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0C,
+ 0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x21,
+ };
+ // PONG v0 with payload "HELLO WORLD!"
+ final byte[] expected = new byte[] {
+ 0x50, 0x4F, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0C,
+ 0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x21,
+ };
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(input);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(42, in, out);
+
+ final byte[] actual = waitForByteArray(out, expected.length);
+ assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ }
+
+ public void testPingTrickle() {
+ final byte[] input = generatePacket(COMMAND_PING_V0, TAG);
+ final byte[] expected = generatePacket(COMMAND_PONG_V0, TAG);
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(input);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(42, new TrickleInputStream(in), out);
+
+ final byte[] actual = waitForByteArray(out, expected.length);
+ assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ }
+
+ public void testPingDelay() {
+ final byte[] input = generatePacket(COMMAND_PING_V0, TAG);
+ final byte[] expected = generatePacket(COMMAND_PONG_V0, TAG);
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(input);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(42, new DelayingInputStream(in, 1000),
+ new DelayingOutputStream(out, 1000));
+
+ final byte[] actual = waitForByteArray(out, expected.length);
+ assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ }
+
+ public void testPingGiant() {
+ final byte[] blob = new byte[500_000];
+ new Random().nextBytes(blob);
+
+ final byte[] input = generatePacket(COMMAND_PING_V0, blob);
+ final byte[] expected = generatePacket(COMMAND_PONG_V0, blob);
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(input);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(42, in, out);
+
+ final byte[] actual = waitForByteArray(out, expected.length);
+ assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ }
+
+ public void testMutiplePingPing() {
+ final byte[] input = concat(generatePacket(COMMAND_PING_V0, "red"),
+ generatePacket(COMMAND_PING_V0, "green"));
+ final byte[] expected = concat(generatePacket(COMMAND_PONG_V0, "red"),
+ generatePacket(COMMAND_PONG_V0, "green"));
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(input);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(42, in, out);
+
+ final byte[] actual = waitForByteArray(out, expected.length);
+ assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ }
+
+ public void testMultipleInvalidPing() {
+ final byte[] input = concat(generatePacket(COMMAND_INVALID, "red"),
+ generatePacket(COMMAND_PING_V0, "green"));
+ final byte[] expected = generatePacket(COMMAND_PONG_V0, "green");
+
+ final ByteArrayInputStream in = new ByteArrayInputStream(input);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(42, in, out);
+
+ final byte[] actual = waitForByteArray(out, expected.length);
+ assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ }
+
+ public void testDoubleAttach() {
+ // Connect an empty connection that is stalled out
+ final InputStream in = new EmptyInputStream();
+ final OutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(42, in, out);
+ SystemClock.sleep(1000);
+
+ // Attach a second transport that has some packets; it should disconnect
+ // the first transport and start replying on the second one
+ testPingHandRolled();
+ }
+
+ public static byte[] concat(byte[] a, byte[] b) {
+ return ByteBuffer.allocate(a.length + b.length).put(a).put(b).array();
+ }
+
+ public static byte[] generatePacket(int command, String data) {
+ return generatePacket(command, data.getBytes(StandardCharsets.UTF_8));
+ }
+
+ public static byte[] generatePacket(int command, byte[] data) {
+ return ByteBuffer.allocate(data.length + 8)
+ .putInt(command).putInt(data.length).put(data).array();
+ }
+
+ private static byte[] waitForByteArray(ByteArrayOutputStream out, int size) {
+ int i = 0;
+ while (out.size() < size) {
+ SystemClock.sleep(100);
+ if (i++ % 10 == 0) {
+ Log.w(TAG, "Waiting for data...");
+ }
+ if (i > 100) {
+ fail();
+ }
+ }
+ return out.toByteArray();
+ }
+
+ private static class EmptyInputStream extends InputStream {
+ @Override
+ public int read() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int read(byte b[], int off, int len) throws IOException {
+ // Instead of hanging indefinitely, wait a bit and claim that
+ // nothing was read, without hitting EOF
+ SystemClock.sleep(100);
+ return 0;
+ }
+ }
+
+ private static class DelayingInputStream extends FilterInputStream {
+ private final long mDelay;
+
+ public DelayingInputStream(InputStream in, long delay) {
+ super(in);
+ mDelay = delay;
+ }
+
+ @Override
+ public int read(byte b[], int off, int len) throws IOException {
+ SystemClock.sleep(mDelay);
+ return super.read(b, off, len);
+ }
+ }
+
+ private static class DelayingOutputStream extends FilterOutputStream {
+ private final long mDelay;
+
+ public DelayingOutputStream(OutputStream out, long delay) {
+ super(out);
+ mDelay = delay;
+ }
+
+ @Override
+ public void write(byte b[], int off, int len) throws IOException {
+ SystemClock.sleep(mDelay);
+ super.write(b, off, len);
+ }
+ }
+
+ private static class TrickleInputStream extends FilterInputStream {
+ public TrickleInputStream(InputStream in) {
+ super(in);
+ }
+
+ @Override
+ public int read(byte b[], int off, int len) throws IOException {
+ return super.read(b, off, 1);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java
index f1d27d4a13ab..2b663bdb7861 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteConnectionPoolTest.java
@@ -33,6 +33,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
+import java.util.concurrent.CountDownLatch;
/**
* Tests for {@link SQLiteConnectionPool}
@@ -74,7 +75,7 @@ public class SQLiteConnectionPoolTest {
Log.i(TAG, "Starting " + thread.getName());
thread.start();
SQLiteConnectionPool pool = SQLiteConnectionPool.open(mTestConf);
- pool.setupIdleConnectionHandler(thread.getLooper(), 100);
+ pool.setupIdleConnectionHandler(thread.getLooper(), 100, null);
SQLiteConnection c1 = pool.acquireConnection("pragma user_version", 0, null);
assertEquals("First connection should be returned", 0, c1.getConnectionId());
pool.releaseConnection(c1);
@@ -89,4 +90,31 @@ public class SQLiteConnectionPoolTest {
pool.close();
thread.quit();
}
+
+ @Test
+ public void testNonprimaryConnectionPoolRecycling() throws InterruptedException {
+ HandlerThread thread = new HandlerThread("test-close-idle-connections-thread");
+ thread.start();
+ SQLiteConnectionPool pool = SQLiteConnectionPool.open(mTestConf);
+ CountDownLatch latch = new CountDownLatch(1);
+ Runnable onIdleConnectionTimeout = () -> latch.countDown();
+ pool.setupIdleConnectionHandler(thread.getLooper(), 1, onIdleConnectionTimeout);
+
+ assertTrue("When the pool was just opened there should only be a primary connection.",
+ !pool.hasAnyAvailableNonPrimaryConnection());
+ SQLiteConnection connection = pool.acquireConnection("pragma user_version", 0, null);
+ pool.releaseConnection(connection);
+ assertTrue("First time acquire should will return the primary connection.",
+ !pool.hasAnyAvailableNonPrimaryConnection());
+
+ // Wait for primary connection to time out
+ latch.await();
+
+ // Now that the primary is closed, acquiring again should open a non primary connection
+ connection = pool.acquireConnection("pragma user_version", 0, null);
+ pool.releaseConnection(connection);
+ assertTrue("There should be an available non primary connection in the pool.",
+ pool.hasAnyAvailableNonPrimaryConnection());
+ pool.close();
+ }
}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 8d3ee2a15dce..18da1a4ab13e 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -16,6 +16,7 @@
package android.view.stylus;
+import static android.provider.Settings.Global.STYLUS_HANDWRITING_ENABLED;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
@@ -32,6 +33,7 @@ import android.app.Instrumentation;
import android.content.Context;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
import android.view.HandwritingInitiator;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -67,6 +69,8 @@ public class HandwritingInitiatorTest {
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_BOTTOM_PX = 40;
+ private static final int SETTING_VALUE_ON = 1;
+ private static final int SETTING_VALUE_OFF = 0;
private int mHandwritingSlop = 4;
private static final Rect sHwArea = new Rect(100, 200, 500, 500);
@@ -74,12 +78,21 @@ public class HandwritingInitiatorTest {
private HandwritingInitiator mHandwritingInitiator;
private View mTestView;
private Context mContext;
+ private int mHwInitialState;
+ private boolean mShouldRestoreInitialHwState;
@Before
public void setup() throws Exception {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
mContext = instrumentation.getTargetContext();
+ mHwInitialState = Settings.Global.getInt(mContext.getContentResolver(),
+ STYLUS_HANDWRITING_ENABLED, SETTING_VALUE_OFF);
+ if (mHwInitialState != SETTING_VALUE_ON) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ STYLUS_HANDWRITING_ENABLED, SETTING_VALUE_ON);
+ mShouldRestoreInitialHwState = true;
+ }
String imeId = HandwritingImeService.getImeId();
instrumentation.getUiAutomation().executeShellCommand("ime enable " + imeId);
instrumentation.getUiAutomation().executeShellCommand("ime set " + imeId);
@@ -105,6 +118,11 @@ public class HandwritingInitiatorTest {
@After
public void tearDown() throws Exception {
+ if (mShouldRestoreInitialHwState) {
+ mShouldRestoreInitialHwState = false;
+ Settings.Global.putInt(mContext.getContentResolver(),
+ STYLUS_HANDWRITING_ENABLED, mHwInitialState);
+ }
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.executeShellCommand("ime reset");
}
diff --git a/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java b/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java
new file mode 100644
index 000000000000..7c7649813824
--- /dev/null
+++ b/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Test for verifying {@link android.os.Bundle} recycles the underlying parcel where needed.
+ *
+ * <p>Build/Install/Run:
+ * atest FrameworksMockingCoreTests:android.os.BundleRecyclingTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class BundleRecyclingTest {
+ private Parcel mParcelSpy;
+ private Bundle mBundle;
+
+ @Before
+ public void setUp() throws Exception {
+ setUpBundle(/* hasLazy */ true);
+ }
+
+ @Test
+ public void bundleClear_whenUnparcelledWithoutLazy_recyclesParcelOnce() {
+ setUpBundle(/* hasLazy */ false);
+ // Will unparcel and immediately recycle parcel
+ assertNotNull(mBundle.getString("key"));
+ verify(mParcelSpy, times(1)).recycle();
+ assertFalse(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+ }
+
+ @Test
+ public void bundleClear_whenParcelled_recyclesParcel() {
+ assertTrue(mBundle.isParcelled());
+ verify(mParcelSpy, times(0)).recycle();
+
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenUnparcelledWithLazyValueUnwrapped_recyclesParcel() {
+ // Will unparcel with a lazy value, and immediately unwrap the lazy value,
+ // with no lazy values left at the end of getParcelable
+ assertNotNull(mBundle.getParcelable("key", CustomParcelable.class));
+ verify(mParcelSpy, times(0)).recycle();
+
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenUnparcelledWithLazy_recyclesParcel() {
+ // Will unparcel but keep the CustomParcelable lazy
+ assertFalse(mBundle.isEmpty());
+ verify(mParcelSpy, times(0)).recycle();
+
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenClearedWithSharedParcel_doesNotRecycleParcel() {
+ Bundle copy = new Bundle();
+ copy.putAll(mBundle);
+
+ mBundle.clear();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ copy.clear();
+ assertTrue(copy.isDefinitelyEmpty());
+
+ verify(mParcelSpy, never()).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenClearedWithCopiedParcel_doesNotRecycleParcel() {
+ // Will unparcel but keep the CustomParcelable lazy
+ assertFalse(mBundle.isEmpty());
+
+ Bundle copy = mBundle.deepCopy();
+ copy.putAll(mBundle);
+
+ mBundle.clear();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ copy.clear();
+ assertTrue(copy.isDefinitelyEmpty());
+
+ verify(mParcelSpy, never()).recycle();
+ }
+
+ private void setUpBundle(boolean hasLazy) {
+ AtomicReference<Parcel> parcel = new AtomicReference<>();
+ StaticMockitoSession session = mockitoSession()
+ .strictness(Strictness.STRICT_STUBS)
+ .spyStatic(Parcel.class)
+ .startMocking();
+ doAnswer((Answer<Parcel>) invocationOnSpy -> {
+ Parcel spy = (Parcel) invocationOnSpy.callRealMethod();
+ spyOn(spy);
+ parcel.set(spy);
+ return spy;
+ }).when(() -> Parcel.obtain());
+
+ Bundle bundle = new Bundle();
+ bundle.setClassLoader(getClass().getClassLoader());
+ Parcel p = createBundle(hasLazy);
+ bundle.readFromParcel(p);
+ p.recycle();
+
+ session.finishMocking();
+
+ mParcelSpy = parcel.get();
+ mBundle = bundle;
+ }
+
+ /**
+ * Create a test bundle, parcel it and return the parcel.
+ */
+ private Parcel createBundle(boolean hasLazy) {
+ final Bundle source = new Bundle();
+ if (hasLazy) {
+ source.putParcelable("key", new CustomParcelable(13, "Tiramisu"));
+ } else {
+ source.putString("key", "tiramisu");
+ }
+ return getParcelledBundle(source);
+ }
+
+ /**
+ * Take a bundle, write it to a parcel and return the parcel.
+ */
+ private Parcel getParcelledBundle(Bundle bundle) {
+ final Parcel p = Parcel.obtain();
+ // Don't use p.writeParcelabe(), which would write the creator, which we don't need.
+ bundle.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ return p;
+ }
+
+ private static class CustomParcelable implements Parcelable {
+ public final int integer;
+ public final String string;
+
+ CustomParcelable(int integer, String string) {
+ this.integer = integer;
+ this.string = string;
+ }
+
+ protected CustomParcelable(Parcel in) {
+ integer = in.readInt();
+ string = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(integer);
+ out.writeString(string);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof CustomParcelable)) {
+ return false;
+ }
+ CustomParcelable that = (CustomParcelable) other;
+ return integer == that.integer && string.equals(that.string);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(integer, string);
+ }
+
+ public static final Creator<CustomParcelable> CREATOR = new Creator<CustomParcelable>() {
+ @Override
+ public CustomParcelable createFromParcel(Parcel in) {
+ return new CustomParcelable(in);
+ }
+ @Override
+ public CustomParcelable[] newArray(int size) {
+ return new CustomParcelable[size];
+ }
+ };
+ }
+}
diff --git a/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java b/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java
index fa4aa803c75e..ed857e8fc960 100644
--- a/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java
+++ b/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java
@@ -88,26 +88,15 @@ public class SizeConfigurationBucketsTest {
}
/**
- * Tests that null size configuration buckets unflips the correct configuration flags.
+ * Tests that {@code null} size configuration buckets do not unflip the configuration flags.
*/
@Test
public void testNullSizeConfigurationBuckets() {
- // Check that all 3 size configurations are filtered out of the diff if the buckets are null
- // and non-size attributes of screen layout are unchanged. Add a non-size related config
- // change (i.e. CONFIG_LOCALE) to test that the diff is not set to zero.
final int diff = CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_SCREEN_LAYOUT
| CONFIG_LOCALE;
final int filteredDiffNonSizeLayoutUnchanged = SizeConfigurationBuckets.filterDiff(diff,
Configuration.EMPTY, Configuration.EMPTY, null);
- assertEquals(CONFIG_LOCALE, filteredDiffNonSizeLayoutUnchanged);
-
- // Check that only screen size and smallest screen size are filtered out of the diff if the
- // buckets are null and non-size attributes of screen layout are changed.
- final Configuration newConfig = new Configuration();
- newConfig.screenLayout |= SCREENLAYOUT_ROUND_YES;
- final int filteredDiffNonSizeLayoutChanged = SizeConfigurationBuckets.filterDiff(diff,
- Configuration.EMPTY, newConfig, null);
- assertEquals(CONFIG_SCREEN_LAYOUT | CONFIG_LOCALE, filteredDiffNonSizeLayoutChanged);
+ assertEquals(diff, filteredDiffNonSizeLayoutUnchanged);
}
/**
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 547144f6ce1a..2ce8fac6d520 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -475,12 +475,6 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ResetTargetTaskHelper.java"
},
- "-1635750891": {
- "message": "Received remote change for Display[%d], applied: [%dx%d, rot = %d]",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONFIGURATION",
- "at": "com\/android\/server\/wm\/RemoteDisplayChangeController.java"
- },
"-1633115609": {
"message": "Key dispatch not paused for screen off",
"level": "VERBOSE",
@@ -1711,6 +1705,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-417730399": {
+ "message": "Preparing to sync a window that was already in the sync, so try dropping buffer. win=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/WindowState.java"
+ },
"-415865166": {
"message": "findFocusedWindow: Found new focus @ %s",
"level": "VERBOSE",
@@ -2137,6 +2137,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
+ "-4263657": {
+ "message": "Got a buffer for request id=%d but latest request is id=%d. Since the buffer is out-of-date, drop it. win=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/WindowState.java"
+ },
"3593205": {
"message": "commitVisibility: %s: visible=%b mVisibleRequested=%b",
"level": "VERBOSE",
@@ -2599,6 +2605,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "385237117": {
+ "message": "moveFocusableActivityToTop: already on top and focused, activity=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_FOCUS",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"385595355": {
"message": "Starting animation on %s: type=%d, anim=%s",
"level": "VERBOSE",
@@ -3403,6 +3415,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1239439010": {
+ "message": "moveFocusableActivityToTop: set focused, activity=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_FOCUS",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"1252594551": {
"message": "Window types in WindowContext and LayoutParams.type should match! Type from LayoutParams is %d, but type from WindowContext is %d",
"level": "WARN",
@@ -3877,12 +3895,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowStateAnimator.java"
},
- "1764619787": {
- "message": "Remote change for Display[%d]: timeout reached",
- "level": "VERBOSE",
- "group": "WM_DEBUG_CONFIGURATION",
- "at": "com\/android\/server\/wm\/RemoteDisplayChangeController.java"
- },
"1774661765": {
"message": "Devices still not ready after waiting %d milliseconds before attempting to detect safe mode.",
"level": "WARN",
@@ -3991,12 +4003,6 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1856211951": {
- "message": "moveFocusableActivityToTop: already on top, activity=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_FOCUS",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"1856783490": {
"message": "resumeTopActivity: Restarting %s",
"level": "DEBUG",
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 1629b6ace35d..239621eeed1e 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -40,6 +40,7 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.net.Uri;
import android.os.Build;
+import android.os.Trace;
import android.system.ErrnoException;
import android.system.Os;
import android.util.DisplayMetrics;
@@ -223,13 +224,21 @@ public final class ImageDecoder implements AutoCloseable {
public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
return nCreate(mData, mOffset, mLength, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "ByteArraySource{len=" + mLength + "}";
+ }
}
private static class ByteBufferSource extends Source {
ByteBufferSource(@NonNull ByteBuffer buffer) {
mBuffer = buffer;
+ mLength = mBuffer.limit() - mBuffer.position();
}
+
private final ByteBuffer mBuffer;
+ private final int mLength;
@Override
public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
@@ -241,6 +250,11 @@ public final class ImageDecoder implements AutoCloseable {
ByteBuffer buffer = mBuffer.slice();
return nCreate(buffer, buffer.position(), buffer.limit(), preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "ByteBufferSource{len=" + mLength + "}";
+ }
}
private static class ContentResolverSource extends Source {
@@ -285,6 +299,16 @@ public final class ImageDecoder implements AutoCloseable {
return createFromAssetFileDescriptor(assetFd, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ String uri = mUri.toString();
+ if (uri.length() > 90) {
+ // We want to keep the Uri usable - usually the authority and the end is important.
+ uri = uri.substring(0, 80) + ".." + uri.substring(uri.length() - 10);
+ }
+ return "ContentResolverSource{uri=" + uri + "}";
+ }
}
@NonNull
@@ -399,6 +423,11 @@ public final class ImageDecoder implements AutoCloseable {
return createFromStream(is, false, preferAnimation, this);
}
}
+
+ @Override
+ public String toString() {
+ return "InputStream{s=" + mInputStream + "}";
+ }
}
/**
@@ -444,6 +473,11 @@ public final class ImageDecoder implements AutoCloseable {
return createFromAsset(ais, preferAnimation, this);
}
}
+
+ @Override
+ public String toString() {
+ return "AssetInputStream{s=" + mAssetInputStream + "}";
+ }
}
private static class ResourceSource extends Source {
@@ -485,6 +519,17 @@ public final class ImageDecoder implements AutoCloseable {
return createFromAsset((AssetInputStream) is, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ // Try to return a human-readable name for debugging purposes.
+ try {
+ return "Resource{name=" + mResources.getResourceName(mResId) + "}";
+ } catch (Resources.NotFoundException e) {
+ // It's ok if we don't find it, fall back to ID.
+ }
+ return "Resource{id=" + mResId + "}";
+ }
}
/**
@@ -521,6 +566,11 @@ public final class ImageDecoder implements AutoCloseable {
InputStream is = mAssets.open(mFileName);
return createFromAsset((AssetInputStream) is, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "AssetSource{file=" + mFileName + "}";
+ }
}
private static class FileSource extends Source {
@@ -534,6 +584,11 @@ public final class ImageDecoder implements AutoCloseable {
public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
return createFromFile(mFile, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "FileSource{file=" + mFile + "}";
+ }
}
private static class CallableSource extends Source {
@@ -557,6 +612,11 @@ public final class ImageDecoder implements AutoCloseable {
}
return createFromAssetFileDescriptor(assetFd, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "CallableSource{obj=" + mCallable.toString() + "}";
+ }
}
/**
@@ -1763,61 +1823,65 @@ public final class ImageDecoder implements AutoCloseable {
@NonNull
private static Drawable decodeDrawableImpl(@NonNull Source src,
@Nullable OnHeaderDecodedListener listener) throws IOException {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ImageDecoder#decodeDrawable");
try (ImageDecoder decoder = src.createImageDecoder(true /*preferAnimation*/)) {
decoder.mSource = src;
decoder.callHeaderDecoded(listener, src);
- if (decoder.mUnpremultipliedRequired) {
- // Though this could be supported (ignored) for opaque images,
- // it seems better to always report this error.
- throw new IllegalStateException("Cannot decode a Drawable " +
- "with unpremultiplied pixels!");
- }
+ try (ImageDecoderSourceTrace unused = new ImageDecoderSourceTrace(decoder)) {
+ if (decoder.mUnpremultipliedRequired) {
+ // Though this could be supported (ignored) for opaque images,
+ // it seems better to always report this error.
+ throw new IllegalStateException(
+ "Cannot decode a Drawable with unpremultiplied pixels!");
+ }
- if (decoder.mMutable) {
- throw new IllegalStateException("Cannot decode a mutable " +
- "Drawable!");
- }
+ if (decoder.mMutable) {
+ throw new IllegalStateException("Cannot decode a mutable Drawable!");
+ }
- // this call potentially manipulates the decoder so it must be performed prior to
- // decoding the bitmap and after decode set the density on the resulting bitmap
- final int srcDensity = decoder.computeDensity(src);
- if (decoder.mAnimated) {
- // AnimatedImageDrawable calls postProcessAndRelease only if
- // mPostProcessor exists.
- ImageDecoder postProcessPtr = decoder.mPostProcessor == null ?
- null : decoder;
- decoder.checkState(true);
- Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
- postProcessPtr, decoder.mDesiredWidth,
- decoder.mDesiredHeight, decoder.getColorSpacePtr(),
- decoder.checkForExtended(), srcDensity,
- src.computeDstDensity(), decoder.mCropRect,
- decoder.mInputStream, decoder.mAssetFd);
- // d has taken ownership of these objects.
- decoder.mInputStream = null;
- decoder.mAssetFd = null;
- return d;
- }
+ // this call potentially manipulates the decoder so it must be performed prior to
+ // decoding the bitmap and after decode set the density on the resulting bitmap
+ final int srcDensity = decoder.computeDensity(src);
+ if (decoder.mAnimated) {
+ // AnimatedImageDrawable calls postProcessAndRelease only if
+ // mPostProcessor exists.
+ ImageDecoder postProcessPtr = decoder.mPostProcessor == null ? null : decoder;
+ decoder.checkState(true);
+ Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
+ postProcessPtr, decoder.mDesiredWidth,
+ decoder.mDesiredHeight, decoder.getColorSpacePtr(),
+ decoder.checkForExtended(), srcDensity,
+ src.computeDstDensity(), decoder.mCropRect,
+ decoder.mInputStream, decoder.mAssetFd);
+ // d has taken ownership of these objects.
+ decoder.mInputStream = null;
+ decoder.mAssetFd = null;
+ return d;
+ }
- Bitmap bm = decoder.decodeBitmapInternal();
- bm.setDensity(srcDensity);
+ Bitmap bm = decoder.decodeBitmapInternal();
+ bm.setDensity(srcDensity);
- Resources res = src.getResources();
- byte[] np = bm.getNinePatchChunk();
- if (np != null && NinePatch.isNinePatchChunk(np)) {
- Rect opticalInsets = new Rect();
- bm.getOpticalInsets(opticalInsets);
- Rect padding = decoder.mOutPaddingRect;
- if (padding == null) {
- padding = new Rect();
+ Resources res = src.getResources();
+ byte[] np = bm.getNinePatchChunk();
+ if (np != null && NinePatch.isNinePatchChunk(np)) {
+ Rect opticalInsets = new Rect();
+ bm.getOpticalInsets(opticalInsets);
+ Rect padding = decoder.mOutPaddingRect;
+ if (padding == null) {
+ padding = new Rect();
+ }
+ nGetPadding(decoder.mNativePtr, padding);
+ return new NinePatchDrawable(res, bm, np, padding,
+ opticalInsets, null);
}
- nGetPadding(decoder.mNativePtr, padding);
- return new NinePatchDrawable(res, bm, np, padding,
- opticalInsets, null);
- }
- return new BitmapDrawable(res, bm);
+ return new BitmapDrawable(res, bm);
+ }
+ } finally {
+ // Close the ImageDecoder#decode trace.
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
@@ -1867,26 +1931,51 @@ public final class ImageDecoder implements AutoCloseable {
@NonNull
private static Bitmap decodeBitmapImpl(@NonNull Source src,
@Nullable OnHeaderDecodedListener listener) throws IOException {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ImageDecoder#decodeBitmap");
try (ImageDecoder decoder = src.createImageDecoder(false /*preferAnimation*/)) {
decoder.mSource = src;
decoder.callHeaderDecoded(listener, src);
+ try (ImageDecoderSourceTrace unused = new ImageDecoderSourceTrace(decoder)) {
+ // this call potentially manipulates the decoder so it must be performed prior to
+ // decoding the bitmap
+ final int srcDensity = decoder.computeDensity(src);
+ Bitmap bm = decoder.decodeBitmapInternal();
+ bm.setDensity(srcDensity);
- // this call potentially manipulates the decoder so it must be performed prior to
- // decoding the bitmap
- final int srcDensity = decoder.computeDensity(src);
- Bitmap bm = decoder.decodeBitmapInternal();
- bm.setDensity(srcDensity);
-
- Rect padding = decoder.mOutPaddingRect;
- if (padding != null) {
- byte[] np = bm.getNinePatchChunk();
- if (np != null && NinePatch.isNinePatchChunk(np)) {
- nGetPadding(decoder.mNativePtr, padding);
+ Rect padding = decoder.mOutPaddingRect;
+ if (padding != null) {
+ byte[] np = bm.getNinePatchChunk();
+ if (np != null && NinePatch.isNinePatchChunk(np)) {
+ nGetPadding(decoder.mNativePtr, padding);
+ }
}
+ return bm;
}
+ } finally {
+ // Close the ImageDecoder#decode trace.
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
- return bm;
+ /**
+ * This describes the decoder in traces to ease debugging. It has to be called after
+ * header has been decoded and width/height have been populated. It should be used
+ * inside a try-with-resources call to automatically complete the trace.
+ */
+ private static AutoCloseable traceDecoderSource(ImageDecoder decoder) {
+ final boolean resourceTracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_RESOURCES);
+ if (resourceTracingEnabled) {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, describeDecoderForTrace(decoder));
}
+
+ return new AutoCloseable() {
+ @Override
+ public void close() throws Exception {
+ if (resourceTracingEnabled) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
+ };
}
// This method may modify the decoder so it must be called prior to performing the decode
@@ -1994,6 +2083,66 @@ public final class ImageDecoder implements AutoCloseable {
}
}
+ /**
+ * Returns a short string describing what passed ImageDecoder is loading -
+ * it reports image dimensions, desired dimensions (if any) and source resource.
+ *
+ * The string appears in perf traces to simplify search for slow or memory intensive
+ * image loads.
+ *
+ * Example: ID#w=300;h=250;dw=150;dh=150;src=Resource{name=@resource}
+ *
+ * @hide
+ */
+ private static String describeDecoderForTrace(@NonNull ImageDecoder decoder) {
+ StringBuilder builder = new StringBuilder();
+ // Source dimensions
+ builder.append("ID#w=");
+ builder.append(decoder.mWidth);
+ builder.append(";h=");
+ builder.append(decoder.mHeight);
+ // Desired dimensions (if present)
+ if (decoder.mDesiredWidth != decoder.mWidth
+ || decoder.mDesiredHeight != decoder.mHeight) {
+ builder.append(";dw=");
+ builder.append(decoder.mDesiredWidth);
+ builder.append(";dh=");
+ builder.append(decoder.mDesiredHeight);
+ }
+ // Source description
+ builder.append(";src=");
+ builder.append(decoder.mSource);
+ return builder.toString();
+ }
+
+ /**
+ * Records a trace with information about the source being decoded - dimensions,
+ * desired dimensions and source information.
+ *
+ * It significantly eases debugging of slow resource loads on main thread and
+ * possible large memory consumers.
+ *
+ * @hide
+ */
+ private static final class ImageDecoderSourceTrace implements AutoCloseable {
+
+ private final boolean mResourceTracingEnabled;
+
+ ImageDecoderSourceTrace(ImageDecoder decoder) {
+ mResourceTracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_RESOURCES);
+ if (mResourceTracingEnabled) {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, describeDecoderForTrace(decoder));
+ }
+ }
+
+ @Override
+ public void close() {
+ if (mResourceTracingEnabled) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
+ }
+
private static native ImageDecoder nCreate(long asset,
boolean preferAnimation, Source src) throws IOException;
private static native ImageDecoder nCreate(ByteBuffer buffer, int position, int limit,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index b15dce7f3f17..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);
@@ -792,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,
@@ -805,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,
@@ -868,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) {
@@ -881,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);
}
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/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index 835c40365cda..effc1a3ef3ea 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock;
import android.annotation.NonNull;
import android.app.Activity;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
@@ -57,13 +58,21 @@ public class EmbeddingTestUtils {
/** Creates a rule to always split the given activity and the given intent. */
static SplitRule createSplitRule(@NonNull Activity primaryActivity,
@NonNull Intent secondaryIntent) {
+ return createSplitRule(primaryActivity, secondaryIntent, true /* clearTop */);
+ }
+
+ /** Creates a rule to always split the given activity and the given intent. */
+ static SplitRule createSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Intent secondaryIntent, boolean clearTop) {
final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent);
return new SplitPairRule.Builder(
activityPair -> false,
targetPair::equals,
w -> true)
.setSplitRatio(SPLIT_RATIO)
- .setShouldClearTop(true)
+ .setShouldClearTop(clearTop)
+ .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
+ .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
.build();
}
@@ -75,6 +84,14 @@ public class EmbeddingTestUtils {
true /* clearTop */);
}
+ /** Creates a rule to always split the given activities. */
+ static SplitRule createSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity, boolean clearTop) {
+ return createSplitRule(primaryActivity, secondaryActivity,
+ DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY,
+ clearTop);
+ }
+
/** Creates a rule to always split the given activities with the given finish behaviors. */
static SplitRule createSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity, int finishPrimaryWithSecondary,
@@ -105,4 +122,12 @@ public class EmbeddingTestUtils {
false /* isTaskFragmentClearedForPip */,
new Point());
}
+
+ static ActivityInfo createActivityInfoWithMinDimensions() {
+ ActivityInfo aInfo = new ActivityInfo();
+ final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
+ aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
+ primaryBounds.width() + 1, primaryBounds.height() + 1);
+ return aInfo;
+ }
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index ef7728cec387..042547fd30f2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
@@ -34,6 +35,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -436,6 +438,50 @@ public class SplitControllerTest {
}
@Test
+ public void testResolveStartActivityIntent_shouldExpandSplitContainer() {
+ final Intent intent = new Intent().setComponent(
+ new ComponentName(ApplicationProvider.getApplicationContext(),
+ MinimumDimensionActivity.class));
+ setupSplitRule(mActivity, intent, false /* clearTop */);
+ final Activity secondaryActivity = createMockActivity();
+ addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */);
+
+ final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
+ mTransaction, TASK_ID, intent, mActivity);
+ final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
+ mActivity);
+
+ assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container));
+ assertTrue(primaryContainer.areLastRequestedBoundsEqual(null));
+ assertTrue(container.areLastRequestedBoundsEqual(null));
+ assertEquals(container, mSplitController.getContainerWithActivity(secondaryActivity));
+ }
+
+ @Test
+ public void testResolveStartActivityIntent_noInfo_shouldCreateSplitContainer() {
+ final Intent intent = new Intent().setComponent(
+ new ComponentName(ApplicationProvider.getApplicationContext(),
+ MinimumDimensionActivity.class));
+ setupSplitRule(mActivity, intent, false /* clearTop */);
+ final Activity secondaryActivity = createMockActivity();
+ addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */);
+
+ final TaskFragmentContainer secondaryContainer = mSplitController
+ .getContainerWithActivity(secondaryActivity);
+ secondaryContainer.mInfo = null;
+
+ final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
+ mTransaction, TASK_ID, intent, mActivity);
+ final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
+ mActivity);
+
+ assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container));
+ assertTrue(primaryContainer.areLastRequestedBoundsEqual(null));
+ assertTrue(container.areLastRequestedBoundsEqual(null));
+ assertNotEquals(container, secondaryContainer);
+ }
+
+ @Test
public void testPlaceActivityInTopContainer() {
mSplitController.placeActivityInTopContainer(mActivity);
@@ -787,11 +833,7 @@ public class SplitControllerTest {
final Activity activityBelow = createMockActivity();
setupSplitRule(mActivity, activityBelow);
- ActivityInfo aInfo = new ActivityInfo();
- final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
- aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
- primaryBounds.width() + 1, primaryBounds.height() + 1);
- doReturn(aInfo).when(mActivity).getActivityInfo();
+ doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo();
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
TASK_ID);
@@ -810,17 +852,12 @@ public class SplitControllerTest {
final Activity activityBelow = createMockActivity();
setupSplitRule(activityBelow, mActivity);
- ActivityInfo aInfo = new ActivityInfo();
- final Rect secondaryBounds = getSplitBounds(false /* isPrimary */);
- aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
- secondaryBounds.width() + 1, secondaryBounds.height() + 1);
- doReturn(aInfo).when(mActivity).getActivityInfo();
+ doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo();
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
TASK_ID);
container.addPendingAppearedActivity(mActivity);
- // Allow to split as primary.
boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* isOnReparent */);
@@ -828,6 +865,29 @@ public class SplitControllerTest {
assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */);
}
+ // Suppress GuardedBy warning on unit tests
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testResolveActivityToContainer_minDimensions_shouldExpandSplitContainer() {
+ final Activity primaryActivity = createMockActivity();
+ final Activity secondaryActivity = createMockActivity();
+ addSplitTaskFragments(primaryActivity, secondaryActivity, false /* clearTop */);
+
+ setupSplitRule(primaryActivity, mActivity, false /* clearTop */);
+ doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo();
+ doReturn(secondaryActivity).when(mSplitController).findActivityBelow(eq(mActivity));
+
+ clearInvocations(mSplitPresenter);
+ boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ false /* isOnReparent */);
+
+ assertTrue(result);
+ assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */);
+ assertEquals(mSplitController.getContainerWithActivity(secondaryActivity),
+ mSplitController.getContainerWithActivity(mActivity));
+ verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any());
+ }
+
@Test
public void testResolveActivityToContainer_inUnknownTaskFragment() {
doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity);
@@ -944,23 +1004,41 @@ public class SplitControllerTest {
/** Setups a rule to always split the given activities. */
private void setupSplitRule(@NonNull Activity primaryActivity,
@NonNull Intent secondaryIntent) {
- final SplitRule splitRule = createSplitRule(primaryActivity, secondaryIntent);
+ setupSplitRule(primaryActivity, secondaryIntent, true /* clearTop */);
+ }
+
+ /** Setups a rule to always split the given activities. */
+ private void setupSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Intent secondaryIntent, boolean clearTop) {
+ final SplitRule splitRule = createSplitRule(primaryActivity, secondaryIntent, clearTop);
mSplitController.setEmbeddingRules(Collections.singleton(splitRule));
}
/** Setups a rule to always split the given activities. */
private void setupSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) {
- final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity);
+ setupSplitRule(primaryActivity, secondaryActivity, true /* clearTop */);
+ }
+
+ /** Setups a rule to always split the given activities. */
+ private void setupSplitRule(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity, boolean clearTop) {
+ final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity, clearTop);
mSplitController.setEmbeddingRules(Collections.singleton(splitRule));
}
/** Adds a pair of TaskFragments as split for the given activities. */
private void addSplitTaskFragments(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) {
+ addSplitTaskFragments(primaryActivity, secondaryActivity, true /* clearTop */);
+ }
+
+ /** Adds a pair of TaskFragments as split for the given activities. */
+ private void addSplitTaskFragments(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity, boolean clearTop) {
registerSplitPair(createMockTaskFragmentContainer(primaryActivity),
createMockTaskFragmentContainer(secondaryActivity),
- createSplitRule(primaryActivity, secondaryActivity));
+ createSplitRule(primaryActivity, secondaryActivity, clearTop));
}
/** Registers the two given TaskFragments as split pair. */
@@ -1011,16 +1089,18 @@ public class SplitControllerTest {
if (primaryContainer.mInfo != null) {
final Rect primaryBounds = matchParentBounds ? new Rect()
: getSplitBounds(true /* isPrimary */);
+ final int windowingMode = matchParentBounds ? WINDOWING_MODE_UNDEFINED
+ : WINDOWING_MODE_MULTI_WINDOW;
assertTrue(primaryContainer.areLastRequestedBoundsEqual(primaryBounds));
- assertTrue(primaryContainer.isLastRequestedWindowingModeEqual(
- WINDOWING_MODE_MULTI_WINDOW));
+ assertTrue(primaryContainer.isLastRequestedWindowingModeEqual(windowingMode));
}
if (secondaryContainer.mInfo != null) {
final Rect secondaryBounds = matchParentBounds ? new Rect()
: getSplitBounds(false /* isPrimary */);
+ final int windowingMode = matchParentBounds ? WINDOWING_MODE_UNDEFINED
+ : WINDOWING_MODE_MULTI_WINDOW;
assertTrue(secondaryContainer.areLastRequestedBoundsEqual(secondaryBounds));
- assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual(
- WINDOWING_MODE_MULTI_WINDOW));
+ assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual(windowingMode));
}
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index acc398a27baf..d79319666c01 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -20,11 +20,16 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START;
+import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPANDED;
+import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
+import static androidx.window.extensions.embedding.SplitPresenter.RESULT_NOT_EXPANDED;
import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition;
import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
@@ -34,6 +39,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -49,6 +55,7 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
import android.util.Size;
@@ -195,6 +202,52 @@ public class SplitPresenterTest {
splitRule, mActivity, minDimensionsPair));
}
+ @Test
+ public void testExpandSplitContainerIfNeeded() {
+ SplitContainer splitContainer = mock(SplitContainer.class);
+ Activity secondaryActivity = createMockActivity();
+ SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
+ TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID);
+ TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID);
+ doReturn(splitRule).when(splitContainer).getSplitRule();
+ doReturn(primaryTf).when(splitContainer).getPrimaryContainer();
+ doReturn(secondaryTf).when(splitContainer).getSecondaryContainer();
+
+ assertThrows(IllegalArgumentException.class, () ->
+ mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity,
+ null /* secondaryActivity */, null /* secondaryIntent */));
+
+ assertEquals(RESULT_NOT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
+ splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
+ verify(mPresenter, never()).expandTaskFragment(any(), any());
+
+ doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo();
+ assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded(
+ mTransaction, splitContainer, mActivity, secondaryActivity,
+ null /* secondaryIntent */));
+
+ primaryTf.setInfo(createMockTaskFragmentInfo(primaryTf, mActivity));
+ secondaryTf.setInfo(createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
+
+ assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
+ splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
+ verify(mPresenter).expandTaskFragment(eq(mTransaction),
+ eq(primaryTf.getTaskFragmentToken()));
+ verify(mPresenter).expandTaskFragment(eq(mTransaction),
+ eq(secondaryTf.getTaskFragmentToken()));
+
+ clearInvocations(mPresenter);
+
+ assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
+ splitContainer, mActivity, null /* secondaryActivity */,
+ new Intent(ApplicationProvider.getApplicationContext(),
+ MinimumDimensionActivity.class)));
+ verify(mPresenter).expandTaskFragment(eq(mTransaction),
+ eq(primaryTf.getTaskFragmentToken()));
+ verify(mPresenter).expandTaskFragment(eq(mTransaction),
+ eq(secondaryTf.getTaskFragmentToken()));
+ }
+
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
final Configuration activityConfig = new Configuration();
@@ -203,6 +256,7 @@ public class SplitPresenterTest {
doReturn(mActivityResources).when(activity).getResources();
doReturn(activityConfig).when(mActivityResources).getConfiguration();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
+ doReturn(mock(IBinder.class)).when(activity).getActivityToken();
return activity;
}
}
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
index 86ca65526336..cc0333efd82b 100644
--- a/libs/WindowManager/Shell/res/values-television/config.xml
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -43,4 +43,13 @@
<!-- Time (duration in milliseconds) that the shell waits for an app to close the PiP by itself
if a custom action is present before closing it. -->
<integer name="config_pipForceCloseDelay">5000</integer>
+
+ <!-- Animation duration when exit starting window: fade out icon -->
+ <integer name="starting_window_app_reveal_icon_fade_out_duration">0</integer>
+
+ <!-- Animation duration when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_delay">0</integer>
+
+ <!-- Animation duration when exit starting window: reveal app -->
+ <integer name="starting_window_app_reveal_anim_duration">0</integer>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 4eba1697b595..cf2734c375f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -591,7 +591,7 @@ public class PipAnimationController {
final Rect insets = computeInsets(fraction);
getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
sourceHintRect, initialSourceValue, bounds, insets,
- isInPipDirection);
+ isInPipDirection, fraction);
if (shouldApplyCornerRadius()) {
final Rect sourceBounds = new Rect(initialContainerRect);
sourceBounds.inset(insets);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index a017a2674359..c0bc108baada 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -104,7 +104,7 @@ public class PipSurfaceTransactionHelper {
public PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx,
SurfaceControl leash, Rect sourceRectHint,
Rect sourceBounds, Rect destinationBounds, Rect insets,
- boolean isInPipDirection) {
+ boolean isInPipDirection, float fraction) {
mTmpDestinationRect.set(sourceBounds);
// Similar to {@link #scale}, we want to position the surface relative to the screen
// coordinates so offset the bounds to 0,0
@@ -116,9 +116,13 @@ public class PipSurfaceTransactionHelper {
if (isInPipDirection
&& sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) {
// scale by sourceRectHint if it's not edge-to-edge, for entering PiP transition only.
- scale = sourceBounds.width() <= sourceBounds.height()
+ final float endScale = sourceBounds.width() <= sourceBounds.height()
? (float) destinationBounds.width() / sourceRectHint.width()
: (float) destinationBounds.height() / sourceRectHint.height();
+ final float startScale = sourceBounds.width() <= sourceBounds.height()
+ ? (float) destinationBounds.width() / sourceBounds.width()
+ : (float) destinationBounds.height() / sourceBounds.height();
+ scale = (1 - fraction) * startScale + fraction * endScale;
} else {
scale = sourceBounds.width() <= sourceBounds.height()
? (float) destinationBounds.width() / sourceBounds.width()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index bd386b5681d8..22b0ccbc8488 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -942,7 +942,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// Re-set the PIP bounds to none.
mPipBoundsState.setBounds(new Rect());
mPipUiEventLoggerLogger.setTaskInfo(null);
- mPipMenuController.detach();
+ mMainExecutor.executeDelayed(() -> mPipMenuController.detach(), 0);
if (info.displayId != Display.DEFAULT_DISPLAY && mOnDisplayIdChangeCallback != null) {
mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
@@ -1472,6 +1472,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
"%s: Abort animation, invalid leash", TAG);
return null;
}
+ if (isInPipDirection(direction)
+ && !isSourceRectHintValidForEnterPip(sourceHintRect, destinationBounds)) {
+ // The given source rect hint is too small for enter PiP animation, reset it to null.
+ sourceHintRect = null;
+ }
final int rotationDelta = mWaitForFixedRotation
? deltaRotation(mCurrentRotation, mNextRotation)
: Surface.ROTATION_0;
@@ -1546,6 +1551,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
/**
+ * This is a situation in which the source rect hint on at least one axis is smaller
+ * than the destination bounds, which represents a problem because we would have to scale
+ * up that axis to fit the bounds. So instead, just fallback to the non-source hint
+ * animation in this case.
+ *
+ * @return {@code false} if the given source is too small to use for the entering animation.
+ */
+ private boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) {
+ return sourceRectHint != null
+ && sourceRectHint.width() > destinationBounds.width()
+ && sourceRectHint.height() > destinationBounds.height();
+ }
+
+ /**
* Sync with {@link SplitScreenController} on destination bounds if PiP is going to
* split screen.
*
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index c80c14f353d0..05a890fc65ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -715,7 +715,7 @@ public class PipTransition extends PipTransitionController {
mSurfaceTransactionHelper
.crop(finishTransaction, leash, destinationBounds)
.round(finishTransaction, leash, true /* applyCornerRadius */);
- mPipMenuController.attach(leash);
+ mTransitions.getMainExecutor().executeDelayed(() -> mPipMenuController.attach(leash), 0);
if (taskInfo.pictureInPictureParams != null
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index a43b6043908b..90a2695bdf90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -28,9 +28,7 @@ import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.TransitionInfo;
@@ -56,7 +54,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH
protected final ShellTaskOrganizer mShellTaskOrganizer;
protected final PipMenuController mPipMenuController;
protected final Transitions mTransitions;
- private final Handler mMainHandler;
private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
protected PipTaskOrganizer mPipOrganizer;
@@ -144,7 +141,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipAnimationController = pipAnimationController;
mTransitions = transitions;
- mMainHandler = new Handler(Looper.getMainLooper());
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.addHandler(this);
}
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 4e7b20e3ae84..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);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS
new file mode 100644
index 000000000000..d325d161ac53
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module TV splitscreen owner
+galinap@google.com
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 05e5b8e66a00..dcd6277966dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -866,13 +866,19 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
});
};
va.addListener(new AnimatorListenerAdapter() {
+ private boolean mFinished = false;
+
@Override
public void onAnimationEnd(Animator animation) {
+ if (mFinished) return;
+ mFinished = true;
finisher.run();
}
@Override
public void onAnimationCancel(Animator animation) {
+ if (mFinished) return;
+ mFinished = true;
finisher.run();
}
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
index 61e11e877b90..61e92f355dc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/LegacyTransitions.java
@@ -107,7 +107,7 @@ public class LegacyTransitions {
}
@Override
- public void onAnimationCancelled() throws RemoteException {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
mCancelled = true;
mApps = mWallpapers = mNonApps = null;
checkApply();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
index 278ba9b0f4db..5b073038059c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/BaseBubbleScreen.kt
@@ -48,7 +48,7 @@ abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) {
ServiceManager.getService(Context.NOTIFICATION_SERVICE))
protected val uid = context.packageManager.getApplicationInfo(
- testApp.component.packageName, 0).uid
+ testApp.`package`, 0).uid
protected abstract val transition: FlickerBuilder.() -> Unit
@@ -59,7 +59,7 @@ abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) {
return {
setup {
test {
- notifyManager.setBubblesAllowed(testApp.component.packageName,
+ notifyManager.setBubblesAllowed(testApp.`package`,
uid, NotificationManager.BUBBLE_PREFERENCE_ALL)
testApp.launchViaIntent(wmHelper)
waitAndGetAddBubbleBtn()
@@ -69,7 +69,7 @@ abstract class BaseBubbleScreen(protected val testSpec: FlickerTestParameter) {
teardown {
test {
- notifyManager.setBubblesAllowed(testApp.component.packageName,
+ notifyManager.setBubblesAllowed(testApp.`package`,
uid, NotificationManager.BUBBLE_PREFERENCE_NONE)
testApp.exit()
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
index 27d65648dbb7..e2b19ea22b3b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/LaunchBubbleFromLockScreen.kt
@@ -16,10 +16,10 @@
package com.android.wm.shell.flicker.bubble
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.WindowInsets
import android.view.WindowManager
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
@@ -29,8 +29,8 @@ import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import org.junit.Assume
-import org.junit.runner.RunWith
import org.junit.Test
+import org.junit.runner.RunWith
import org.junit.runners.Parameterized
/**
@@ -54,20 +54,21 @@ class LaunchBubbleFromLockScreen(testSpec: FlickerTestParameter) : BaseBubbleScr
val addBubbleBtn = waitAndGetAddBubbleBtn()
addBubbleBtn?.click() ?: error("Bubble widget not found")
device.sleep()
- wmHelper.waitFor("noAppWindowsOnTop") {
- it.wmState.topVisibleAppWindow.isEmpty()
- }
+ wmHelper.StateSyncBuilder()
+ .withoutTopVisibleAppWindows()
+ .waitForAndVerify()
device.wakeUp()
}
}
transitions {
// Swipe & wait for the notification shade to expand so all can be seen
val wm = context.getSystemService(WindowManager::class.java)
- val metricInsets = wm.getCurrentWindowMetrics().windowInsets
+ ?: error("Unable to obtain WM service")
+ val metricInsets = wm.currentWindowMetrics.windowInsets
val insets = metricInsets.getInsetsIgnoringVisibility(
WindowInsets.Type.statusBars()
or WindowInsets.Type.displayCutout())
- device.swipe(100, insets.top + 100, 100, device.getDisplayHeight() / 2, 4)
+ device.swipe(100, insets.top + 100, 100, device.displayHeight / 2, 4)
device.waitForIdle(2000)
instrumentation.uiAutomation.syncInputTransactions()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
index 41cd31aabf05..f4305ed19824 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt
@@ -17,44 +17,10 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import com.android.server.wm.flicker.Flicker
-import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.server.wm.traces.common.region.Region
class AppPairsHelper(
instrumentation: Instrumentation,
activityLabel: String,
component: FlickerComponentName
-) : BaseAppHelper(instrumentation, activityLabel, component) {
- fun getPrimaryBounds(dividerBounds: Region): Region {
- val primaryAppBounds = Region.from(0, 0, dividerBounds.bounds.right,
- dividerBounds.bounds.bottom + WindowUtils.dockedStackDividerInset)
- return primaryAppBounds
- }
-
- fun getSecondaryBounds(dividerBounds: Region): Region {
- val displayBounds = WindowUtils.displayBounds
- val secondaryAppBounds = Region.from(0,
- dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
- return secondaryAppBounds
- }
-
- companion object {
- const val TEST_REPETITIONS = 1
- const val TIMEOUT_MS = 3_000L
-
- fun Flicker.waitAppsShown(app1: SplitScreenHelper?, app2: SplitScreenHelper?) {
- wmHelper.waitFor("primaryAndSecondaryAppsVisible") { dump ->
- val primaryAppVisible = app1?.let {
- dump.wmState.isWindowSurfaceShown(app1.defaultWindowName)
- } ?: false
- val secondaryAppVisible = app2?.let {
- dump.wmState.isWindowSurfaceShown(app2.defaultWindowName)
- } ?: false
- primaryAppVisible && secondaryAppVisible
- }
- }
- }
-}
+) : BaseAppHelper(instrumentation, activityLabel, component)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
index 3dd9e0572947..912ba67285f7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
@@ -40,7 +40,7 @@ abstract class BaseAppHelper(
component,
LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy
) {
- private val appSelector = By.pkg(component.packageName).depth(0)
+ private val appSelector = By.pkg(`package`).depth(0)
protected val isTelevision: Boolean
get() = context.packageManager.run {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
index cc5b9f9eb26d..2e690de666f4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
@@ -18,7 +18,6 @@ package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
import com.android.server.wm.traces.parser.toFlickerComponent
@@ -35,8 +34,7 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
*
* @param wmHelper Helper used to wait for WindowManager states
*/
- @JvmOverloads
- open fun openIME(wmHelper: WindowManagerStateHelper? = null) {
+ open fun openIME(wmHelper: WindowManagerStateHelper) {
if (!isTelevision) {
val editText = uiDevice.wait(
Until.findObject(By.res(getPackage(), "plain_text_input")),
@@ -47,7 +45,9 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
"was left in an unknown state (e.g. in split screen)"
}
editText.click()
- waitAndAssertIMEShown(uiDevice, wmHelper)
+ wmHelper.StateSyncBuilder()
+ .withImeShown()
+ .waitForAndVerify()
} else {
// If we do the same thing as above - editText.click() - on TV, that's going to force TV
// into the touch mode. We really don't want that.
@@ -55,36 +55,22 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
}
- protected fun waitAndAssertIMEShown(
- device: UiDevice,
- wmHelper: WindowManagerStateHelper? = null
- ) {
- if (wmHelper == null) {
- device.waitForIdle()
- } else {
- wmHelper.waitImeShown()
- }
- }
-
/**
* Opens the IME and wait for it to be gone
*
* @param wmHelper Helper used to wait for WindowManager states
*/
- @JvmOverloads
- open fun closeIME(wmHelper: WindowManagerStateHelper? = null) {
+ open fun closeIME(wmHelper: WindowManagerStateHelper) {
if (!isTelevision) {
uiDevice.pressBack()
// Using only the AccessibilityInfo it is not possible to identify if the IME is active
- if (wmHelper == null) {
- uiDevice.waitForIdle()
- } else {
- wmHelper.waitImeGone()
- }
+ wmHelper.StateSyncBuilder()
+ .withImeGone()
+ .waitForAndVerify()
} else {
// While pressing the back button should close the IME on TV as well, it may also lead
// to the app closing. So let's instead just ask the app to close the IME.
launchViaIntent(action = Components.ImeActivity.ACTION_CLOSE_IME)
}
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index f73d191b1917..216445fe9356 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -19,13 +19,13 @@ package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
import android.media.session.MediaController
import android.media.session.MediaSessionManager
-import android.os.SystemClock
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE
import com.android.server.wm.traces.common.Rect
+import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow
@@ -43,11 +43,11 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
private val mediaController: MediaController?
get() = mediaSessionManager.getActiveSessions(null).firstOrNull {
- it.packageName == component.packageName
+ it.packageName == `package`
}
fun clickObject(resId: String) {
- val selector = By.res(component.packageName, resId)
+ val selector = By.res(`package`, resId)
val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
if (!isTelevision) {
@@ -71,8 +71,12 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
) {
launchViaIntentAndWaitShown(
wmHelper, expectedWindowName, action, stringExtras,
- waitConditions = arrayOf(WindowManagerStateHelper.pipShownCondition)
+ waitConditions = arrayOf(WindowManagerConditionsFactory.hasPipWindow())
)
+
+ wmHelper.StateSyncBuilder()
+ .withPipShown()
+ .waitForAndVerify()
}
/**
@@ -95,12 +99,13 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
return false
}
- @JvmOverloads
- fun clickEnterPipButton(wmHelper: WindowManagerStateHelper? = null) {
+ fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) {
clickObject(ENTER_PIP_BUTTON_ID)
// Wait on WMHelper or simply wait for 3 seconds
- wmHelper?.waitPipShown() ?: SystemClock.sleep(3_000)
+ wmHelper.StateSyncBuilder()
+ .withPipShown()
+ .waitForAndVerify()
// when entering pip, the dismiss button is visible at the start. to ensure the pip
// animation is complete, wait until the pip dismiss button is no longer visible.
// b/176822698: dismiss-only state will be removed in the future
@@ -116,7 +121,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
fun checkWithCustomActionsCheckbox() = uiDevice
- .findObject(By.res(component.packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID))
+ .findObject(By.res(`package`, WITH_CUSTOM_ACTIONS_BUTTON_ID))
?.takeIf { it.isCheckable }
?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) }
?: error("'With custom actions' checkbox not found")
@@ -166,8 +171,10 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
}
// Wait for animation to complete.
- wmHelper.waitPipGone()
- wmHelper.waitForHomeActivityVisible()
+ wmHelper.StateSyncBuilder()
+ .withPipGone()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
}
/**
@@ -183,8 +190,10 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
?: error("PIP window expand button not found")
val expandButtonBounds = expandPipObject.visibleBounds
uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
- wmHelper.waitPipGone()
- wmHelper.waitForAppTransitionIdle()
+ wmHelper.StateSyncBuilder()
+ .withPipGone()
+ .withFullScreenApp(component)
+ .waitForAndVerify()
}
/**
@@ -194,7 +203,9 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
val windowRect = getWindowRect(wmHelper)
uiDevice.click(windowRect.centerX(), windowRect.centerY())
uiDevice.click(windowRect.centerX(), windowRect.centerY())
- wmHelper.waitForAppTransitionIdle()
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .waitForAndVerify()
}
companion object {
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 e7d641e9c66e..cb84fc441f75 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
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.pip
import androidx.test.filters.RequiresDevice
-import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.annotation.Group3
@@ -51,7 +50,6 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
- protected val taplInstrumentation = LauncherInstrumentation()
/**
* Defines the transition used to run the test
*/
@@ -70,7 +68,7 @@ class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest
}
}
transitions {
- taplInstrumentation.goHome()
+ tapl.goHome()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index eb318fb256c9..c18c8ce345fc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -16,16 +16,16 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.FlickerComponentName.Companion.LAUNCHER
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -157,15 +157,15 @@ open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec
}
/**
- * Checks [LAUNCHER_COMPONENT] layer remains visible throughout the animation
+ * Checks [LAUNCHER] layer remains visible throughout the animation
*/
@Presubmit
@Test
fun launcherLayerBecomesVisible() {
testSpec.assertLayers {
- isInvisible(LAUNCHER_COMPONENT)
+ isInvisible(LAUNCHER)
.then()
- .isVisible(LAUNCHER_COMPONENT)
+ .isVisible(LAUNCHER)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 75481f2199c2..0333577b0d2a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -33,8 +33,8 @@ import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
+import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -98,10 +98,12 @@ class EnterPipToOtherOrientationTest(
// Enter PiP, and assert that the PiP is within bounds now that the device is back
// in portrait
broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
- wmHelper.waitPipShown()
- wmHelper.waitForAppTransitionIdle()
// during rotation the status bar becomes invisible and reappears at the end
- wmHelper.waitForNavBarStatusBarVisible()
+ wmHelper.StateSyncBuilder()
+ .withPipShown()
+ .withAppTransitionIdle()
+ .withNavBarStatusBarVisible()
+ .waitForAndVerify()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
index 0b4bc761838d..47215b9fb883 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipTransition.kt
@@ -19,10 +19,10 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.view.Surface
import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.traces.common.FlickerComponentName.Companion.LAUNCHER
import org.junit.Test
/**
@@ -78,7 +78,7 @@ abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition
}
/**
- * Checks that [pipApp] and [LAUNCHER_COMPONENT] layers are visible at the start
+ * Checks that [pipApp] and [LAUNCHER] layers are visible at the start
* of the transition. Then [pipApp] layer becomes invisible, and remains invisible
* until the end of the transition
*/
@@ -87,10 +87,10 @@ abstract class ExitPipTransition(testSpec: FlickerTestParameter) : PipTransition
open fun pipLayerBecomesInvisible() {
testSpec.assertLayers {
this.isVisible(pipApp.component)
- .isVisible(LAUNCHER_COMPONENT)
+ .isVisible(LAUNCHER)
.then()
.isInvisible(pipApp.component)
- .isVisible(LAUNCHER_COMPONENT)
+ .isVisible(LAUNCHER)
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 46424a768408..32022ad5f018 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -16,8 +16,8 @@
package com.android.wm.shell.flicker.pip
-import android.view.Surface
import android.platform.test.annotations.FlakyTest
+import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -74,7 +74,9 @@ class ExitPipViaExpandButtonClickTest(
// This will bring PipApp to fullscreen
pipApp.expandPipWindowToApp(wmHelper)
// Wait until the other app is no longer visible
- wmHelper.waitForWindowSurfaceDisappeared(testApp.component)
+ wmHelper.StateSyncBuilder()
+ .withWindowSurfaceDisappeared(testApp.component)
+ .waitForAndVerify()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 18711d8fd3c6..3fbea488d091 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -73,7 +73,9 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit
// This will bring PipApp to fullscreen
pipApp.exitPipToFullScreenViaIntent(wmHelper)
// Wait until the other app is no longer visible
- wmHelper.waitForWindowSurfaceDisappeared(testApp.component)
+ wmHelper.StateSyncBuilder()
+ .withWindowSurfaceDisappeared(testApp.component)
+ .waitForAndVerify()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 9aae7f324269..210b1968b2da 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -16,9 +16,9 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -64,9 +64,12 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti
val pipCenterY = pipRegion.centerY()
val displayCenterX = device.displayWidth / 2
device.swipe(pipCenterX, pipCenterY, displayCenterX, device.displayHeight, 10)
- wmHelper.waitPipGone()
- wmHelper.waitForWindowSurfaceDisappeared(pipApp.component)
- wmHelper.waitForAppTransitionIdle()
+ // Wait until the other app is no longer visible
+ wmHelper.StateSyncBuilder()
+ .withPipGone()
+ .withWindowSurfaceDisappeared(pipApp.component)
+ .withAppTransitionIdle()
+ .waitForAndVerify()
}
}
@@ -108,4 +111,4 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti
repetitions = 3)
}
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index ffbb89ee8f6b..dc1fdde18d1b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -23,9 +23,9 @@ import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.traces.common.FlickerComponentName.Companion.LAUNCHER
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -147,13 +147,13 @@ class ExpandPipOnDoubleClickTest(testSpec: FlickerTestParameter) : PipTransition
}
/**
- * Checks [pipApp] layer remains visible throughout the animation
+ * Checks [LAUNCHER] layer remains visible throughout the animation
*/
@Presubmit
@Test
fun launcherIsAlwaysVisible() {
testSpec.assertLayers {
- isVisible(LAUNCHER_COMPONENT)
+ isVisible(LAUNCHER)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index 8d542c8ec9e6..fe5dd8b83cfb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -21,6 +21,7 @@ import android.content.Intent
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
@@ -41,6 +42,7 @@ import org.junit.Test
abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ protected val tapl = LauncherInstrumentation()
protected val pipApp = PipAppHelper(instrumentation)
protected val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation)
@@ -117,13 +119,11 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
test {
if (!eachRun) {
pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
- wmHelper.waitPipShown()
}
}
eachRun {
if (eachRun) {
pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
- wmHelper.waitPipShown()
}
}
}
@@ -171,4 +171,4 @@ abstract class PipTransition(protected val testSpec: FlickerTestParameter) {
@Presubmit
@Test
open fun entireScreenCovered() = testSpec.entireScreenCovered()
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index e44cf3866a09..83d3fe260928 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -16,18 +16,18 @@
package com.android.wm.shell.flicker.pip
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.testapp.Components
@@ -66,11 +66,12 @@ open class SetRequestedOrientationWhilePinnedTest(
EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString()))
// Enter PiP.
broadcastActionTrigger.doAction(Components.PipActivity.ACTION_ENTER_PIP)
- wmHelper.waitPipShown()
- wmHelper.waitForRotation(Surface.ROTATION_0)
- wmHelper.waitForAppTransitionIdle()
// System bar may fade out during fixed rotation.
- wmHelper.waitForNavBarStatusBarVisible()
+ wmHelper.StateSyncBuilder()
+ .withPipShown()
+ .withRotation(Surface.ROTATION_0)
+ .withNavBarStatusBarVisible()
+ .waitForAndVerify()
}
}
teardown {
@@ -85,11 +86,13 @@ open class SetRequestedOrientationWhilePinnedTest(
transitions {
// Launch the activity back into fullscreen and ensure that it is now in landscape
pipApp.launchViaIntent(wmHelper)
- wmHelper.waitForFullScreenApp(pipApp.component)
- wmHelper.waitForRotation(Surface.ROTATION_90)
- wmHelper.waitForAppTransitionIdle()
// System bar may fade out during fixed rotation.
- wmHelper.waitForNavBarStatusBarVisible()
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(pipApp.component)
+ .withRotation(Surface.ROTATION_90)
+ .withAppTransitionIdle()
+ .withNavBarStatusBarVisible()
+ .waitForAndVerify()
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
index 49094e609fbc..31fb16ffbd3e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipBasicTest.kt
@@ -43,7 +43,7 @@ class TvPipBasicTest(
// Set up ratio and enter Pip
testApp.clickObject(radioButtonId)
- testApp.clickEnterPipButton()
+ testApp.clickEnterPipButton(wmHelper)
val actualRatio: Float = testApp.ui?.visibleBounds?.ratio
?: fail("Application UI not found")
@@ -84,4 +84,4 @@ class TvPipBasicTest(
)
}
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
index 061218a015e4..4be19d61278b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipMenuTests.kt
@@ -244,7 +244,7 @@ class TvPipMenuTests : TvPipTestBase() {
}
private fun enterPip_openMenu_assertShown(): UiObject2 {
- testApp.clickEnterPipButton()
+ testApp.clickEnterPipButton(wmHelper)
// Pressing the Window key should bring up Pip menu
uiDevice.pressWindowKey()
return uiDevice.waitForTvPipMenu() ?: fail("Pip menu should have been shown")
@@ -256,4 +256,4 @@ class TvPipMenuTests : TvPipTestBase() {
uiDevice.findTvPipMenuFullscreenButton()
?: fail("\"Full screen\" button should be shown in Pip menu")
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
index bcf38d340867..134e97bd46e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
@@ -56,7 +56,7 @@ class TvPipNotificationTests : TvPipTestBase() {
@Test
fun pipNotification_postedAndDismissed() {
testApp.launchViaIntent()
- testApp.clickEnterPipButton()
+ testApp.clickEnterPipButton(wmHelper)
assertNotNull("Pip notification should have been posted",
waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.appName) })
@@ -70,7 +70,7 @@ class TvPipNotificationTests : TvPipTestBase() {
@Test
fun pipNotification_closeIntent() {
testApp.launchViaIntent()
- testApp.clickEnterPipButton()
+ testApp.clickEnterPipButton(wmHelper)
val notification: StatusBarNotification = waitForNotificationToAppear {
it.isPipNotificationWithTitle(testApp.appName)
@@ -87,8 +87,8 @@ class TvPipNotificationTests : TvPipTestBase() {
@Test
fun pipNotification_menuIntent() {
- testApp.launchViaIntent()
- testApp.clickEnterPipButton()
+ testApp.launchViaIntent(wmHelper)
+ testApp.clickEnterPipButton(wmHelper)
val notification: StatusBarNotification = waitForNotificationToAppear {
it.isPipNotificationWithTitle(testApp.appName)
@@ -106,10 +106,10 @@ class TvPipNotificationTests : TvPipTestBase() {
@Test
fun pipNotification_mediaSessionTitle_isDisplayed() {
- testApp.launchViaIntent()
+ testApp.launchViaIntent(wmHelper)
// Start media session and to PiP
testApp.clickStartMediaSessionButton()
- testApp.clickEnterPipButton()
+ testApp.clickEnterPipButton(wmHelper)
// Wait for the correct notification to show up...
waitForNotificationToAppear {
@@ -170,4 +170,4 @@ private val StatusBarNotification.deleteIntent: PendingIntent?
get() = tvExtensions?.getParcelable("delete_intent")
private fun StatusBarNotification.isPipNotificationWithTitle(expectedTitle: String): Boolean =
- tag == "TvPip" && title == expectedTitle \ No newline at end of file
+ tag == "TvPip" && title == expectedTitle
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index 9c3b0fa183b6..a97994e7d6c3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -23,6 +23,7 @@ import android.os.SystemClock
import android.view.Surface.ROTATION_0
import android.view.Surface.rotationToString
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.pip.PipTestBase
import org.junit.After
@@ -33,6 +34,7 @@ import org.junit.Before
abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATION_0) {
private val systemUiProcessObserver = SystemUiProcessObserver()
+ protected val wmHelper = WindowManagerStateHelper()
@Before
final override fun televisionSetUp() {
@@ -88,4 +90,4 @@ abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATIO
companion object {
private const val AFTER_TEXT_PROCESS_CHECK_DELAY = 1_000L // 1 sec
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 702710caded7..cfba3387825c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -56,7 +56,7 @@ class EnterSplitScreenByDragFromAllApps(
) : SplitScreenBase(testSpec) {
@Before
- open fun before() {
+ fun before() {
Assume.assumeTrue(taplInstrumentation.isTablet)
}
@@ -73,8 +73,7 @@ class EnterSplitScreenByDragFromAllApps(
taplInstrumentation.launchedAppState.taskbar
.openAllApps()
.getAppIcon(secondaryApp.appName)
- .dragToSplitscreen(secondaryApp.component.packageName,
- primaryApp.component.packageName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
}
}
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index cab3f8414d7b..130b204954b4 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -131,8 +131,9 @@ void SpriteController::doUpdateSprites() {
update.state.surfaceHeight = update.state.icon.height();
update.state.surfaceDrawn = false;
update.state.surfaceVisible = false;
- update.state.surfaceControl = obtainSurface(
- update.state.surfaceWidth, update.state.surfaceHeight);
+ update.state.surfaceControl =
+ obtainSurface(update.state.surfaceWidth, update.state.surfaceHeight,
+ update.state.displayId);
if (update.state.surfaceControl != NULL) {
update.surfaceChanged = surfaceChanged = true;
}
@@ -168,8 +169,8 @@ void SpriteController::doUpdateSprites() {
}
}
- // If surface is a new one, we have to set right layer stack.
- if (update.surfaceChanged || update.state.dirty & DIRTY_DISPLAY_ID) {
+ // If surface has changed to a new display, we have to reparent it.
+ if (update.state.dirty & DIRTY_DISPLAY_ID) {
t.reparent(update.state.surfaceControl, mParentSurfaceProvider(update.state.displayId));
needApplyTransaction = true;
}
@@ -330,21 +331,28 @@ void SpriteController::ensureSurfaceComposerClient() {
}
}
-sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height) {
+sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height,
+ int32_t displayId) {
ensureSurfaceComposerClient();
- sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
- String8("Sprite"), width, height, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eHidden |
- ISurfaceComposerClient::eCursorWindow);
- if (surfaceControl == NULL || !surfaceControl->isValid()) {
+ const sp<SurfaceControl> parent = mParentSurfaceProvider(displayId);
+ if (parent == nullptr) {
+ ALOGE("Failed to get the parent surface for pointers on display %d", displayId);
+ }
+
+ const sp<SurfaceControl> surfaceControl =
+ mSurfaceComposerClient->createSurface(String8("Sprite"), width, height,
+ PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eHidden |
+ ISurfaceComposerClient::eCursorWindow,
+ parent ? parent->getHandle() : nullptr);
+ if (surfaceControl == nullptr || !surfaceControl->isValid()) {
ALOGE("Error creating sprite surface.");
- return NULL;
+ return nullptr;
}
return surfaceControl;
}
-
// --- SpriteController::SpriteImpl ---
SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 2e9cb9685c46..1f113c045360 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -265,7 +265,7 @@ private:
void doDisposeSurfaces();
void ensureSurfaceComposerClient();
- sp<SurfaceControl> obtainSurface(int32_t width, int32_t height);
+ sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId);
};
} // namespace android
diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp
index 132d71eb6db8..128be3c33ac5 100644
--- a/libs/protoutil/Android.bp
+++ b/libs/protoutil/Android.bp
@@ -55,6 +55,7 @@ cc_library {
export_include_dirs: ["include"],
+ min_sdk_version: "30",
apex_available: [
"//apex_available:platform",
"com.android.os.statsd",
@@ -81,5 +82,5 @@ cc_test {
proto: {
type: "full",
- }
+ },
}
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
index b136d5bc4db3..2bdd5c8bc977 100644
--- a/media/java/android/media/projection/IMediaProjection.aidl
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -17,7 +17,7 @@
package android.media.projection;
import android.media.projection.IMediaProjectionCallback;
-import android.window.WindowContainerToken;
+import android.os.IBinder;
/** {@hide} */
interface IMediaProjection {
@@ -31,14 +31,14 @@ interface IMediaProjection {
void unregisterCallback(IMediaProjectionCallback callback);
/**
- * Returns the {@link android.window.WindowContainerToken} identifying the task to record, or
- * {@code null} if there is none.
+ * Returns the {@link android.os.IBinder} identifying the task to record, or {@code null} if
+ * there is none.
*/
- WindowContainerToken getTaskRecordingWindowContainerToken();
+ IBinder getLaunchCookie();
/**
- * Updates the {@link android.window.WindowContainerToken} identifying the task to record, or
- * {@code null} if there is none.
+ * Updates the {@link android.os.IBinder} identifying the task to record, or {@code null} if
+ * there is none.
*/
- void setTaskRecordingWindowContainerToken(in WindowContainerToken token);
+ void setLaunchCookie(in IBinder launchCookie);
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index ba7bf3f5f5d3..ae44fc575f7c 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -25,13 +25,13 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.Log;
import android.view.ContentRecordingSession;
import android.view.Surface;
-import android.window.WindowContainerToken;
import java.util.Map;
@@ -172,18 +172,16 @@ public final class MediaProjection {
@NonNull VirtualDisplayConfig.Builder virtualDisplayConfig,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
try {
- final WindowContainerToken taskWindowContainerToken =
- mImpl.getTaskRecordingWindowContainerToken();
+ final IBinder launchCookie = mImpl.getLaunchCookie();
Context windowContext = null;
ContentRecordingSession session;
- if (taskWindowContainerToken == null) {
+ if (launchCookie == null) {
windowContext = mContext.createWindowContext(mContext.getDisplayNoVerify(),
TYPE_APPLICATION, null /* options */);
session = ContentRecordingSession.createDisplaySession(
windowContext.getWindowContextToken());
} else {
- session = ContentRecordingSession.createTaskSession(
- taskWindowContainerToken.asBinder());
+ session = ContentRecordingSession.createTaskSession(launchCookie);
}
virtualDisplayConfig.setWindowManagerMirroring(true);
final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 88ca30ceb8a6..c0261f203604 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -1613,7 +1613,9 @@ public final class TvInteractiveAppManager {
mHandler.post(new Runnable() {
@Override
public void run() {
- mSession.getInputSession().requestBroadcastInfo(request);
+ if (mSession.getInputSession() != null) {
+ mSession.getInputSession().requestBroadcastInfo(request);
+ }
}
});
}
@@ -1622,7 +1624,9 @@ public final class TvInteractiveAppManager {
mHandler.post(new Runnable() {
@Override
public void run() {
- mSession.getInputSession().removeBroadcastInfo(requestId);
+ if (mSession.getInputSession() != null) {
+ mSession.getInputSession().removeBroadcastInfo(requestId);
+ }
}
});
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 6b9daa35f9ca..cf23a5446f91 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -48,7 +48,6 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.format.Formatter;
-import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.SparseArray;
@@ -116,7 +115,6 @@ public class ApplicationsState {
final Context mContext;
final PackageManager mPm;
- final IconDrawableFactory mDrawableFactory;
final IPackageManager mIpm;
final UserManager mUm;
final StorageStatsManager mStats;
@@ -194,7 +192,6 @@ public class ApplicationsState {
private ApplicationsState(Application app, IPackageManager iPackageManager) {
mContext = app;
mPm = mContext.getPackageManager();
- mDrawableFactory = IconDrawableFactory.newInstance(mContext);
mIpm = iPackageManager;
mUm = mContext.getSystemService(UserManager.class);
mStats = mContext.getSystemService(StorageStatsManager.class);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 89e10c4b5e11..fc70ba40fb47 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -20,15 +20,19 @@ import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
+import android.os.Build;
import android.os.ParcelUuid;
import android.util.Log;
+import androidx.annotation.ChecksSdkIntAtLeast;
+
import com.android.internal.annotations.VisibleForTesting;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* CsipDeviceManager manages the set of remote CSIP Bluetooth devices.
@@ -126,32 +130,84 @@ public class CsipDeviceManager {
}
}
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
+ private static boolean isAtLeastT() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
+ }
+
// Group devices by groupId
@VisibleForTesting
void onGroupIdChanged(int groupId) {
- int firstMatchedIndex = -1;
- CachedBluetoothDevice mainDevice = null;
+ if (!isValidGroupId(groupId)) {
+ log("onGroupIdChanged: groupId is invalid");
+ return;
+ }
+ log("onGroupIdChanged: mCachedDevices list =" + mCachedDevices.toString());
+ final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+ final CachedBluetoothDeviceManager deviceManager = mBtManager.getCachedDeviceManager();
+ final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile();
+ final BluetoothDevice mainBluetoothDevice = (leAudioProfile != null && isAtLeastT()) ?
+ leAudioProfile.getConnectedGroupLeadDevice(groupId) : null;
+ CachedBluetoothDevice newMainDevice =
+ mainBluetoothDevice != null ? deviceManager.findDevice(mainBluetoothDevice) : null;
+ if (newMainDevice != null) {
+ final CachedBluetoothDevice finalNewMainDevice = newMainDevice;
+ final List<CachedBluetoothDevice> memberDevices = mCachedDevices.stream()
+ .filter(cachedDevice -> !cachedDevice.equals(finalNewMainDevice)
+ && cachedDevice.getGroupId() == groupId)
+ .collect(Collectors.toList());
+ if (memberDevices == null || memberDevices.isEmpty()) {
+ log("onGroupIdChanged: There is no member device in list.");
+ return;
+ }
+ log("onGroupIdChanged: removed from UI device =" + memberDevices
+ + ", with groupId=" + groupId + " mainDevice= " + newMainDevice);
+ for (CachedBluetoothDevice memberDeviceItem : memberDevices) {
+ Set<CachedBluetoothDevice> memberSet = memberDeviceItem.getMemberDevice();
+ if (!memberSet.isEmpty()) {
+ log("onGroupIdChanged: Transfer the member list into new main device.");
+ for (CachedBluetoothDevice memberListItem : memberSet) {
+ if (!memberListItem.equals(newMainDevice)) {
+ newMainDevice.addMemberDevice(memberListItem);
+ }
+ }
+ memberSet.clear();
+ }
- for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
- final CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
- if (cachedDevice.getGroupId() != groupId) {
- continue;
+ newMainDevice.addMemberDevice(memberDeviceItem);
+ mCachedDevices.remove(memberDeviceItem);
+ mBtManager.getEventManager().dispatchDeviceRemoved(memberDeviceItem);
}
- if (firstMatchedIndex == -1) {
- // Found the first one
- firstMatchedIndex = i;
- mainDevice = cachedDevice;
- continue;
+ if (!mCachedDevices.contains(newMainDevice)) {
+ mCachedDevices.add(newMainDevice);
+ mBtManager.getEventManager().dispatchDeviceAdded(newMainDevice);
}
+ } else {
+ log("onGroupIdChanged: There is no main device from the LE profile.");
+ int firstMatchedIndex = -1;
- log("onGroupIdChanged: removed from UI device =" + cachedDevice
- + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex);
+ for (int i = mCachedDevices.size() - 1; i >= 0; i--) {
+ final CachedBluetoothDevice cachedDevice = mCachedDevices.get(i);
+ if (cachedDevice.getGroupId() != groupId) {
+ continue;
+ }
+
+ if (firstMatchedIndex == -1) {
+ // Found the first one
+ firstMatchedIndex = i;
+ newMainDevice = cachedDevice;
+ continue;
+ }
+
+ log("onGroupIdChanged: removed from UI device =" + cachedDevice
+ + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex);
- mainDevice.addMemberDevice(cachedDevice);
- mCachedDevices.remove(i);
- mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
- break;
+ newMainDevice.addMemberDevice(cachedDevice);
+ mCachedDevices.remove(i);
+ mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice);
+ break;
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 19df1e9c0730..0f57d8785de9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -21,6 +21,7 @@ import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_ALL;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothCodecConfig;
@@ -183,6 +184,37 @@ public class LeAudioProfile implements LocalBluetoothProfile {
return mBluetoothAdapter.getActiveDevices(BluetoothProfile.LE_AUDIO);
}
+ /**
+ * Get Lead device for the group.
+ *
+ * Lead device is the device that can be used as an active device in the system.
+ * Active devices points to the Audio Device for the Le Audio group.
+ * This method returns the Lead devices for the connected LE Audio
+ * group and this device should be used in the setActiveDevice() method by other parts
+ * of the system, which wants to set to active a particular Le Audio group.
+ *
+ * Note: getActiveDevice() returns the Lead device for the currently active LE Audio group.
+ * Note: When Lead device gets disconnected while Le Audio group is active and has more devices
+ * in the group, then Lead device will not change. If Lead device gets disconnected, for the
+ * Le Audio group which is not active, a new Lead device will be chosen
+ *
+ * @param groupId The group id.
+ * @return group lead device.
+ *
+ * @hide
+ */
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ public @Nullable BluetoothDevice getConnectedGroupLeadDevice(int groupId) {
+ if (DEBUG) {
+ Log.d(TAG,"getConnectedGroupLeadDevice");
+ }
+ if (mService == null) {
+ Log.e(TAG,"No service.");
+ return null;
+ }
+ return mService.getConnectedGroupLeadDevice(groupId);
+ }
+
@Override
public boolean isEnabled(BluetoothDevice device) {
if (mService == null || device == null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 4939e04dad6a..132a631e25cc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -135,7 +135,7 @@ public class BatteryStatus {
* @return true if the device is charged
*/
public boolean isCharged() {
- return status == BATTERY_STATUS_FULL || level >= 100;
+ return isCharged(status, level);
}
/**
@@ -177,4 +177,31 @@ public class BatteryStatus {
return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged
+ ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}";
}
+
+ /**
+ * Whether or not the device is charged. Note that some devices never return 100% for
+ * battery level, so this allows either battery level or status to determine if the
+ * battery is charged.
+ *
+ * @param batteryChangedIntent ACTION_BATTERY_CHANGED intent
+ * @return true if the device is charged
+ */
+ public static boolean isCharged(Intent batteryChangedIntent) {
+ int status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
+ int level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0);
+ return isCharged(status, level);
+ }
+
+ /**
+ * Whether or not the device is charged. Note that some devices never return 100% for
+ * battery level, so this allows either battery level or status to determine if the
+ * battery is charged.
+ *
+ * @param status values for "status" field in the ACTION_BATTERY_CHANGED Intent
+ * @param level values from 0 to 100
+ * @return true if the device is charged
+ */
+ public static boolean isCharged(int status, int level) {
+ return status == BATTERY_STATUS_FULL || level >= 100;
+ }
}
diff --git a/packages/StatementService/OWNERS b/packages/StatementService/OWNERS
new file mode 100644
index 000000000000..f0b4ce7bf5b1
--- /dev/null
+++ b/packages/StatementService/OWNERS
@@ -0,0 +1,2 @@
+include /PACKAGE_MANAGER_OWNERS
+
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
index 0ec8ed3416b8..acb54f6093de 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
@@ -67,6 +67,10 @@ class DomainVerificationReceiverV1 : BaseDomainVerificationReceiver() {
}
}
+ //clear sp before enqueue unique work since policy is REPLACE
+ val deContext = context.createDeviceProtectedStorageContext()
+ val editor = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)?.edit()
+ editor?.clear()?.apply()
WorkManager.getInstance(context)
.beginUniqueWork(
"$PACKAGE_WORK_PREFIX_V1$packageName",
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/CollectV1Worker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/CollectV1Worker.kt
index 3a3aea9288cd..36c81722b5d9 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/CollectV1Worker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/CollectV1Worker.kt
@@ -41,9 +41,7 @@ class CollectV1Worker(appContext: Context, params: WorkerParameters) :
Data.Builder()
.putInt(VERIFICATION_ID_KEY, verificationId)
.apply {
- if (DEBUG) {
- putString(PACKAGE_NAME_KEY, packageName)
- }
+ putString(PACKAGE_NAME_KEY, packageName)
}
.build()
)
@@ -52,6 +50,18 @@ class CollectV1Worker(appContext: Context, params: WorkerParameters) :
override suspend fun doWork() = coroutineScope {
if (!AndroidUtils.isReceiverV1Enabled(appContext)) {
+ //clear sp and commit here
+ val inputData = params.inputData
+ val packageName = inputData.getString(PACKAGE_NAME_KEY)
+ val deContext = appContext.createDeviceProtectedStorageContext()
+ val sp = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)
+ val editor = sp?.edit()
+ editor?.clear()?.commit()
+ //delete sp file
+ val retOfDel = deContext?.deleteSharedPreferences(packageName)
+ if (DEBUG) {
+ Log.d(TAG, "delete sp for $packageName return $retOfDel")
+ }
return@coroutineScope Result.success()
}
@@ -59,7 +69,10 @@ class CollectV1Worker(appContext: Context, params: WorkerParameters) :
val verificationId = inputData.getInt(VERIFICATION_ID_KEY, -1)
val successfulHosts = mutableListOf<String>()
val failedHosts = mutableListOf<String>()
- inputData.keyValueMap.entries.forEach { (key, _) ->
+ val packageName = inputData.getString(PACKAGE_NAME_KEY)
+ val deContext = appContext.createDeviceProtectedStorageContext()
+ val sp = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)
+ sp?.all?.entries?.forEach { (key, _) ->
when {
key.startsWith(SingleV1RequestWorker.HOST_SUCCESS_PREFIX) ->
successfulHosts += key.removePrefix(SingleV1RequestWorker.HOST_SUCCESS_PREFIX)
@@ -69,7 +82,6 @@ class CollectV1Worker(appContext: Context, params: WorkerParameters) :
}
if (DEBUG) {
- val packageName = inputData.getString(PACKAGE_NAME_KEY)
Log.d(
TAG, "Domain verification v1 request for $packageName: " +
"success = $successfulHosts, failed = $failedHosts"
@@ -84,6 +96,15 @@ class CollectV1Worker(appContext: Context, params: WorkerParameters) :
appContext.packageManager.verifyIntentFilter(verificationId, resultCode, failedHosts)
+ //clear sp and commit here
+ val editor = sp?.edit()
+ editor?.clear()?.commit()
+ //delete sp file
+ val retOfDel = deContext?.deleteSharedPreferences(packageName)
+ if (DEBUG) {
+ Log.d(TAG, "delete sp for $packageName return $retOfDel")
+ }
+
Result.success()
}
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
index cd8a18218004..7a198cb59ca4 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
@@ -71,16 +71,18 @@ class SingleV1RequestWorker(appContext: Context, params: WorkerParameters) :
// Coerce failure results into success so that final collection task gets a chance to run
when (result) {
- is Result.Success -> Result.success(
- Data.Builder()
- .putInt("$HOST_SUCCESS_PREFIX$host", status.value)
- .build()
- )
- is Result.Failure -> Result.success(
- Data.Builder()
- .putInt("$HOST_FAILURE_PREFIX$host", status.value)
- .build()
- )
+ is Result.Success -> {
+ val deContext = appContext.createDeviceProtectedStorageContext()
+ val sp = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)
+ sp?.edit()?.putInt("$HOST_SUCCESS_PREFIX$host", status.value)?.apply()
+ Result.success()
+ }
+ is Result.Failure -> {
+ val deContext = appContext.createDeviceProtectedStorageContext()
+ val sp = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)
+ sp?.edit()?.putInt("$HOST_FAILURE_PREFIX$host", status.value)?.apply()
+ Result.success()
+ }
else -> result
}
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index de9e1f4ccec5..bae7064f9b0f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -93,6 +93,7 @@ android_library {
"SystemUISharedLib",
"SystemUI-statsd",
"SettingsLib",
+ "androidx.core_core-ktx",
"androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
"androidx.recyclerview_recyclerview",
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 3bd6d51eecc6..f9a9ef65cab6 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -52,6 +52,17 @@
]
},
{
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
// Permission indicators
"name": "CtsPermission4TestCases",
"options": [
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 5b47ae525919..fbe33565f11d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -656,7 +656,7 @@ class ActivityLaunchAnimator(
controller.onLaunchAnimationCancelled()
}
- override fun onAnimationCancelled() {
+ override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {
if (timedOut) {
return
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
index 1ad54846fb25..c58c16259abe 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
@@ -17,8 +17,12 @@
package com.android.systemui
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -29,14 +33,37 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import kotlin.math.roundToInt
/**
* This is an example Compose feature, which shows a text and a count that is incremented when
- * clicked.
+ * clicked. We also show the max width available to this component, which is displayed either next
+ * to or below the text depending on that max width.
*/
@Composable
fun ExampleFeature(text: String, modifier: Modifier = Modifier) {
+ BoxWithConstraints(modifier) {
+ val maxWidth = maxWidth
+ if (maxWidth < 600.dp) {
+ Column {
+ CounterTile(text)
+ Spacer(Modifier.size(16.dp))
+ MaxWidthTile(maxWidth)
+ }
+ } else {
+ Row {
+ CounterTile(text)
+ Spacer(Modifier.size(16.dp))
+ MaxWidthTile(maxWidth)
+ }
+ }
+ }
+}
+
+@Composable
+private fun CounterTile(text: String, modifier: Modifier = Modifier) {
Surface(
modifier,
color = MaterialTheme.colorScheme.primaryContainer,
@@ -51,3 +78,17 @@ fun ExampleFeature(text: String, modifier: Modifier = Modifier) {
}
}
}
+
+@Composable
+private fun MaxWidthTile(maxWidth: Dp, modifier: Modifier = Modifier) {
+ Surface(
+ modifier,
+ color = MaterialTheme.colorScheme.tertiaryContainer,
+ shape = RoundedCornerShape(28.dp),
+ ) {
+ Text(
+ "The max available width to me is: ${maxWidth.value.roundToInt()}dp",
+ Modifier.padding(16.dp)
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp
index 030ad854992a..bddc1e63fee8 100644
--- a/packages/SystemUI/compose/gallery/Android.bp
+++ b/packages/SystemUI/compose/gallery/Android.bp
@@ -52,6 +52,7 @@ android_library {
android_app {
name: "SystemUIComposeGallery",
defaults: ["platform_app_defaults"],
+ manifest: "app/AndroidManifest.xml",
static_libs: [
"SystemUIComposeGalleryLib",
diff --git a/packages/SystemUI/compose/gallery/AndroidManifest.xml b/packages/SystemUI/compose/gallery/AndroidManifest.xml
index 81132632079a..562e1cc3339a 100644
--- a/packages/SystemUI/compose/gallery/AndroidManifest.xml
+++ b/packages/SystemUI/compose/gallery/AndroidManifest.xml
@@ -17,21 +17,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.systemui.compose.gallery">
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/Theme.SystemUIGallery">
- <activity
- android:name=".GalleryActivity"
- android:exported="true"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
+
</manifest>
diff --git a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
new file mode 100644
index 000000000000..81132632079a
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.compose.gallery">
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.SystemUIGallery">
+ <activity
+ android:name=".GalleryActivity"
+ android:exported="true"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
index 652c403470f1..6e1721490f98 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
@@ -18,10 +18,11 @@ package com.android.systemui.compose.gallery
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
import com.android.systemui.ExampleFeature
/** The screen that shows ExampleFeature. */
@Composable
-fun ExampleFeatureScreen() {
- Column { ExampleFeature("This is an example feature!") }
+fun ExampleFeatureScreen(modifier: Modifier = Modifier) {
+ Column(modifier) { ExampleFeature("This is an example feature!") }
}
diff --git a/packages/SystemUI/compose/testing/Android.bp b/packages/SystemUI/compose/testing/Android.bp
new file mode 100644
index 000000000000..293e51f68b98
--- /dev/null
+++ b/packages/SystemUI/compose/testing/Android.bp
@@ -0,0 +1,43 @@
+// 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: "SystemUIComposeTesting",
+ manifest: "AndroidManifest.xml",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "SystemUIComposeCore",
+ "SystemUIScreenshotLib",
+
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.material3_material3",
+ "androidx.compose.ui_ui-test-junit4",
+ "androidx.compose.ui_ui-test-manifest",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/compose/testing/AndroidManifest.xml b/packages/SystemUI/compose/testing/AndroidManifest.xml
new file mode 100644
index 000000000000..b1f7c3be2796
--- /dev/null
+++ b/packages/SystemUI/compose/testing/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.systemui.testing.compose">
+ <application
+ android:appComponentFactory="androidx.core.app.AppComponentFactory"
+ tools:replace="android:appComponentFactory">
+ </application>
+</manifest>
diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
new file mode 100644
index 000000000000..7a0b1f190f99
--- /dev/null
+++ b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.compose
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onRoot
+import com.android.systemui.compose.theme.SystemUITheme
+import com.android.systemui.testing.screenshot.ScreenshotActivity
+import com.android.systemui.testing.screenshot.ScreenshotTestRule
+import com.android.systemui.testing.screenshot.ScreenshotTestSpec
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/** A rule for Compose screenshot diff tests. */
+class ComposeScreenshotTestRule(testSpec: ScreenshotTestSpec) : TestRule {
+ private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
+ private val screenshotRule = ScreenshotTestRule(testSpec)
+
+ private val delegate = RuleChain.outerRule(screenshotRule).around(composeRule)
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return delegate.apply(base, description)
+ }
+
+ /**
+ * Compare [content] with the golden image identified by [goldenIdentifier] in the context of
+ * [testSpec].
+ */
+ fun screenshotTest(
+ goldenIdentifier: String,
+ content: @Composable () -> Unit,
+ ) {
+ // Make sure that the activity draws full screen and fits the whole display instead of the
+ // system bars.
+ val activity = composeRule.activity
+ activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
+
+ // Set the content using the AndroidComposeRule to make sure that the Activity is set up
+ // correctly.
+ composeRule.setContent {
+ SystemUITheme {
+ Surface(
+ color = MaterialTheme.colorScheme.background,
+ ) {
+ content()
+ }
+ }
+ }
+ composeRule.waitForIdle()
+
+ val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
+ screenshotRule.screenshotTest(goldenIdentifier, view)
+ }
+}
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index 5e8b892018eb..2b3d11b0e191 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -14,20 +14,19 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<!-- TODO(b/203800646): layout_marginTop doesn't seem to work on some large screens. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/media_ttt_receiver_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@drawable/media_ttt_chip_background_receiver"
>
<com.android.internal.widget.CachingIconView
android:id="@+id/app_icon"
android:layout_width="@dimen/media_ttt_icon_size_receiver"
android:layout_height="@dimen/media_ttt_icon_size_receiver"
- android:layout_gravity="center"
+ android:layout_gravity="center|bottom"
+ android:alpha="0.0"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 1210b79d3ff5..009a123de40c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1051,6 +1051,7 @@
<!-- Since the generic icon isn't circular, we need to scale it down so it still fits within
the circular chip. -->
<dimen name="media_ttt_generic_icon_size_receiver">70dp</dimen>
+ <dimen name="media_ttt_receiver_vert_translation">20dp</dimen>
<!-- Window magnification -->
<dimen name="magnification_border_drag_size">35dp</dimen>
diff --git a/packages/SystemUI/screenshot/Android.bp b/packages/SystemUI/screenshot/Android.bp
new file mode 100644
index 000000000000..a79fd9040db3
--- /dev/null
+++ b/packages/SystemUI/screenshot/Android.bp
@@ -0,0 +1,48 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUIScreenshotLib",
+ manifest: "AndroidManifest.xml",
+
+ srcs: [
+ // All files in this library should be in Kotlin besides some exceptions.
+ "src/**/*.kt",
+
+ // This file was forked from google3, so exceptionally it can be in Java.
+ "src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java",
+ ],
+
+ resource_dirs: [
+ "res",
+ ],
+
+ static_libs: [
+ "SystemUI-core",
+ "androidx.test.espresso.core",
+ "androidx.appcompat_appcompat",
+ "platform-screenshot-diff-core",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/screenshot/AndroidManifest.xml b/packages/SystemUI/screenshot/AndroidManifest.xml
new file mode 100644
index 000000000000..3b703be34e5d
--- /dev/null
+++ b/packages/SystemUI/screenshot/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.testing.screenshot">
+ <application>
+ <activity
+ android:name="com.android.systemui.testing.screenshot.ScreenshotActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SystemUI.Screenshot" />
+ </application>
+
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+</manifest>
diff --git a/packages/SystemUI/screenshot/res/values/themes.xml b/packages/SystemUI/screenshot/res/values/themes.xml
new file mode 100644
index 000000000000..40e50bbb6bbf
--- /dev/null
+++ b/packages/SystemUI/screenshot/res/values/themes.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <style name="Theme.SystemUI.Screenshot" parent="Theme.SystemUI">
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+
+ <!-- Make sure that device specific cutouts don't impact the outcome of screenshot tests -->
+ <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java
new file mode 100644
index 000000000000..96ec4c543474
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/DynamicColorsTestUtils.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.ColorRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.core.content.ContextCompat;
+import androidx.test.espresso.Espresso;
+import androidx.test.espresso.IdlingRegistry;
+import androidx.test.espresso.IdlingResource;
+
+import org.json.JSONObject;
+import org.junit.function.ThrowingRunnable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/*
+ * Note: This file was forked from
+ * google3/third_party/java_src/android_libs/material_components/screenshot_tests/java/android/
+ * support/design/scuba/color/DynamicColorsTestUtils.java.
+ */
+
+/** Utility that helps change the dynamic system colors for testing. */
+@RequiresApi(32)
+public class DynamicColorsTestUtils {
+
+ private static final String TAG = DynamicColorsTestUtils.class.getSimpleName();
+
+ private static final String THEME_CUSTOMIZATION_KEY = "theme_customization_overlay_packages";
+ private static final String THEME_CUSTOMIZATION_SYSTEM_PALETTE_KEY =
+ "android.theme.customization.system_palette";
+
+ private static final int ORANGE_SYSTEM_SEED_COLOR = 0xA66800;
+ private static final int ORANGE_EXPECTED_SYSTEM_ACCENT1_600_COLOR = -8235756;
+
+ private DynamicColorsTestUtils() {
+ }
+
+ /**
+ * Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on an orange
+ * seed color, and then wait for the change to propagate to the app by comparing
+ * android.R.color.system_accent1_600 to the expected orange value.
+ */
+ public static void updateSystemColorsToOrange() {
+ updateSystemColors(ORANGE_SYSTEM_SEED_COLOR, ORANGE_EXPECTED_SYSTEM_ACCENT1_600_COLOR);
+ }
+
+ /**
+ * Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on the provided
+ * {@code seedColor}, and then wait for the change to propagate to the app by comparing
+ * android.R.color.system_accent1_600 to {@code expectedSystemAccent1600}.
+ */
+ public static void updateSystemColors(
+ @ColorInt int seedColor, @ColorInt int expectedSystemAccent1600) {
+ Context context = getInstrumentation().getTargetContext();
+
+ int actualSystemAccent1600 =
+ ContextCompat.getColor(context, android.R.color.system_accent1_600);
+
+ if (expectedSystemAccent1600 == actualSystemAccent1600) {
+ String expectedColorString = Integer.toHexString(expectedSystemAccent1600);
+ Log.d(
+ TAG,
+ "Skipped updating system colors since system_accent1_600 is already equal to "
+ + "expected: "
+ + expectedColorString);
+ return;
+ }
+
+ updateSystemColors(seedColor);
+ }
+
+ /**
+ * Update system dynamic colors (e.g., android.R.color.system_accent1_600) based on the provided
+ * {@code seedColor}, and then wait for the change to propagate to the app by checking
+ * android.R.color.system_accent1_600 for any change.
+ */
+ public static void updateSystemColors(@ColorInt int seedColor) {
+ Context context = getInstrumentation().getTargetContext();
+
+ // Initialize system color idling resource with original system_accent1_600 value.
+ ColorChangeIdlingResource systemColorIdlingResource =
+ new ColorChangeIdlingResource(context, android.R.color.system_accent1_600);
+
+ // Update system theme color setting to trigger fabricated resource overlay.
+ runWithShellPermissionIdentity(
+ () ->
+ Settings.Secure.putString(
+ context.getContentResolver(),
+ THEME_CUSTOMIZATION_KEY,
+ buildThemeCustomizationString(seedColor)));
+
+ // Wait for system color update to propagate to app.
+ IdlingRegistry idlingRegistry = IdlingRegistry.getInstance();
+ idlingRegistry.register(systemColorIdlingResource);
+ Espresso.onIdle();
+ idlingRegistry.unregister(systemColorIdlingResource);
+
+ Log.d(TAG,
+ Settings.Secure.getString(context.getContentResolver(), THEME_CUSTOMIZATION_KEY));
+ }
+
+ private static String buildThemeCustomizationString(@ColorInt int seedColor) {
+ String seedColorHex = Integer.toHexString(seedColor);
+ Map<String, String> themeCustomizationMap = new HashMap<>();
+ themeCustomizationMap.put(THEME_CUSTOMIZATION_SYSTEM_PALETTE_KEY, seedColorHex);
+ return new JSONObject(themeCustomizationMap).toString();
+ }
+
+ private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable) {
+ UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity();
+ try {
+ runnable.run();
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ private static class ColorChangeIdlingResource implements IdlingResource {
+
+ private final Context mContext;
+ private final int mColorResId;
+ private final int mInitialColorInt;
+
+ private ResourceCallback mResourceCallback;
+ private boolean mIdleNow;
+
+ ColorChangeIdlingResource(Context context, @ColorRes int colorResId) {
+ this.mContext = context;
+ this.mColorResId = colorResId;
+ this.mInitialColorInt = ContextCompat.getColor(context, colorResId);
+ }
+
+ @Override
+ public String getName() {
+ return ColorChangeIdlingResource.class.getName();
+ }
+
+ @Override
+ public boolean isIdleNow() {
+ if (mIdleNow) {
+ return true;
+ }
+
+ int currentColorInt = ContextCompat.getColor(mContext, mColorResId);
+
+ String initialColorString = Integer.toHexString(mInitialColorInt);
+ String currentColorString = Integer.toHexString(currentColorInt);
+ Log.d(TAG, String.format("Initial=%s, Current=%s", initialColorString,
+ currentColorString));
+
+ mIdleNow = currentColorInt != mInitialColorInt;
+ Log.d(TAG, String.format("idleNow=%b", mIdleNow));
+
+ if (mIdleNow) {
+ mResourceCallback.onTransitionToIdle();
+ }
+ return mIdleNow;
+ }
+
+ @Override
+ public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
+ this.mResourceCallback = resourceCallback;
+ }
+ }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
new file mode 100644
index 000000000000..2a55a80eb7f4
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+import androidx.activity.ComponentActivity
+
+/** The Activity that is launched and whose content is set for screenshot tests. */
+class ScreenshotActivity : ComponentActivity()
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestRule.kt
new file mode 100644
index 000000000000..363ce10fa36c
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestRule.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+import android.app.UiModeManager
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.os.UserHandle
+import android.view.Display
+import android.view.View
+import android.view.WindowManagerGlobal
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.PathConfig
+import platform.test.screenshot.PathElementNoContext
+import platform.test.screenshot.ScreenshotTestRule
+import platform.test.screenshot.matchers.PixelPerfectMatcher
+
+/**
+ * A base rule for screenshot diff tests.
+ *
+ * This rules takes care of setting up the activity according to [testSpec] by:
+ * - emulating the display size and density.
+ * - setting the dark/light mode.
+ * - setting the system (Material You) colors to a fixed value.
+ *
+ * @see ComposeScreenshotTestRule
+ * @see ViewScreenshotTestRule
+ */
+class ScreenshotTestRule(private val testSpec: ScreenshotTestSpec) : TestRule {
+ private var currentDisplay: DisplaySpec? = null
+ private var currentGoldenIdentifier: String? = null
+
+ private val pathConfig =
+ PathConfig(
+ PathElementNoContext("model", isDir = true) {
+ currentDisplay?.name ?: error("currentDisplay is null")
+ },
+ )
+ private val defaultMatcher = PixelPerfectMatcher()
+
+ private val screenshotRule =
+ ScreenshotTestRule(
+ SystemUIGoldenImagePathManager(
+ pathConfig,
+ currentGoldenIdentifier = {
+ currentGoldenIdentifier ?: error("currentGoldenIdentifier is null")
+ },
+ )
+ )
+
+ override fun apply(base: Statement, description: Description): Statement {
+ // The statement which call beforeTest() before running the test and afterTest() afterwards.
+ val statement =
+ object : Statement() {
+ override fun evaluate() {
+ try {
+ beforeTest()
+ base.evaluate()
+ } finally {
+ afterTest()
+ }
+ }
+ }
+
+ return screenshotRule.apply(statement, description)
+ }
+
+ private fun beforeTest() {
+ // Update the system colors to a fixed color, so that tests don't depend on the host device
+ // extracted colors. Note that we don't restore the default device colors at the end of the
+ // test because changing the colors (and waiting for them to be applied) is costly and makes
+ // the screenshot tests noticeably slower.
+ DynamicColorsTestUtils.updateSystemColorsToOrange()
+
+ // Emulate the display size and density.
+ val display = testSpec.display
+ val density = display.densityDpi
+ val wm = WindowManagerGlobal.getWindowManagerService()
+ val (width, height) = getEmulatedDisplaySize()
+ wm.setForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, density, UserHandle.myUserId())
+ wm.setForcedDisplaySize(Display.DEFAULT_DISPLAY, width, height)
+
+ // Force the dark/light theme.
+ val uiModeManager =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
+ uiModeManager.setApplicationNightMode(
+ if (testSpec.isDarkTheme) {
+ UiModeManager.MODE_NIGHT_YES
+ } else {
+ UiModeManager.MODE_NIGHT_NO
+ }
+ )
+ }
+
+ private fun afterTest() {
+ // Reset the density and display size.
+ val wm = WindowManagerGlobal.getWindowManagerService()
+ wm.clearForcedDisplayDensityForUser(Display.DEFAULT_DISPLAY, UserHandle.myUserId())
+ wm.clearForcedDisplaySize(Display.DEFAULT_DISPLAY)
+
+ // Reset the dark/light theme.
+ val uiModeManager =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
+ uiModeManager.setApplicationNightMode(UiModeManager.MODE_NIGHT_AUTO)
+ }
+
+ /**
+ * Compare the content of [view] with the golden image identified by [goldenIdentifier] in the
+ * context of [testSpec].
+ */
+ fun screenshotTest(goldenIdentifier: String, view: View) {
+ val bitmap = drawIntoBitmap(view)
+
+ // Compare bitmap against golden asset.
+ val isDarkTheme = testSpec.isDarkTheme
+ val isLandscape = testSpec.isLandscape
+ val identifierWithSpec = buildString {
+ append(goldenIdentifier)
+ if (isDarkTheme) append("_dark")
+ if (isLandscape) append("_landscape")
+ }
+
+ // TODO(b/230832101): Provide a way to pass a PathConfig and override the file name on
+ // device to assertBitmapAgainstGolden instead?
+ currentDisplay = testSpec.display
+ currentGoldenIdentifier = goldenIdentifier
+ screenshotRule.assertBitmapAgainstGolden(bitmap, identifierWithSpec, defaultMatcher)
+ currentDisplay = null
+ currentGoldenIdentifier = goldenIdentifier
+ }
+
+ /** Draw [view] into a [Bitmap]. */
+ private fun drawIntoBitmap(view: View): Bitmap {
+ val bitmap =
+ Bitmap.createBitmap(
+ view.measuredWidth,
+ view.measuredHeight,
+ Bitmap.Config.ARGB_8888,
+ )
+ val canvas = Canvas(bitmap)
+ view.draw(canvas)
+ return bitmap
+ }
+
+ /** Get the emulated display size for [testSpec]. */
+ private fun getEmulatedDisplaySize(): Pair<Int, Int> {
+ val display = testSpec.display
+ val isPortraitNaturalPosition = display.width < display.height
+ return if (testSpec.isLandscape) {
+ if (isPortraitNaturalPosition) {
+ display.height to display.width
+ } else {
+ display.width to display.height
+ }
+ } else {
+ if (isPortraitNaturalPosition) {
+ display.width to display.height
+ } else {
+ display.height to display.width
+ }
+ }
+ }
+}
+
+private class SystemUIGoldenImagePathManager(
+ pathConfig: PathConfig,
+ private val currentGoldenIdentifier: () -> String,
+) :
+ GoldenImagePathManager(
+ appContext = InstrumentationRegistry.getInstrumentation().context,
+ deviceLocalPath =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/sysui_screenshots",
+ pathConfig = pathConfig,
+ ) {
+ // This string is appended to all actual/expected screenshots on the device. We append the
+ // golden identifier so that our pull_golden.py scripts can map a screenshot on device to its
+ // asset (and automatically update it, if necessary).
+ override fun toString() = currentGoldenIdentifier()
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestSpec.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestSpec.kt
new file mode 100644
index 000000000000..7fc624554738
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotTestSpec.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+/** The specification of a device display to be used in a screenshot test. */
+data class DisplaySpec(
+ val name: String,
+ val width: Int,
+ val height: Int,
+ val densityDpi: Int,
+)
+
+/** The specification of a screenshot diff test. */
+class ScreenshotTestSpec(
+ val display: DisplaySpec,
+ val isDarkTheme: Boolean = false,
+ val isLandscape: Boolean = false,
+) {
+ companion object {
+ /**
+ * Return a list of [ScreenshotTestSpec] for each of the [displays].
+ *
+ * If [isDarkTheme] is null, this will create a spec for both light and dark themes, for
+ * each of the orientation.
+ *
+ * If [isLandscape] is null, this will create a spec for both portrait and landscape, for
+ * each of the light/dark themes.
+ */
+ fun forDisplays(
+ vararg displays: DisplaySpec,
+ isDarkTheme: Boolean? = null,
+ isLandscape: Boolean? = null,
+ ): List<ScreenshotTestSpec> {
+ return displays.flatMap { display ->
+ buildList {
+ fun addDisplay(isLandscape: Boolean) {
+ if (isDarkTheme != true) {
+ add(ScreenshotTestSpec(display, isDarkTheme = false, isLandscape))
+ }
+
+ if (isDarkTheme != false) {
+ add(ScreenshotTestSpec(display, isDarkTheme = true, isLandscape))
+ }
+ }
+
+ if (isLandscape != true) {
+ addDisplay(isLandscape = false)
+ }
+
+ if (isLandscape != false) {
+ addDisplay(isLandscape = true)
+ }
+ }
+ }
+ }
+ }
+
+ override fun toString(): String = buildString {
+ // This string is appended to PNGs stored in the device, so let's keep it simple.
+ append(display.name)
+ if (isDarkTheme) append("_dark")
+ if (isLandscape) append("_landscape")
+ }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
new file mode 100644
index 000000000000..35812e39677d
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -0,0 +1,80 @@
+package com.android.systemui.testing.screenshot
+
+import android.app.Activity
+import android.app.Dialog
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import org.junit.Assert.assertEquals
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/** A rule for View screenshot diff tests. */
+class ViewScreenshotTestRule(testSpec: ScreenshotTestSpec) : TestRule {
+ private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
+ private val screenshotRule = ScreenshotTestRule(testSpec)
+
+ private val delegate = RuleChain.outerRule(screenshotRule).around(activityRule)
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return delegate.apply(base, description)
+ }
+
+ /**
+ * Compare the content of the view provided by [viewProvider] with the golden image identified
+ * by [goldenIdentifier] in the context of [testSpec].
+ */
+ fun screenshotTest(
+ goldenIdentifier: String,
+ layoutParams: LayoutParams =
+ LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT),
+ viewProvider: (Activity) -> View,
+ ) {
+ activityRule.scenario.onActivity { activity ->
+ // Make sure that the activity draws full screen and fits the whole display instead of
+ // the system bars.
+ activity.window.setDecorFitsSystemWindows(false)
+ activity.setContentView(viewProvider(activity), layoutParams)
+ }
+
+ // We call onActivity again because it will make sure that our Activity is done measuring,
+ // laying out and drawing its content (that we set in the previous onActivity lambda).
+ activityRule.scenario.onActivity { activity ->
+ // Check that the content is what we expected.
+ val content = activity.requireViewById<ViewGroup>(android.R.id.content)
+ assertEquals(1, content.childCount)
+ screenshotRule.screenshotTest(goldenIdentifier, content.getChildAt(0))
+ }
+ }
+
+ /**
+ * Compare the content of the dialog provided by [dialogProvider] with the golden image
+ * identified by [goldenIdentifier] in the context of [testSpec].
+ */
+ fun dialogScreenshotTest(
+ goldenIdentifier: String,
+ dialogProvider: (Activity) -> Dialog,
+ ) {
+ var dialog: Dialog? = null
+ activityRule.scenario.onActivity { activity ->
+ // Make sure that the dialog draws full screen and fits the whole display instead of the
+ // system bars.
+ dialog =
+ dialogProvider(activity).apply {
+ window.setDecorFitsSystemWindows(false)
+ show()
+ }
+ }
+
+ // We call onActivity again because it will make sure that our Dialog is done measuring,
+ // laying out and drawing its content (that we set in the previous onActivity lambda).
+ activityRule.scenario.onActivity {
+ // Check that the content is what we expected.
+ val dialog = dialog ?: error("dialog is null")
+ screenshotRule.screenshotTest(goldenIdentifier, dialog.window.decorView)
+ }
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 06247c6c9523..835d6e92a63d 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
@@ -74,18 +74,27 @@ open class ClockRegistry(
open var currentClockId: ClockId
get() {
- val json = Settings.Secure.getString(
- context.contentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
- )
- return gson.fromJson(json, ClockSetting::class.java)?.clockId ?: DEFAULT_CLOCK_ID
+ return try {
+ val json = Settings.Secure.getString(
+ context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
+ )
+ gson.fromJson(json, ClockSetting::class.java)?.clockId ?: DEFAULT_CLOCK_ID
+ } catch (ex: Exception) {
+ Log.e(TAG, "Failed to parse clock setting", ex)
+ DEFAULT_CLOCK_ID
+ }
}
set(value) {
- val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
- Settings.Secure.putString(
- context.contentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
- )
+ try {
+ val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
+ Settings.Secure.putString(
+ context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
+ )
+ } catch (ex: Exception) {
+ Log.e(TAG, "Failed to set clock setting", ex)
+ }
}
init {
@@ -183,6 +192,6 @@ open class ClockRegistry(
private data class ClockSetting(
val clockId: ClockId,
- val _applied_timestamp: Long
+ val _applied_timestamp: Long?
)
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 88fe03465405..203b236fcdd6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -80,7 +80,8 @@ public class PipSurfaceTransactionHelper {
public PictureInPictureSurfaceTransaction scaleAndCrop(
SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect sourceRectHint, Rect sourceBounds, Rect destinationBounds, Rect insets) {
+ Rect sourceRectHint, Rect sourceBounds, Rect destinationBounds, Rect insets,
+ float progress) {
mTmpSourceRectF.set(sourceBounds);
mTmpDestinationRect.set(sourceBounds);
mTmpDestinationRect.inset(insets);
@@ -93,9 +94,13 @@ public class PipSurfaceTransactionHelper {
: (float) destinationBounds.height() / sourceBounds.height();
} else {
// scale by sourceRectHint if it's not edge-to-edge
- scale = sourceRectHint.width() <= sourceRectHint.height()
+ final float endScale = sourceRectHint.width() <= sourceRectHint.height()
? (float) destinationBounds.width() / sourceRectHint.width()
: (float) destinationBounds.height() / sourceRectHint.height();
+ final float startScale = sourceRectHint.width() <= sourceRectHint.height()
+ ? (float) destinationBounds.width() / sourceBounds.width()
+ : (float) destinationBounds.height() / sourceBounds.height();
+ scale = (1 - progress) * startScale + progress * endScale;
}
final float left = destinationBounds.left - (insets.left + sourceBounds.left) * scale;
final float top = destinationBounds.top - (insets.top + sourceBounds.top) * scale;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 302ba8444f05..9265f07ad284 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -105,7 +105,7 @@ public class RemoteAnimationAdapterCompat {
}
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
remoteAnimationAdapter.onAnimationCancelled();
}
};
@@ -220,9 +220,9 @@ public class RemoteAnimationAdapterCompat {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
info.getChanges().get(i).getLeash().release();
}
- for (int i = leashMap.size() - 1; i >= 0; --i) {
- leashMap.valueAt(i).release();
- }
+ // Don't release here since launcher might still be using them. Instead
+ // let launcher release them (eg. via RemoteAnimationTargets)
+ leashMap.clear();
try {
finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
} catch (RemoteException e) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 4ce110bf7c47..249133a9a63b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -141,8 +141,10 @@ public class RemoteAnimationTargetCompat {
final int mode = change.getMode();
t.reparent(leash, info.getRootLeash());
- t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
- change.getStartAbsBounds().top - info.getRootOffset().y);
+ final Rect absBounds =
+ (mode == TRANSIT_OPEN) ? change.getEndAbsBounds() : change.getStartAbsBounds();
+ t.setPosition(leash, absBounds.left - info.getRootOffset().x,
+ absBounds.top - info.getRootOffset().y);
// Put all the OPEN/SHOW on top
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index e0b11d83bf75..15b16ff971ea 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -22,7 +22,9 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.Rect;
import android.icu.text.NumberFormat;
+import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -30,16 +32,24 @@ import androidx.annotation.VisibleForTesting;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
+import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
import java.io.PrintWriter;
import java.util.Locale;
import java.util.Objects;
+import java.util.Optional;
import java.util.TimeZone;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
/**
* Controller for an AnimatableClockView on the keyguard. Instantiated by
@@ -54,7 +64,10 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final BatteryController mBatteryController;
private final int mDozingColor = Color.WHITE;
+ private Optional<RegionSamplingHelper> mRegionSamplingHelper = Optional.empty();
+ private Rect mSamplingBounds = new Rect();
private int mLockScreenColor;
+ private final boolean mRegionSamplingEnabled;
private boolean mIsDozing;
private boolean mIsCharging;
@@ -67,13 +80,17 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
private final float mBurmeseLineSpacing;
private final float mDefaultLineSpacing;
+ @Inject
public AnimatableClockController(
AnimatableClockView view,
StatusBarStateController statusBarStateController,
BroadcastDispatcher broadcastDispatcher,
BatteryController batteryController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- @Main Resources resources
+ @Main Resources resources,
+ @Main Executor mainExecutor,
+ @Background Executor bgExecutor,
+ FeatureFlags featureFlags
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -86,6 +103,40 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
R.dimen.keyguard_clock_line_spacing_scale_burmese);
mDefaultLineSpacing = resources.getFloat(
R.dimen.keyguard_clock_line_spacing_scale);
+
+ mRegionSamplingEnabled = featureFlags.isEnabled(Flags.REGION_SAMPLING);
+ if (!mRegionSamplingEnabled) {
+ return;
+ }
+
+ mRegionSamplingHelper = Optional.of(new RegionSamplingHelper(mView,
+ new RegionSamplingHelper.SamplingCallback() {
+ @Override
+ public void onRegionDarknessChanged(boolean isRegionDark) {
+ if (isRegionDark) {
+ mLockScreenColor = Color.WHITE;
+ } else {
+ mLockScreenColor = Color.BLACK;
+ }
+ initColors();
+ }
+
+ @Override
+ public Rect getSampledRegion(View sampledView) {
+ mSamplingBounds = new Rect(sampledView.getLeft(), sampledView.getTop(),
+ sampledView.getRight(), sampledView.getBottom());
+ return mSamplingBounds;
+ }
+
+ @Override
+ public boolean isSamplingEnabled() {
+ return mRegionSamplingEnabled;
+ }
+ }, mainExecutor, bgExecutor)
+ );
+ mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
+ regionSamplingHelper.setWindowVisible(true);
+ });
}
private void reset() {
@@ -169,6 +220,10 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
+ regionSamplingHelper.start(mSamplingBounds);
+ });
+
refreshTime();
initColors();
mView.animateDoze(mIsDozing, false);
@@ -180,6 +235,9 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
mBatteryController.removeCallback(mBatteryCallback);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
+ regionSamplingHelper.stop();
+ });
}
/** Animate the clock appearance */
@@ -223,8 +281,10 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
}
private void initColors() {
- mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
- com.android.systemui.R.attr.wallpaperTextColorAccent);
+ if (!mRegionSamplingEnabled) {
+ mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
+ com.android.systemui.R.attr.wallpaperTextColorAccent);
+ }
mView.setColors(mDozingColor, mLockScreenColor);
mView.animateDoze(mIsDozing, false);
}
@@ -235,5 +295,8 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
public void dump(@NonNull PrintWriter pw) {
pw.println(this);
mView.dump(pw);
+ mRegionSamplingHelper.ifPresent((regionSamplingHelper) -> {
+ regionSamplingHelper.dump(pw);
+ });
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index eeab538932c0..dd78f1cef78c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -263,9 +263,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mCurrentClockSize = clockSize;
+ Clock clock = getClock();
boolean appeared = mView.switchToClock(clockSize, animate);
- if (animate && appeared && clockSize == LARGE) {
- getClock().getAnimations().enter();
+ if (clock != null && animate && appeared && clockSize == LARGE) {
+ clock.getAnimations().enter();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index ec4cf2fd8bd4..24b893340ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -510,6 +510,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
mKeyguardViewManager.isBouncerInTransit() ? BouncerPanelExpansionCalculator
.aboutToShowBouncerProgress(fraction) : fraction;
updateAlpha();
+ updatePauseAuth();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 8de8e020a359..4096ed4283e5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -46,6 +46,7 @@ import android.content.res.AssetManager;
import android.content.res.Resources;
import android.hardware.SensorManager;
import android.hardware.SensorPrivacyManager;
+import android.hardware.camera2.CameraManager;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.ColorDisplayManager;
@@ -561,4 +562,10 @@ public class FrameworkServicesModule {
static SafetyCenterManager provideSafetyCenterManager(Context context) {
return context.getSystemService(SafetyCenterManager.class);
}
+
+ @Provides
+ @Singleton
+ static CameraManager provideCameraManager(Context context) {
+ return context.getSystemService(CameraManager.class);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 56bb53b567be..2fa104a7ce18 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -162,8 +162,13 @@ public class Flags {
new ResourceBooleanFlag(800, R.bool.flag_monet);
/***************************************/
+ // 801 - region sampling
+ public static final BooleanFlag REGION_SAMPLING =
+ new BooleanFlag(801, false);
+
+ /***************************************/
// 900 - media
- public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
+ public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
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 d71956d054be..95b3b3f6452f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -263,7 +263,7 @@ public class KeyguardService extends Service {
// already finished (or not started yet), so do nothing.
return;
}
- runner.onAnimationCancelled();
+ 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.
@@ -396,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/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index de8b0cfa495a..f1d3653050fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -843,7 +843,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
@Override
public void onLaunchAnimationCancelled() {
- setOccluded(true /* occluded */, false /* animate */);
Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: "
+ mOccluded);
}
@@ -911,12 +910,12 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
private final Matrix mUnoccludeMatrix = new Matrix();
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
if (mUnoccludeAnimator != null) {
mUnoccludeAnimator.cancel();
}
- setOccluded(false /* isOccluded */, false /* animate */);
+ setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
+ mOccluded);
}
@@ -3169,9 +3168,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);
}
}
@@ -3212,9 +3211,12 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
}
@Override
- public void onAnimationCancelled() throws RemoteException {
- super.onAnimationCancelled();
- Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: " + mOccluded);
+ public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
+ super.onAnimationCancelled(isKeyguardOccluded);
+ setOccluded(isKeyguardOccluded /* occluded */, false /* animate */);
+
+ Log.d(TAG, "Occlude animation cancelled by WM. "
+ + "Setting occluded state to: " + mOccluded);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index fe1ac80e24df..0f1cdcc3fa5b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -25,17 +25,14 @@ import android.graphics.drawable.Drawable
import android.os.PowerManager
import android.os.SystemClock
import android.util.Log
-import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
-import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.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
import com.android.systemui.R
@@ -65,12 +62,15 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
private val powerManager: PowerManager,
@LayoutRes private val chipLayoutRes: Int
) {
- /** The window layout parameters we'll use when attaching the view to a window. */
+
+ /**
+ * Window layout params that will be used as a starting point for the [windowLayoutParams] of
+ * all subclasses.
+ */
@SuppressLint("WrongConstant") // We're allowed to use TYPE_VOLUME_OVERLAY
- private val windowLayoutParams = WindowManager.LayoutParams().apply {
+ internal val commonWindowLayoutParams = WindowManager.LayoutParams().apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
- gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = WINDOW_TITLE
@@ -78,6 +78,14 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
setTrustedOverlay()
}
+ /**
+ * The window layout parameters we'll use when attaching the view to a window.
+ *
+ * Subclasses must override this to provide their specific layout params, and they should use
+ * [commonWindowLayoutParams] as part of their layout params.
+ */
+ internal abstract val windowLayoutParams: WindowManager.LayoutParams
+
/** The chip view currently being displayed. Null if the chip is not being displayed. */
private var chipView: ViewGroup? = null
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 a5d763c5327b..f9818f0ad8be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -24,6 +24,8 @@ import android.media.MediaRoute2Info
import android.os.Handler
import android.os.PowerManager
import android.util.Log
+import android.view.Gravity
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
@@ -36,6 +38,7 @@ import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCom
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
@@ -69,6 +72,11 @@ class MediaTttChipControllerReceiver @Inject constructor(
powerManager,
R.layout.media_ttt_chip_receiver
) {
+ override val windowLayoutParams = commonWindowLayoutParams.apply {
+ height = getWindowHeight()
+ gravity = Gravity.BOTTOM.or(Gravity.CENTER_HORIZONTAL)
+ }
+
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
@@ -131,6 +139,19 @@ class MediaTttChipControllerReceiver @Inject constructor(
)
}
+ override fun animateChipIn(chipView: ViewGroup) {
+ val appIconView = chipView.requireViewById<View>(R.id.app_icon)
+ appIconView.animate()
+ .translationYBy(-1 * getTranslationAmount().toFloat())
+ .setDuration(30.frames)
+ .start()
+ appIconView.animate()
+ .alpha(1f)
+ .setDuration(5.frames)
+ .start()
+
+ }
+
override fun getIconSize(isAppIcon: Boolean): Int? =
context.resources.getDimensionPixelSize(
if (isAppIcon) {
@@ -139,6 +160,17 @@ class MediaTttChipControllerReceiver @Inject constructor(
R.dimen.media_ttt_generic_icon_size_receiver
}
)
+
+ private fun getWindowHeight(): Int {
+ return context.resources.getDimensionPixelSize(R.dimen.media_ttt_icon_size_receiver) +
+ // Make the window large enough to accommodate the animation amount
+ getTranslationAmount()
+ }
+
+ /** Returns the amount that the chip will be translated by in its intro animation. */
+ private fun getTranslationAmount(): Int {
+ return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
+ }
}
data class ChipReceiverInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 943604cff887..797a7701413b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.media.MediaRoute2Info
import android.os.PowerManager
import android.util.Log
+import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
@@ -69,6 +70,10 @@ class MediaTttChipControllerSender @Inject constructor(
powerManager,
R.layout.media_ttt_chip
) {
+ override val windowLayoutParams = commonWindowLayoutParams.apply {
+ gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
+ }
+
private var currentlyDisplayedChipState: ChipStateSender? = null
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 0288c9fce64a..8f6c3737d6da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -40,11 +40,13 @@ import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.Dumpable
import com.android.systemui.R
@@ -87,6 +89,7 @@ class FgsManagerController @Inject constructor(
private val LOG_TAG = FgsManagerController::class.java.simpleName
private const val DEFAULT_TASK_MANAGER_ENABLED = true
private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
+ private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true
}
var changesSinceDialog = false
@@ -96,6 +99,8 @@ class FgsManagerController @Inject constructor(
private set
var showFooterDot = false
private set
+ var showStopBtnForUserAllowlistedApps = false
+ private set
private val lock = Any()
@@ -163,6 +168,9 @@ class FgsManagerController @Inject constructor(
isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable)
showFooterDot =
it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, showFooterDot)
+ showStopBtnForUserAllowlistedApps = it.getBoolean(
+ TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ showStopBtnForUserAllowlistedApps)
}
isAvailable = deviceConfigProxy.getBoolean(
@@ -173,6 +181,10 @@ class FgsManagerController @Inject constructor(
NAMESPACE_SYSTEMUI,
TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT
)
+ showStopBtnForUserAllowlistedApps = deviceConfigProxy.getBoolean(
+ NAMESPACE_SYSTEMUI,
+ TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS)
dumpManager.registerDumpable(this)
@@ -275,6 +287,20 @@ class FgsManagerController @Inject constructor(
}
}
+ @VisibleForTesting
+ @JvmName("getNumVisibleButtons")
+ internal fun getNumVisibleButtons(): Int {
+ synchronized(lock) {
+ return getNumVisibleButtonsLocked()
+ }
+ }
+
+ private fun getNumVisibleButtonsLocked(): Int {
+ return runningServiceTokens.keys.count {
+ it.uiControl != UIControl.HIDE_BUTTON && currentProfileIds.contains(it.userId)
+ }
+ }
+
fun shouldUpdateFooterVisibility() = dialog == null
fun showDialog(viewLaunchedFrom: View?) {
@@ -505,6 +531,13 @@ class FgsManagerController @Inject constructor(
PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI,
PowerExemptionManager.REASON_ROLE_DIALER,
PowerExemptionManager.REASON_SYSTEM_MODULE -> UIControl.HIDE_BUTTON
+
+ PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE ->
+ if (showStopBtnForUserAllowlistedApps) {
+ UIControl.NORMAL
+ } else {
+ UIControl.HIDE_BUTTON
+ }
else -> UIControl.NORMAL
}
uiControlInitialized = true
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index fbdabc74aba4..a1c66b35bacc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -396,7 +396,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
}
void saveTilesToSettings(List<String> tileSpecs) {
- if (tileSpecs.contains("work")) Log.wtfStack(TAG, "Saving work tile");
mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
null /* tag */, false /* default */, mCurrentUser,
true /* overrideable by restore */);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 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/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
index 5df593b64c24..558bcac681d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
@@ -61,7 +61,7 @@ class WiredChargingRippleController @Inject constructor(
private val systemClock: SystemClock,
private val uiEventLogger: UiEventLogger
) {
- private var pluggedIn: Boolean? = null
+ private var pluggedIn: Boolean = false
private val rippleEnabled: Boolean = featureFlags.isEnabled(Flags.CHARGING_RIPPLE) &&
!SystemProperties.getBoolean("persist.debug.suppress-charging-ripple", false)
private var normalizedPortPosX: Float = context.resources.getFloat(
@@ -99,15 +99,17 @@ class WiredChargingRippleController @Inject constructor(
nowPluggedIn: Boolean,
charging: Boolean
) {
- // Suppresses the ripple when the state change comes from wireless charging.
- if (batteryController.isPluggedInWireless) {
+ // Suppresses the ripple when the state change comes from wireless charging or
+ // its dock.
+ if (batteryController.isPluggedInWireless ||
+ batteryController.isChargingSourceDock) {
return
}
- val wasPluggedIn = pluggedIn
- pluggedIn = nowPluggedIn
- if ((wasPluggedIn == null || !wasPluggedIn) && nowPluggedIn) {
+
+ if (!pluggedIn && nowPluggedIn) {
startRippleWithDebounce()
}
+ pluggedIn = nowPluggedIn
}
}
batteryController.addCallback(batteryStateChangeCallback)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
index 2397005a1a61..52dcf02e3a19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
@@ -17,18 +17,32 @@
package com.android.systemui.statusbar.notification
import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.util.Compile
import javax.inject.Inject
/** Logger for [NotificationEntryManager]. */
class NotificationEntryManagerLogger @Inject constructor(
+ notifPipelineFlags: NotifPipelineFlags,
@NotificationLog private val buffer: LogBuffer
) {
+ private val devLoggingEnabled by lazy { notifPipelineFlags.isDevLoggingEnabled() }
+
+ private inline fun devLog(
+ level: LogLevel,
+ initializer: LogMessage.() -> Unit,
+ noinline printer: LogMessage.() -> String
+ ) {
+ if (Compile.IS_DEBUG && devLoggingEnabled) buffer.log(TAG, level, initializer, printer)
+ }
+
fun logNotifAdded(key: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
}, {
"NOTIF ADDED $str1"
@@ -36,7 +50,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logNotifUpdated(key: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
}, {
"NOTIF UPDATED $str1"
@@ -44,7 +58,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logInflationAborted(key: String, status: String, reason: String) {
- buffer.log(TAG, DEBUG, {
+ devLog(DEBUG, {
str1 = key
str2 = status
str3 = reason
@@ -54,7 +68,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logNotifInflated(key: String, isNew: Boolean) {
- buffer.log(TAG, DEBUG, {
+ devLog(DEBUG, {
str1 = key
bool1 = isNew
}, {
@@ -63,7 +77,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logRemovalIntercepted(key: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
}, {
"NOTIF REMOVE INTERCEPTED for $str1"
@@ -71,7 +85,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logLifetimeExtended(key: String, extenderName: String, status: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
str2 = extenderName
str3 = status
@@ -81,7 +95,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logNotifRemoved(key: String, removedByUser: Boolean) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
bool1 = removedByUser
}, {
@@ -90,7 +104,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logFilterAndSort(reason: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = reason
}, {
"FILTER AND SORT reason=$str1"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
index 63c37e9584d6..ed80f33be701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
@@ -9,4 +9,6 @@ juliacr@google.com
juliatuttle@google.com
lynhan@google.com
steell@google.com
-yurilin@google.com \ No newline at end of file
+yurilin@google.com
+
+per-file MediaNotificationProcessor.java = ethibodeau@google.com, asc@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index aedbd1b56622..0a16fb65b1ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -175,6 +175,10 @@ public final class NotificationEntry extends ListEntry {
public boolean mRemoteEditImeAnimatingAway;
public boolean mRemoteEditImeVisible;
private boolean mExpandAnimationRunning;
+ /**
+ * Flag to determine if the entry is blockable by DnD filters
+ */
+ private boolean mBlockable;
/**
* @param sbn the StatusBarNotification from system server
@@ -253,6 +257,7 @@ public final class NotificationEntry extends ListEntry {
}
mRanking = ranking.withAudiblyAlertedInfo(mRanking);
+ updateIsBlockable();
}
/*
@@ -781,15 +786,20 @@ public final class NotificationEntry extends ListEntry {
* or is not in an allowList).
*/
public boolean isBlockable() {
+ return mBlockable;
+ }
+
+ private void updateIsBlockable() {
if (getChannel() == null) {
- return false;
+ mBlockable = false;
+ return;
}
if (getChannel().isImportanceLockedByCriticalDeviceFunction()
&& !getChannel().isBlockable()) {
- return false;
+ mBlockable = false;
+ return;
}
-
- return true;
+ mBlockable = true;
}
private boolean shouldSuppressVisualEffect(int effect) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
index 3501b44a2c2e..38e3d496a60c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
@@ -19,20 +19,24 @@ package com.android.systemui.statusbar.notification.collection.render
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.util.Compile
import javax.inject.Inject
class NodeSpecBuilderLogger @Inject constructor(
+ notifPipelineFlags: NotifPipelineFlags,
@NotificationLog private val buffer: LogBuffer
) {
+ private val devLoggingEnabled by lazy { notifPipelineFlags.isDevLoggingEnabled() }
+
fun logBuildNodeSpec(
oldSections: Set<NotifSection?>,
newHeaders: Map<NotifSection?, NodeController?>,
newCounts: Map<NotifSection?, Int>,
newSectionOrder: List<NotifSection?>
) {
- if (!Compile.IS_DEBUG)
+ if (!(Compile.IS_DEBUG && devLoggingEnabled))
return
buffer.log(TAG, LogLevel.DEBUG, {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 134f24e7e646..27aa4b38e0ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -266,9 +266,14 @@ public class NotificationConversationInfo extends LinearLayout implements
snooze.setOnClickListener(mOnSnoozeClick);
*/
- if (mAppBubble == BUBBLE_PREFERENCE_ALL) {
- ((TextView) findViewById(R.id.default_summary)).setText(getResources().getString(
+ TextView defaultSummaryTextView = findViewById(R.id.default_summary);
+ if (mAppBubble == BUBBLE_PREFERENCE_ALL
+ && BubblesManager.areBubblesEnabled(mContext, mSbn.getUser())) {
+ defaultSummaryTextView.setText(getResources().getString(
R.string.notification_channel_summary_default_with_bubbles, mAppName));
+ } else {
+ defaultSummaryTextView.setText(getResources().getString(
+ R.string.notification_channel_summary_default));
}
findViewById(R.id.priority).setOnClickListener(mOnFavoriteClick);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS
index 18f0fb38999c..f5828f914eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS
@@ -1,3 +1,16 @@
per-file *Notification* = set noparent
per-file *Notification* = file:../notification/OWNERS
-per-file NotificationIcon* = ccassidy@google.com, evanlaird@google.com, pixel@google.com \ No newline at end of file
+
+per-file NotificationIcon* = ccassidy@google.com, evanlaird@google.com, pixel@google.com
+
+per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com
+per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com
+
+per-file NotificationShadeWindowControllerImpl.java = dupin@google.com, cinek@google.com, beverlyt@google.com, pixel@google.com, juliacr@google.com
+per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com
+per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com
+
+per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com
+
+per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com
+per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/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/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index a89c128dd584..753e94015751 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -119,6 +119,17 @@ public interface BatteryController extends DemoMode, Dumpable,
}
/**
+ * Returns {@code true} if the charging source is
+ * {@link android.os.BatteryManager#BATTERY_PLUGGED_DOCK}.
+ *
+ * <P>Note that charging from dock is not considered as wireless charging. In other words,
+ * {@link BatteryController#isWirelessCharging()} and this are mutually exclusive.
+ */
+ default boolean isChargingSourceDock() {
+ return false;
+ }
+
+ /**
* A listener that will be notified whenever a change in battery level or power save mode has
* occurred.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 917a5e0b9374..33ddf7eed006 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -76,7 +76,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
protected int mLevel;
protected boolean mPluggedIn;
- private boolean mPluggedInWireless;
+ private int mPluggedChargingSource;
protected boolean mCharging;
private boolean mStateUnknown = false;
private boolean mCharged;
@@ -195,10 +195,8 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
mLevel = (int)(100f
* intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
/ intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
- mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
- mPluggedInWireless = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)
- == BatteryManager.BATTERY_PLUGGED_WIRELESS;
-
+ mPluggedChargingSource = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ mPluggedIn = mPluggedChargingSource != 0;
final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
BatteryManager.BATTERY_STATUS_UNKNOWN);
mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
@@ -284,7 +282,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
@Override
public boolean isPluggedInWireless() {
- return mPluggedInWireless;
+ return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
}
@Override
@@ -441,4 +439,9 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
registerReceiver();
updatePowerSave();
}
+
+ @Override
+ public boolean isChargingSourceDock() {
+ return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_DOCK;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index 4cf1d2b3f91d..9946b4b9ecaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -17,15 +17,11 @@
package com.android.systemui.statusbar.policy;
import android.annotation.WorkerThread;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.text.TextUtils;
@@ -33,12 +29,19 @@ import android.util.Log;
import androidx.annotation.NonNull;
+import com.android.internal.annotations.GuardedBy;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
@@ -59,65 +62,88 @@ public class FlashlightControllerImpl implements FlashlightController {
"com.android.settings.flashlight.action.FLASHLIGHT_CHANGED";
private final CameraManager mCameraManager;
- private final Context mContext;
- /** Call {@link #ensureHandler()} before using */
- private Handler mHandler;
+ private final Executor mExecutor;
+ private final SecureSettings mSecureSettings;
+ private final DumpManager mDumpManager;
+ private final BroadcastSender mBroadcastSender;
- /** Lock on mListeners when accessing */
+ private final boolean mHasFlashlight;
+
+ @GuardedBy("mListeners")
private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
- /** Lock on {@code this} when accessing */
+ @GuardedBy("this")
private boolean mFlashlightEnabled;
-
- private String mCameraId;
+ @GuardedBy("this")
private boolean mTorchAvailable;
+ private final AtomicReference<String> mCameraId;
+ private final AtomicBoolean mInitted = new AtomicBoolean(false);
+
@Inject
- public FlashlightControllerImpl(Context context, DumpManager dumpManager) {
- mContext = context;
- mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+ public FlashlightControllerImpl(
+ DumpManager dumpManager,
+ CameraManager cameraManager,
+ @Background Executor bgExecutor,
+ SecureSettings secureSettings,
+ BroadcastSender broadcastSender,
+ PackageManager packageManager
+ ) {
+ mCameraManager = cameraManager;
+ mExecutor = bgExecutor;
+ mCameraId = new AtomicReference<>(null);
+ mSecureSettings = secureSettings;
+ mDumpManager = dumpManager;
+ mBroadcastSender = broadcastSender;
+
+ mHasFlashlight = packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
+ init();
+ }
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
- tryInitCamera();
+ private void init() {
+ if (!mInitted.getAndSet(true)) {
+ mDumpManager.registerDumpable(getClass().getSimpleName(), this);
+ mExecutor.execute(this::tryInitCamera);
+ }
}
+ @WorkerThread
private void tryInitCamera() {
+ if (!mHasFlashlight || mCameraId.get() != null) return;
try {
- mCameraId = getCameraId();
+ mCameraId.set(getCameraId());
} catch (Throwable e) {
Log.e(TAG, "Couldn't initialize.", e);
return;
}
- if (mCameraId != null) {
- ensureHandler();
- mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
+ if (mCameraId.get() != null) {
+ mCameraManager.registerTorchCallback(mExecutor, mTorchCallback);
}
}
public void setFlashlight(boolean enabled) {
- boolean pendingError = false;
- synchronized (this) {
- if (mCameraId == null) return;
- if (mFlashlightEnabled != enabled) {
- mFlashlightEnabled = enabled;
- try {
- mCameraManager.setTorchMode(mCameraId, enabled);
- } catch (CameraAccessException e) {
- Log.e(TAG, "Couldn't set torch mode", e);
- mFlashlightEnabled = false;
- pendingError = true;
+ if (!mHasFlashlight) return;
+ if (mCameraId.get() == null) {
+ mExecutor.execute(this::tryInitCamera);
+ }
+ mExecutor.execute(() -> {
+ if (mCameraId.get() == null) return;
+ synchronized (this) {
+ if (mFlashlightEnabled != enabled) {
+ try {
+ mCameraManager.setTorchMode(mCameraId.get(), enabled);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Couldn't set torch mode", e);
+ dispatchError();
+ }
}
}
- }
- dispatchModeChanged(mFlashlightEnabled);
- if (pendingError) {
- dispatchError();
- }
+ });
}
public boolean hasFlashlight() {
- return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
+ return mHasFlashlight;
}
public synchronized boolean isEnabled() {
@@ -131,13 +157,13 @@ public class FlashlightControllerImpl implements FlashlightController {
@Override
public void addCallback(@NonNull FlashlightListener l) {
synchronized (mListeners) {
- if (mCameraId == null) {
- tryInitCamera();
+ if (mCameraId.get() == null) {
+ mExecutor.execute(this::tryInitCamera);
}
cleanUpListenersLocked(l);
mListeners.add(new WeakReference<>(l));
- l.onFlashlightAvailabilityChanged(mTorchAvailable);
- l.onFlashlightChanged(mFlashlightEnabled);
+ l.onFlashlightAvailabilityChanged(isAvailable());
+ l.onFlashlightChanged(isEnabled());
}
}
@@ -148,14 +174,7 @@ public class FlashlightControllerImpl implements FlashlightController {
}
}
- private synchronized void ensureHandler() {
- if (mHandler == null) {
- HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
- mHandler = new Handler(thread.getLooper());
- }
- }
-
+ @WorkerThread
private String getCameraId() throws CameraAccessException {
String[] ids = mCameraManager.getCameraIdList();
for (String id : ids) {
@@ -221,10 +240,9 @@ public class FlashlightControllerImpl implements FlashlightController {
@Override
@WorkerThread
public void onTorchModeUnavailable(String cameraId) {
- if (TextUtils.equals(cameraId, mCameraId)) {
+ if (TextUtils.equals(cameraId, mCameraId.get())) {
setCameraAvailable(false);
- Settings.Secure.putInt(
- mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 0);
+ mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 0);
}
}
@@ -232,14 +250,12 @@ public class FlashlightControllerImpl implements FlashlightController {
@Override
@WorkerThread
public void onTorchModeChanged(String cameraId, boolean enabled) {
- if (TextUtils.equals(cameraId, mCameraId)) {
+ if (TextUtils.equals(cameraId, mCameraId.get())) {
setCameraAvailable(true);
setTorchMode(enabled);
- Settings.Secure.putInt(
- mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
- Settings.Secure.putInt(
- mContext.getContentResolver(), Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
- mContext.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
+ mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
+ mSecureSettings.putInt(Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
+ mBroadcastSender.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index d5df9fe0c2e8..c48cbb19b40a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -159,7 +159,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
@Test
fun doesNotStartIfAnimationIsCancelled() {
val runner = activityLaunchAnimator.createRunner(controller)
- runner.onAnimationCancelled()
+ runner.onAnimationCancelled(false /* isKeyguardOccluded */)
runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
waitForIdleSync()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 0fdd9054e4bc..b61bda8edd10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -468,6 +469,40 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
verify(mView).setUnpausedAlpha(255);
}
+ @Test
+ public void testUpdatePanelExpansion_pauseAuth() {
+ // GIVEN view is attached + on the keyguard
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureStatusBarExpansionListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+ reset(mView);
+
+ // WHEN panelViewExpansion changes to hide
+ when(mView.getUnpausedAlpha()).thenReturn(0);
+ updateStatusBarExpansion(0f, false);
+
+ // THEN pause auth is updated to PAUSE
+ verify(mView, atLeastOnce()).setPauseAuth(true);
+ }
+
+ @Test
+ public void testUpdatePanelExpansion_unpauseAuth() {
+ // GIVEN view is attached + on the keyguard + panel expansion is 0f
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureStatusBarExpansionListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+ reset(mView);
+
+ // WHEN panelViewExpansion changes to expanded
+ when(mView.getUnpausedAlpha()).thenReturn(255);
+ updateStatusBarExpansion(1f, true);
+
+ // THEN pause auth is updated to NOT pause
+ verify(mView, atLeastOnce()).setPauseAuth(false);
+ }
+
private void sendStatusBarStateChanged(int statusBarState) {
mStatusBarStateListener.onStateChanged(statusBarState);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
index df506b479b88..b5e9e8decb4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
@@ -38,6 +38,7 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -53,6 +54,8 @@ import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import java.util.concurrent.Executor;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -69,6 +72,12 @@ public class AnimatableClockControllerTest extends SysuiTestCase {
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
private Resources mResources;
+ @Mock
+ private Executor mMainExecutor;
+ @Mock
+ private Executor mBgExecutor;
+ @Mock
+ private FeatureFlags mFeatureFlags;
private MockitoSession mStaticMockSession;
private AnimatableClockController mAnimatableClockController;
@@ -96,7 +105,10 @@ public class AnimatableClockControllerTest extends SysuiTestCase {
mBroadcastDispatcher,
mBatteryController,
mKeyguardUpdateMonitor,
- mResources
+ mResources,
+ mMainExecutor,
+ mBgExecutor,
+ mFeatureFlags
);
mAnimatableClockController.init();
captureAttachListener();
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 1527f0d0d71f..2eb478303cb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -371,11 +371,9 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
powerManager,
R.layout.media_ttt_chip
) {
- override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {
-
- }
-
- override fun getIconSize(isAppIcon: Boolean): Int? = ICON_SIZE
+ override val windowLayoutParams = commonWindowLayoutParams
+ override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {}
+ override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE
}
inner class ChipInfo : ChipInfoCommon {
@@ -386,4 +384,4 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
private const val PACKAGE_NAME = "com.android.systemui"
private const val APP_NAME = "Fake App Name"
private const val TIMEOUT_MS = 10000L
-private const val ICON_SIZE = 47 \ No newline at end of file
+private const val ICON_SIZE = 47
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 2927669020c8..3dc10d0ac6ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs;
+import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
@@ -34,6 +36,7 @@ import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -222,7 +225,41 @@ public class FgsManagerControllerTest extends SysuiTestCase {
Assert.assertEquals(2, mFmc.getNumRunningPackages());
}
+ @Test
+ public void testButtonVisibilityOnShowAllowlistButtonFlagChange() throws Exception {
+ setUserProfiles(0);
+ setBackgroundRestrictionExemptionReason("pkg", 12345, REASON_ALLOWLISTED_PACKAGE);
+
+ final Binder binder = new Binder();
+ setShowStopButtonForUserAllowlistedApps(true);
+ mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
+ Assert.assertEquals(1, mFmc.getNumVisibleButtons());
+
+ mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, false);
+ Assert.assertEquals(0, mFmc.getNumVisibleButtons());
+
+ setShowStopButtonForUserAllowlistedApps(false);
+ mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
+ Assert.assertEquals(0, mFmc.getNumVisibleButtons());
+ }
+ private void setShowStopButtonForUserAllowlistedApps(boolean enable) {
+ mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ enable ? "true" : "false", false);
+ mBackgroundExecutor.advanceClockToLast();
+ mBackgroundExecutor.runAllReady();
+ }
+
+ private void setBackgroundRestrictionExemptionReason(String pkgName, int uid, int reason)
+ throws Exception {
+ Mockito.doReturn(uid)
+ .when(mPackageManager)
+ .getPackageUidAsUser(pkgName, UserHandle.getUserId(uid));
+ Mockito.doReturn(reason)
+ .when(mIActivityManager)
+ .getBackgroundRestrictionExemptionReason(uid);
+ }
FgsManagerController createFgsManagerController() throws RemoteException {
ArgumentCaptor<IForegroundServiceObserver> iForegroundServiceObserverArgumentCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
index b4cae38d8b6e..d0cf792b698d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
@@ -74,9 +74,9 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
// Verify ripple added to window manager.
captor.value.onBatteryLevelChanged(
- 0 /* unusedBatteryLevel */,
- true /* plugged in */,
- false /* charging */)
+ /* unusedBatteryLevel= */ 0,
+ /* plugged in= */ true,
+ /* charging= */ false)
val attachListenerCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
verify(rippleView).addOnAttachStateChangeListener(attachListenerCaptor.capture())
@@ -144,4 +144,22 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
// Verify that ripple is triggered.
verify(rippleView).addOnAttachStateChangeListener(ArgumentMatchers.any())
}
+
+ @Test
+ fun testRipple_whenDocked_doesNotPlayRipple() {
+ `when`(batteryController.isChargingSourceDock).thenReturn(true)
+ val captor = ArgumentCaptor
+ .forClass(BatteryController.BatteryStateChangeCallback::class.java)
+ verify(batteryController).addCallback(captor.capture())
+
+ captor.value.onBatteryLevelChanged(
+ /* unusedBatteryLevel= */ 0,
+ /* plugged in= */ true,
+ /* charging= */ false)
+
+ val attachListenerCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
+ verify(rippleView, never()).addOnAttachStateChangeListener(attachListenerCaptor.capture())
+ verify(windowManager, never()).addView(eq(rippleView), any<WindowManager.LayoutParams>())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 769143ddbc0d..d4add7547656 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -108,6 +108,7 @@ public class NotificationEntryTest extends SysuiTestCase {
@Test
public void testBlockableEntryWhenCritical() {
doReturn(true).when(mChannel).isBlockable();
+ mEntry.setRanking(mEntry.getRanking());
assertTrue(mEntry.isBlockable());
}
@@ -117,6 +118,7 @@ public class NotificationEntryTest extends SysuiTestCase {
public void testBlockableEntryWhenCriticalAndChannelNotBlockable() {
doReturn(true).when(mChannel).isBlockable();
doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
+ mEntry.setRanking(mEntry.getRanking());
assertTrue(mEntry.isBlockable());
}
@@ -125,6 +127,7 @@ public class NotificationEntryTest extends SysuiTestCase {
public void testNonBlockableEntryWhenCriticalAndChannelNotBlockable() {
doReturn(false).when(mChannel).isBlockable();
doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
+ mEntry.setRanking(mEntry.getRanking());
assertFalse(mEntry.isBlockable());
}
@@ -164,6 +167,9 @@ public class NotificationEntryTest extends SysuiTestCase {
doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
doReturn(false).when(mChannel).isBlockable();
+ mEntry.setRanking(mEntry.getRanking());
+
+ assertFalse(mEntry.isBlockable());
assertTrue(mEntry.isExemptFromDndVisualSuppression());
assertFalse(mEntry.shouldSuppressAmbient());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index fec2123b304a..fda80a2f9ed8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Intent;
+import android.os.BatteryManager;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerSaveState;
@@ -196,4 +197,26 @@ public class BatteryControllerTest extends SysuiTestCase {
TestableLooper.get(this).processAllMessages();
// Should not throw an exception
}
+
+ @Test
+ public void batteryStateChanged_withChargingSourceDock_isChargingSourceDockTrue() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
+ intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_DOCK);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertTrue(mBatteryController.isChargingSourceDock());
+ }
+
+ @Test
+ public void batteryStateChanged_withChargingSourceNotDock_isChargingSourceDockFalse() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING);
+ intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertFalse(mBatteryController.isChargingSourceDock());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
new file mode 100644
index 000000000000..db0029af4ee2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.pm.PackageManager
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraManager
+import android.hardware.camera2.impl.CameraMetadataNative
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FlashlightControllerImplTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var dumpManager: DumpManager
+
+ @Mock
+ private lateinit var cameraManager: CameraManager
+
+ @Mock
+ private lateinit var broadcastSender: BroadcastSender
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
+ private lateinit var fakeSettings: FakeSettings
+ private lateinit var fakeSystemClock: FakeSystemClock
+ private lateinit var backgroundExecutor: FakeExecutor
+ private lateinit var controller: FlashlightControllerImpl
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ fakeSystemClock = FakeSystemClock()
+ backgroundExecutor = FakeExecutor(fakeSystemClock)
+ fakeSettings = FakeSettings()
+
+ `when`(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH))
+ .thenReturn(true)
+
+ controller = FlashlightControllerImpl(
+ dumpManager,
+ cameraManager,
+ backgroundExecutor,
+ fakeSettings,
+ broadcastSender,
+ packageManager
+ )
+ }
+
+ @Test
+ fun testNoCameraManagerInteractionDirectlyOnConstructor() {
+ verifyZeroInteractions(cameraManager)
+ }
+
+ @Test
+ fun testCameraManagerInitAfterConstructionOnExecutor() {
+ injectCamera()
+ backgroundExecutor.runAllReady()
+
+ verify(cameraManager).registerTorchCallback(eq(backgroundExecutor), any())
+ }
+
+ @Test
+ fun testNoCallbackIfNoFlashCamera() {
+ injectCamera(flash = false)
+ backgroundExecutor.runAllReady()
+
+ verify(cameraManager, never()).registerTorchCallback(any<Executor>(), any())
+ }
+
+ @Test
+ fun testNoCallbackIfNoBackCamera() {
+ injectCamera(facing = CameraCharacteristics.LENS_FACING_FRONT)
+ backgroundExecutor.runAllReady()
+
+ verify(cameraManager, never()).registerTorchCallback(any<Executor>(), any())
+ }
+
+ @Test
+ fun testSetFlashlightInBackgroundExecutor() {
+ val id = injectCamera()
+ backgroundExecutor.runAllReady()
+
+ clearInvocations(cameraManager)
+ val enable = !controller.isEnabled
+ controller.setFlashlight(enable)
+ verifyNoMoreInteractions(cameraManager)
+
+ backgroundExecutor.runAllReady()
+ verify(cameraManager).setTorchMode(id, enable)
+ }
+
+ private fun injectCamera(
+ flash: Boolean = true,
+ facing: Int = CameraCharacteristics.LENS_FACING_BACK
+ ): String {
+ val cameraID = "ID"
+ val camera = CameraCharacteristics(CameraMetadataNative().apply {
+ set(CameraCharacteristics.FLASH_INFO_AVAILABLE, flash)
+ set(CameraCharacteristics.LENS_FACING, facing)
+ })
+ `when`(cameraManager.cameraIdList).thenReturn(arrayOf(cameraID))
+ `when`(cameraManager.getCameraCharacteristics(cameraID)).thenReturn(camera)
+ return cameraID
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index ab9966f218c1..c3545619bcb9 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -62,6 +62,7 @@ import android.companion.DeviceNotAssociatedException;
import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceManager;
import android.companion.IOnAssociationsChangedListener;
+import android.companion.ISystemDataTransferCallback;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
@@ -76,6 +77,7 @@ import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
import android.os.PowerWhitelistManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -110,6 +112,7 @@ import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager;
import com.android.server.pm.UserManagerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.File;
import java.io.FileDescriptor;
@@ -153,6 +156,7 @@ public class CompanionDeviceManagerService extends SystemService {
private CompanionApplicationController mCompanionAppController;
private CompanionSecureCommunicationsManager mSecureCommsManager;
+ private final ActivityTaskManagerInternal mAtmInternal;
private final ActivityManagerInternal mAmInternal;
private final IAppOpsService mAppOpsManager;
private final PowerWhitelistManager mPowerWhitelistManager;
@@ -205,6 +209,7 @@ public class CompanionDeviceManagerService extends SystemService {
mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
mAppOpsManager = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
+ mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mUserManager = context.getSystemService(UserManager.class);
@@ -721,8 +726,22 @@ public class CompanionDeviceManagerService extends SystemService {
}
@Override
- public void startSystemDataTransfer(String packageName, int userId, int associationId) {
+ public void startSystemDataTransfer(String packageName, int userId, int associationId,
+ ISystemDataTransferCallback callback) {
mSystemDataTransferProcessor.startSystemDataTransfer(packageName, userId,
+ associationId, callback);
+ }
+
+ @Override
+ public void attachSystemDataTransport(String packageName, int userId, int associationId,
+ ParcelFileDescriptor fd) {
+ mSystemDataTransferProcessor.attachSystemDataTransport(packageName, userId,
+ associationId, fd);
+ }
+
+ @Override
+ public void detachSystemDataTransport(String packageName, int userId, int associationId) {
+ mSystemDataTransferProcessor.detachSystemDataTransport(packageName, userId,
associationId);
}
@@ -1246,6 +1265,9 @@ public class CompanionDeviceManagerService extends SystemService {
companionAppUids.add(uid);
}
}
+ if (mAtmInternal != null) {
+ mAtmInternal.setCompanionAppUids(userId, companionAppUids);
+ }
if (mAmInternal != null) {
// Make a copy of the set and send it to ActivityManager.
mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
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 70745ba0b368..7eede552ac71 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -29,6 +29,7 @@ import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.companion.DeviceNotAssociatedException;
+import android.companion.ISystemDataTransferCallback;
import android.companion.datatransfer.PermissionSyncRequest;
import android.companion.datatransfer.SystemDataTransferRequest;
import android.content.ComponentName;
@@ -37,16 +38,26 @@ import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.ParcelFileDescriptor;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.permission.PermissionControllerManager;
import android.util.Slog;
+import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.PermissionsUtils;
import com.android.server.companion.proto.CompanionMessage;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -77,6 +88,8 @@ public class SystemDataTransferProcessor {
private final CompanionMessageProcessor mCompanionMessageProcessor;
private final PermissionControllerManager mPermissionControllerManager;
private final ExecutorService mExecutor;
+ @GuardedBy("mTransports")
+ private final SparseArray<Transport> mTransports = new SparseArray<>();
public SystemDataTransferProcessor(CompanionDeviceManagerService service,
AssociationStore associationStore,
@@ -92,10 +105,11 @@ public class SystemDataTransferProcessor {
}
/**
- * Build a PendingIntent of permission sync user consent dialog
+ * Resolve the requested association, throwing if the caller doesn't have
+ * adequate permissions.
*/
- public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
- @UserIdInt int userId, int associationId) {
+ private @NonNull AssociationInfo resolveAssociation(String packageName, int userId,
+ int associationId) {
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
association = PermissionsUtils.sanitizeWithCallerChecks(mContext, association);
if (association == null) {
@@ -103,6 +117,15 @@ public class SystemDataTransferProcessor {
+ associationId + " is not associated with the app " + packageName
+ " for user " + userId);
}
+ return association;
+ }
+
+ /**
+ * Build a PendingIntent of permission sync user consent dialog
+ */
+ public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
+ @UserIdInt int userId, int associationId) {
+ final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
// Check if the request's data type has been requested before.
List<SystemDataTransferRequest> storedRequests =
@@ -145,18 +168,15 @@ public class SystemDataTransferProcessor {
/**
* Start system data transfer. It should first try to establish a secure channel and then sync
* system data.
+ *
+ * TODO: execute callback when the transfer finishes successfully or with errors.
*/
- public void startSystemDataTransfer(String packageName, int userId, int associationId) {
+ public void startSystemDataTransfer(String packageName, int userId, int associationId,
+ ISystemDataTransferCallback callback) {
Slog.i(LOG_TAG, "Start system data transfer for package [" + packageName
+ "] userId [" + userId + "] associationId [" + associationId + "]");
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- association = PermissionsUtils.sanitizeWithCallerChecks(mContext, association);
- if (association == null) {
- throw new DeviceNotAssociatedException("Association "
- + associationId + " is not associated with the app " + packageName
- + " for user " + userId);
- }
+ final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
// Check if the request has been consented by the user.
List<SystemDataTransferRequest> storedRequests =
@@ -188,6 +208,35 @@ public class SystemDataTransferProcessor {
}
}
+ public void attachSystemDataTransport(String packageName, int userId, int associationId,
+ ParcelFileDescriptor fd) {
+ synchronized (mTransports) {
+ // TODO: restore once testing has evolved
+ // resolveAssociation(packageName, userId, associationId);
+
+ if (mTransports.contains(associationId)) {
+ detachSystemDataTransport(packageName, userId, associationId);
+ }
+
+ final Transport transport = new Transport(fd);
+ transport.start();
+ mTransports.put(associationId, transport);
+ }
+ }
+
+ public void detachSystemDataTransport(String packageName, int userId, int associationId) {
+ synchronized (mTransports) {
+ // TODO: restore once testing has evolved
+ // resolveAssociation(packageName, userId, associationId);
+
+ final Transport transport = mTransports.get(associationId);
+ if (transport != null) {
+ mTransports.delete(associationId);
+ transport.stop();
+ }
+ }
+ }
+
/**
* Process a complete decrypted message reported by the companion app.
*/
@@ -242,4 +291,74 @@ public class SystemDataTransferProcessor {
Slog.e(LOG_TAG, "Unknown result code:" + resultCode);
}
};
+
+ private class Transport {
+ private final InputStream mRemoteIn;
+ private final OutputStream mRemoteOut;
+
+ private volatile boolean mStopped;
+
+ public Transport(ParcelFileDescriptor fd) {
+ mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+ mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ }
+
+ public void start() {
+ new Thread(() -> {
+ try {
+ while (!mStopped) {
+ processNextCommand();
+ }
+ } catch (IOException e) {
+ if (!mStopped) {
+ Slog.w(LOG_TAG, "Trouble during transport", e);
+ stop();
+ }
+ }
+ }).start();
+ }
+
+ public void stop() {
+ mStopped = true;
+
+ IoUtils.closeQuietly(mRemoteIn);
+ IoUtils.closeQuietly(mRemoteOut);
+ }
+
+ private void processNextCommand() throws IOException {
+ Slog.d(LOG_TAG, "Waiting for next command...");
+
+ // Read message header
+ final byte[] headerBytes = new byte[8];
+ Streams.readFully(mRemoteIn, headerBytes);
+ final ByteBuffer header = ByteBuffer.wrap(headerBytes);
+ final int command = header.getInt();
+ final int length = header.getInt();
+
+ Slog.d(LOG_TAG, "Received command 0x" + Integer.toHexString(command)
+ + " length " + length);
+ switch (command) {
+ case 0x50490000: // PI(NG) version 0
+ // Repeat back the given payload, within reason
+ final int target = Math.min(length, 1_000_000);
+ final byte[] payload = new byte[target];
+ Streams.readFully(mRemoteIn, payload);
+ Streams.skipByReading(mRemoteIn, length - target);
+
+ // Respond with PO(NG) version 0
+ header.rewind();
+ header.putInt(0x504F0000);
+ header.putInt(target);
+ mRemoteOut.write(header.array());
+ mRemoteOut.write(payload);
+ break;
+
+ default:
+ // Emit local warning, and skip message to
+ // handle next one
+ Slog.w(LOG_TAG, "Unknown command 0x" + Integer.toHexString(command));
+ mRemoteIn.skip(length);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index b63a4daab88f..76c897aac051 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -306,11 +306,13 @@ public class Watchdog implements Dumpable {
}
String describeBlockedStateLocked() {
+ Thread thread = getThread();
+ String threadIdentifier = thread.getName() + ", tid=" + thread.getId();
if (mCurrentMonitor == null) {
- return "Blocked in handler on " + mName + " (" + getThread().getName() + ")";
+ return "Blocked in handler on " + mName + " (" + threadIdentifier + ")";
} else {
return "Blocked in monitor " + mCurrentMonitor.getClass().getName()
- + " on " + mName + " (" + getThread().getName() + ")";
+ + " on " + mName + " (" + threadIdentifier + ")";
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d63fd53a383d..60286bece7d7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8755,7 +8755,7 @@ public class ActivityManagerService extends IActivityManager.Stub
private static String processClass(ProcessRecord process) {
if (process == null || process.getPid() == MY_PID) {
return "system_server";
- } else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ } else if (process.info.isSystemApp() || process.info.isSystemExt()) {
return "system_app";
} else {
return "data_app";
@@ -12484,6 +12484,12 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public PendingIntent getRunningServiceControlPanel(ComponentName name) {
enforceNotIsolatedCaller("getRunningServiceControlPanel");
+ final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ if (name == null || getPackageManagerInternal()
+ .filterAppAccess(name.getPackageName(), callingUid, callingUserId)) {
+ return null;
+ }
synchronized (this) {
return mServices.getRunningServiceControlPanelLocked(name);
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index bddc784385e7..a23870567cdb 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -56,7 +56,6 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
import android.os.ParcelFormatException;
import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
@@ -728,56 +727,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub
return mBatteryUsageStatsProvider.getBatteryUsageStats(queries);
}
- @Override
- @EnforcePermission(BATTERY_STATS)
- public byte[] getStatistics() {
- //Slog.i("foo", "SENDING BATTERY INFO:");
- //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
- Parcel out = Parcel.obtain();
- // Drain the handler queue to make sure we've handled all pending works, so we'll get
- // an accurate stats.
- awaitCompletion();
- syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
- synchronized (mStats) {
- mStats.writeToParcel(out, 0);
- }
- byte[] data = out.marshall();
- out.recycle();
- return data;
- }
-
- /**
- * Returns parceled BatteryStats as a MemoryFile.
- *
- * @param forceUpdate If true, runs a sync to get fresh battery stats. Otherwise,
- * returns the current values.
- */
- @Override
- @EnforcePermission(BATTERY_STATS)
- public ParcelFileDescriptor getStatisticsStream(boolean forceUpdate) {
- //Slog.i("foo", "SENDING BATTERY INFO:");
- //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
- Parcel out = Parcel.obtain();
- if (forceUpdate) {
- // Drain the handler queue to make sure we've handled all pending works, so we'll get
- // an accurate stats.
- awaitCompletion();
- syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
- }
- synchronized (mStats) {
- mStats.writeToParcel(out, 0);
- }
- byte[] data = out.marshall();
- if (DBG) Slog.d(TAG, "getStatisticsStream parcel size is:" + data.length);
- out.recycle();
- try {
- return ParcelFileDescriptor.fromData(data, "battery-stats");
- } catch (IOException e) {
- Slog.w(TAG, "Unable to create shared memory", e);
- return null;
- }
- }
-
/** Register callbacks for statsd pulled atoms. */
private void registerStatsCallbacks() {
final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 453385938aca..4ff1a129f691 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -29,6 +29,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerService.TAG_MU;
import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_PROVIDER;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -48,7 +49,6 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.PathPermission;
import android.content.pm.ProviderInfo;
import android.content.pm.UserInfo;
@@ -958,20 +958,22 @@ public class ContentProviderHelper {
String getProviderMimeType(Uri uri, int userId) {
mService.enforceNotIsolatedCaller("getProviderMimeType");
final String name = uri.getAuthority();
- int callingUid = Binder.getCallingUid();
- int callingPid = Binder.getCallingPid();
- long ident = 0;
- boolean clearedIdentity = false;
- userId = mService.mUserController.unsafeConvertIncomingUser(userId);
- if (canClearIdentity(callingPid, callingUid, userId)) {
- clearedIdentity = true;
- ident = Binder.clearCallingIdentity();
- }
- ContentProviderHolder holder = null;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId);
+ final long ident = canClearIdentity(callingPid, callingUid, safeUserId)
+ ? Binder.clearCallingIdentity() : 0;
+ final ContentProviderHolder holder;
try {
holder = getContentProviderExternalUnchecked(name, null, callingUid,
- "*getmimetype*", userId);
- if (holder != null) {
+ "*getmimetype*", safeUserId);
+ } finally {
+ if (ident != 0) {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ try {
+ if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) {
final IBinder providerConnection = holder.connection;
final ComponentName providerName = holder.info.getComponentName();
// Note: creating a new Runnable instead of using a lambda here since lambdas in
@@ -1000,15 +1002,13 @@ public class ContentProviderHelper {
return null;
} finally {
// We need to clear the identity to call removeContentProviderExternalUnchecked
- if (!clearedIdentity) {
- ident = Binder.clearCallingIdentity();
- }
+ final long token = Binder.clearCallingIdentity();
try {
if (holder != null) {
- removeContentProviderExternalUnchecked(name, null, userId);
+ removeContentProviderExternalUnchecked(name, null /* token */, safeUserId);
}
} finally {
- Binder.restoreCallingIdentity(ident);
+ Binder.restoreCallingIdentity(token);
}
}
@@ -1027,12 +1027,20 @@ public class ContentProviderHelper {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int safeUserId = mService.mUserController.unsafeConvertIncomingUser(userId);
- final long ident = canClearIdentity(callingPid, callingUid, userId)
+ final long ident = canClearIdentity(callingPid, callingUid, safeUserId)
? Binder.clearCallingIdentity() : 0;
+ final ContentProviderHolder holder;
try {
- final ContentProviderHolder holder = getContentProviderExternalUnchecked(name, null,
+ holder = getContentProviderExternalUnchecked(name, null /* token */,
callingUid, "*getmimetype*", safeUserId);
- if (holder != null) {
+ } finally {
+ if (ident != 0) {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ try {
+ if (isHolderVisibleToCaller(holder, callingUid, safeUserId)) {
holder.provider.getTypeAsync(uri, new RemoteCallback(result -> {
final long identity = Binder.clearCallingIdentity();
try {
@@ -1048,8 +1056,6 @@ public class ContentProviderHelper {
} catch (RemoteException e) {
Log.w(TAG, "Content provider dead retrieving " + uri, e);
resultCallback.sendResult(Bundle.EMPTY);
- } finally {
- Binder.restoreCallingIdentity(ident);
}
}
@@ -1065,6 +1071,16 @@ public class ContentProviderHelper {
callingUid, -1, true) == PackageManager.PERMISSION_GRANTED;
}
+ private boolean isHolderVisibleToCaller(@Nullable ContentProviderHolder holder, int callingUid,
+ @UserIdInt int userId) {
+ if (holder == null || holder.info == null) {
+ return false;
+ }
+
+ return !mService.getPackageManagerInternal()
+ .filterAppAccess(holder.info.packageName, callingUid, userId);
+ }
+
/**
* Check if the calling UID has a possible chance at accessing the provider
* at the given authority and user.
@@ -1133,9 +1149,7 @@ public class ContentProviderHelper {
"*checkContentProviderUriPermission*", userId);
if (holder != null) {
- final PackageManagerInternal packageManagerInt = LocalServices.getService(
- PackageManagerInternal.class);
- final AndroidPackage androidPackage = packageManagerInt
+ final AndroidPackage androidPackage = mService.getPackageManagerInternal()
.getPackage(Binder.getCallingUid());
if (androidPackage == null) {
return PackageManager.PERMISSION_DENIED;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 5c7af47682e4..d38cd8ef6181 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -3126,8 +3126,8 @@ public class AppOpsService extends IAppOpsService.Stub {
if (callback == null) {
return;
}
- final boolean mayWatchPackageName =
- packageName != null && !filterAppAccessUnlocked(packageName);
+ final boolean mayWatchPackageName = packageName != null
+ && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
synchronized (this) {
int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
@@ -3331,7 +3331,8 @@ public class AppOpsService extends IAppOpsService.Stub {
// When the caller is the system, it's possible that the packageName is the special
// one (e.g., "root") which isn't actually existed.
if (resolveUid(packageName) == uid
- || (isPackageExisted(packageName) && !filterAppAccessUnlocked(packageName))) {
+ || (isPackageExisted(packageName)
+ && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
return AppOpsManager.MODE_ALLOWED;
}
return AppOpsManager.MODE_ERRORED;
@@ -3350,10 +3351,10 @@ public class AppOpsService extends IAppOpsService.Stub {
*
* NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
*/
- private boolean filterAppAccessUnlocked(String packageName) {
+ private boolean filterAppAccessUnlocked(String packageName, int userId) {
final int callingUid = Binder.getCallingUid();
return LocalServices.getService(PackageManagerInternal.class)
- .filterAppAccess(packageName, callingUid, UserHandle.getUserId(callingUid));
+ .filterAppAccess(packageName, callingUid, userId);
}
@Override
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 5b26672c7de2..dd44af1b68ee 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -280,18 +280,13 @@ public class SpatializerHelper {
}
// for both transaural / binaural, we are not forcing enablement as the init() method
// could have been called another time after boot in case of audioserver restart
- if (mTransauralSupported) {
- // not force-enabling as this device might already be in the device list
- addCompatibleAudioDevice(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
- false /*forceEnable*/);
- }
- if (mBinauralSupported) {
- // not force-enabling as this device might already be in the device list
- addCompatibleAudioDevice(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
- false /*forceEnable*/);
- }
+ addCompatibleAudioDevice(
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+ false /*forceEnable*/);
+ // not force-enabling as this device might already be in the device list
+ addCompatibleAudioDevice(
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
+ false /*forceEnable*/);
} catch (RemoteException e) {
resetCapabilities();
} finally {
@@ -497,10 +492,9 @@ public class SpatializerHelper {
synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
// build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices
ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>();
- for (SADeviceState dev : mSADevices) {
- if (dev.mEnabled) {
- compatList.add(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
- dev.mDeviceType, dev.mDeviceAddress == null ? "" : dev.mDeviceAddress));
+ for (SADeviceState deviceState : mSADevices) {
+ if (deviceState.mEnabled) {
+ compatList.add(deviceState.getAudioDeviceAttributes());
}
}
return compatList;
@@ -521,15 +515,15 @@ public class SpatializerHelper {
*/
private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada,
boolean forceEnable) {
+ if (!isDeviceCompatibleWithSpatializationModes(ada)) {
+ return;
+ }
loglogi("addCompatibleAudioDevice: dev=" + ada);
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
boolean isInList = false;
SADeviceState deviceUpdated = null; // non-null on update.
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
isInList = true;
if (forceEnable) {
deviceState.mEnabled = true;
@@ -539,11 +533,10 @@ public class SpatializerHelper {
}
}
if (!isInList) {
- final SADeviceState dev = new SADeviceState(deviceType,
- wireless ? ada.getAddress() : "");
- dev.mEnabled = true;
- mSADevices.add(dev);
- deviceUpdated = dev;
+ final SADeviceState deviceState = new SADeviceState(ada.getType(), ada.getAddress());
+ deviceState.mEnabled = true;
+ mSADevices.add(deviceState);
+ deviceUpdated = deviceState;
}
if (deviceUpdated != null) {
onRoutingUpdated();
@@ -574,13 +567,10 @@ public class SpatializerHelper {
synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
loglogi("removeCompatibleAudioDevice: dev=" + ada);
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
SADeviceState deviceUpdated = null; // non-null on update.
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
deviceState.mEnabled = false;
deviceUpdated = deviceState;
break;
@@ -602,10 +592,9 @@ public class SpatializerHelper {
// if not a wireless device, this value will be overwritten to map the type
// to TYPE_BUILTIN_SPEAKER or TYPE_WIRED_HEADPHONES
@AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
// if not a wireless device: find if media device is in the speaker, wired headphones
- if (!wireless) {
+ if (!isWireless(deviceType)) {
// is the device type capable of doing SA?
if (!mSACapableDeviceTypes.contains(deviceType)) {
Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada);
@@ -640,9 +629,7 @@ public class SpatializerHelper {
boolean enabled = false;
boolean available = false;
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
available = true;
enabled = deviceState.mEnabled;
break;
@@ -652,11 +639,12 @@ public class SpatializerHelper {
}
private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) {
+ if (!isDeviceCompatibleWithSpatializationModes(ada)) {
+ return;
+ }
boolean knownDevice = false;
for (SADeviceState deviceState : mSADevices) {
- // wireless device so always check address
- if (ada.getType() == deviceState.mDeviceType
- && ada.getAddress().equals(deviceState.mDeviceAddress)) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
knownDevice = true;
break;
}
@@ -704,13 +692,8 @@ public class SpatializerHelper {
if (ada.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
return false;
}
-
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
return true;
}
}
@@ -719,12 +702,19 @@ public class SpatializerHelper {
private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes,
@NonNull AudioFormat format, @NonNull AudioDeviceAttributes[] devices) {
- final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(devices[0].getType(),
+ if (isDeviceCompatibleWithSpatializationModes(devices[0])) {
+ return AudioSystem.canBeSpatialized(attributes, format, devices);
+ }
+ return false;
+ }
+
+ private boolean isDeviceCompatibleWithSpatializationModes(@NonNull AudioDeviceAttributes ada) {
+ final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
/*default when type not found*/ SpatializationMode.SPATIALIZER_BINAURAL);
if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
|| (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL
&& mTransauralSupported)) {
- return AudioSystem.canBeSpatialized(attributes, format, devices);
+ return true;
}
return false;
}
@@ -1089,13 +1079,8 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled
+ " for " + ada);
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
if (!deviceState.mHasHeadTracker) {
Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled
+ " device:" + ada + " on a device without headtracker");
@@ -1109,7 +1094,7 @@ public class SpatializerHelper {
}
}
// check current routing to see if it affects the headtracking mode
- if (ROUTING_DEVICES[0].getType() == deviceType
+ if (ROUTING_DEVICES[0].getType() == ada.getType()
&& ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
: Spatializer.HEAD_TRACKING_MODE_DISABLED);
@@ -1121,13 +1106,8 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada);
return false;
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
return deviceState.mHasHeadTracker;
}
}
@@ -1144,13 +1124,8 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada);
return false;
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
if (!deviceState.mHasHeadTracker) {
deviceState.mHasHeadTracker = true;
mAudioService.persistSpatialAudioDeviceSettings();
@@ -1168,13 +1143,8 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada);
return false;
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
if (!deviceState.mHasHeadTracker) {
return false;
}
@@ -1531,7 +1501,7 @@ public class SpatializerHelper {
SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @NonNull String address) {
mDeviceType = deviceType;
- mDeviceAddress = Objects.requireNonNull(address);
+ mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
}
@Override
@@ -1599,6 +1569,18 @@ public class SpatializerHelper {
return null;
}
}
+
+ public AudioDeviceAttributes getAudioDeviceAttributes() {
+ return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress);
+ }
+
+ public boolean matchesAudioDeviceAttributes(AudioDeviceAttributes ada) {
+ final int deviceType = ada.getType();
+ final boolean wireless = isWireless(deviceType);
+ return (deviceType == mDeviceType)
+ && (!wireless || ada.getAddress().equals(mDeviceAddress));
+ }
}
/*package*/ synchronized String getSADeviceSettings() {
@@ -1619,7 +1601,9 @@ public class SpatializerHelper {
// small list, not worth overhead of Arrays.stream(devSettings)
for (String setting : devSettings) {
SADeviceState devState = SADeviceState.fromPersistedString(setting);
- if (devState != null) {
+ if (devState != null
+ && isDeviceCompatibleWithSpatializationModes(
+ devState.getAudioDeviceAttributes())) {
mSADevices.add(devState);
logDeviceState(devState, "setSADeviceSettings");
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index cc49f07dd0e5..41ca13f5d5f5 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -538,13 +538,12 @@ public final class AuthSession implements IBinder.DeathRecipient {
void onDialogAnimatedIn() {
if (mState != STATE_AUTH_STARTED) {
- Slog.w(TAG, "onDialogAnimatedIn, unexpected state: " + mState);
+ Slog.e(TAG, "onDialogAnimatedIn, unexpected state: " + mState);
+ return;
}
mState = STATE_AUTH_STARTED_UI_SHOWING;
-
startAllPreparedFingerprintSensors();
- mState = STATE_AUTH_STARTED_UI_SHOWING;
}
void onTryAgainPressed() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index 968146a166ed..ef2931ff5850 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -20,14 +20,18 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.biometrics.BiometricConstants;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.function.BooleanSupplier;
/**
* Contains all the necessary information for a HAL operation.
@@ -84,6 +88,8 @@ public class BiometricSchedulerOperation {
private final BaseClientMonitor mClientMonitor;
@Nullable
private final ClientMonitorCallback mClientCallback;
+ @NonNull
+ private final BooleanSupplier mIsDebuggable;
@Nullable
private ClientMonitorCallback mOnStartCallback;
@OperationState
@@ -99,14 +105,33 @@ public class BiometricSchedulerOperation {
this(clientMonitor, callback, STATE_WAITING_IN_QUEUE);
}
+ @VisibleForTesting
+ BiometricSchedulerOperation(
+ @NonNull BaseClientMonitor clientMonitor,
+ @Nullable ClientMonitorCallback callback,
+ @NonNull BooleanSupplier isDebuggable
+ ) {
+ this(clientMonitor, callback, STATE_WAITING_IN_QUEUE, isDebuggable);
+ }
+
protected BiometricSchedulerOperation(
@NonNull BaseClientMonitor clientMonitor,
@Nullable ClientMonitorCallback callback,
@OperationState int state
) {
+ this(clientMonitor, callback, state, Build::isDebuggable);
+ }
+
+ private BiometricSchedulerOperation(
+ @NonNull BaseClientMonitor clientMonitor,
+ @Nullable ClientMonitorCallback callback,
+ @OperationState int state,
+ @NonNull BooleanSupplier isDebuggable
+ ) {
mClientMonitor = clientMonitor;
mClientCallback = callback;
mState = state;
+ mIsDebuggable = isDebuggable;
mCancelWatchdog = () -> {
if (!isFinished()) {
Slog.e(TAG, "[Watchdog Triggered]: " + this);
@@ -144,13 +169,19 @@ public class BiometricSchedulerOperation {
* @return if this operation started
*/
public boolean start(@NonNull ClientMonitorCallback callback) {
- checkInState("start",
+ if (errorWhenNoneOf("start",
STATE_WAITING_IN_QUEUE,
STATE_WAITING_FOR_COOKIE,
- STATE_WAITING_IN_QUEUE_CANCELING);
+ STATE_WAITING_IN_QUEUE_CANCELING)) {
+ return false;
+ }
if (mClientMonitor.getCookie() != 0) {
- throw new IllegalStateException("operation requires cookie");
+ String err = "operation requires cookie";
+ if (mIsDebuggable.getAsBoolean()) {
+ throw new IllegalStateException(err);
+ }
+ Slog.e(TAG, err);
}
return doStart(callback);
@@ -164,16 +195,18 @@ public class BiometricSchedulerOperation {
* @return if this operation started
*/
public boolean startWithCookie(@NonNull ClientMonitorCallback callback, int cookie) {
- checkInState("start",
- STATE_WAITING_IN_QUEUE,
- STATE_WAITING_FOR_COOKIE,
- STATE_WAITING_IN_QUEUE_CANCELING);
-
if (mClientMonitor.getCookie() != cookie) {
Slog.e(TAG, "Mismatched cookie for operation: " + this + ", received: " + cookie);
return false;
}
+ if (errorWhenNoneOf("start",
+ STATE_WAITING_IN_QUEUE,
+ STATE_WAITING_FOR_COOKIE,
+ STATE_WAITING_IN_QUEUE_CANCELING)) {
+ return false;
+ }
+
return doStart(callback);
}
@@ -217,10 +250,12 @@ public class BiometricSchedulerOperation {
* immediately abort the operation and notify the client that it has finished unsuccessfully.
*/
public void abort() {
- checkInState("cannot abort a non-pending operation",
+ if (errorWhenNoneOf("abort",
STATE_WAITING_IN_QUEUE,
STATE_WAITING_FOR_COOKIE,
- STATE_WAITING_IN_QUEUE_CANCELING);
+ STATE_WAITING_IN_QUEUE_CANCELING)) {
+ return;
+ }
if (isHalOperation()) {
((HalClientMonitor<?>) mClientMonitor).unableToStart();
@@ -247,7 +282,9 @@ public class BiometricSchedulerOperation {
* the callback used from {@link #start(ClientMonitorCallback)} is used)
*/
public void cancel(@NonNull Handler handler, @NonNull ClientMonitorCallback callback) {
- checkNotInState("cancel", STATE_FINISHED);
+ if (errorWhenOneOf("cancel", STATE_FINISHED)) {
+ return;
+ }
final int currentState = mState;
if (!isInterruptable()) {
@@ -402,21 +439,28 @@ public class BiometricSchedulerOperation {
return mClientMonitor;
}
- private void checkNotInState(String message, @OperationState int... states) {
- for (int state : states) {
- if (mState == state) {
- throw new IllegalStateException(message + ": illegal state= " + state);
+ private boolean errorWhenOneOf(String op, @OperationState int... states) {
+ final boolean isError = ArrayUtils.contains(states, mState);
+ if (isError) {
+ String err = op + ": mState must not be " + mState;
+ if (mIsDebuggable.getAsBoolean()) {
+ throw new IllegalStateException(err);
}
+ Slog.e(TAG, err);
}
+ return isError;
}
- private void checkInState(String message, @OperationState int... states) {
- for (int state : states) {
- if (mState == state) {
- return;
+ private boolean errorWhenNoneOf(String op, @OperationState int... states) {
+ final boolean isError = !ArrayUtils.contains(states, mState);
+ if (isError) {
+ String err = op + ": mState=" + mState + " must be one of " + Arrays.toString(states);
+ if (mIsDebuggable.getAsBoolean()) {
+ throw new IllegalStateException(err);
}
+ Slog.e(TAG, err);
}
- throw new IllegalStateException(message + ": illegal state= " + mState);
+ return isError;
}
@Override
diff --git a/services/core/java/com/android/server/broadcastradio/OWNERS b/services/core/java/com/android/server/broadcastradio/OWNERS
index 3e360e7e992c..d2bdd643b0a2 100644
--- a/services/core/java/com/android/server/broadcastradio/OWNERS
+++ b/services/core/java/com/android/server/broadcastradio/OWNERS
@@ -1,3 +1,3 @@
-keunyoung@google.com
+xuweilin@google.com
oscarazu@google.com
-twasilczyk@google.com
+keunyoung@google.com
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 845e932cdff9..5b282ced73b5 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3301,8 +3301,13 @@ public class Vpn {
cancelHandleNetworkLostTimeout();
synchronized (Vpn.this) {
+ String category = null;
+ int errorClass = -1;
+ int errorCode = -1;
if (exception instanceof IkeProtocolException) {
final IkeProtocolException ikeException = (IkeProtocolException) exception;
+ category = VpnManager.CATEGORY_EVENT_IKE_ERROR;
+ errorCode = ikeException.getErrorType();
switch (ikeException.getErrorType()) {
case IkeProtocolException.ERROR_TYPE_NO_PROPOSAL_CHOSEN: // Fallthrough
@@ -3312,105 +3317,53 @@ public class Vpn {
case IkeProtocolException.ERROR_TYPE_FAILED_CP_REQUIRED: // Fallthrough
case IkeProtocolException.ERROR_TYPE_TS_UNACCEPTABLE:
// All the above failures are configuration errors, and are terminal
- // TODO(b/230548427): Remove SDK check once VPN related stuff are
- // decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
- sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_IKE_ERROR,
- VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
- ikeException.getErrorType(),
- getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- network,
- getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
- mUnderlyingNetworkCapabilities),
- getRedactedLinkPropertiesOfUnderlyingNetwork(
- mUnderlyingLinkProperties));
- }
- markFailedAndDisconnect(exception);
- return;
+ errorClass = VpnManager.ERROR_CLASS_NOT_RECOVERABLE;
+ break;
// All other cases possibly recoverable.
default:
// All the above failures are configuration errors, and are terminal
- // TODO(b/230548427): Remove SDK check once VPN related stuff are
- // decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
- sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_IKE_ERROR,
- VpnManager.ERROR_CLASS_RECOVERABLE,
- ikeException.getErrorType(),
- getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- network,
- getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
- mUnderlyingNetworkCapabilities),
- getRedactedLinkPropertiesOfUnderlyingNetwork(
- mUnderlyingLinkProperties));
- }
+ errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
}
} else if (exception instanceof IllegalArgumentException) {
// Failed to build IKE/ChildSessionParams; fatal profile configuration error
markFailedAndDisconnect(exception);
return;
} else if (exception instanceof IkeNetworkLostException) {
- // TODO(b/230548427): Remove SDK check once VPN related stuff are
- // decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
- sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
- VpnManager.ERROR_CLASS_RECOVERABLE,
- VpnManager.ERROR_CODE_NETWORK_LOST,
- getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- network,
- getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
- mUnderlyingNetworkCapabilities),
- getRedactedLinkPropertiesOfUnderlyingNetwork(
- mUnderlyingLinkProperties));
- }
+ category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
+ errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
+ errorCode = VpnManager.ERROR_CODE_NETWORK_LOST;
} else if (exception instanceof IkeNonProtocolException) {
+ category = VpnManager.CATEGORY_EVENT_NETWORK_ERROR;
+ errorClass = VpnManager.ERROR_CLASS_RECOVERABLE;
if (exception.getCause() instanceof UnknownHostException) {
- // TODO(b/230548427): Remove SDK check once VPN related stuff are
- // decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
- sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
- VpnManager.ERROR_CLASS_RECOVERABLE,
- VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST,
- getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- network,
- getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
- mUnderlyingNetworkCapabilities),
- getRedactedLinkPropertiesOfUnderlyingNetwork(
- mUnderlyingLinkProperties));
- }
+ errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
} else if (exception.getCause() instanceof IkeTimeoutException) {
- // TODO(b/230548427): Remove SDK check once VPN related stuff are
- // decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
- sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
- VpnManager.ERROR_CLASS_RECOVERABLE,
- VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT,
- getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- network,
- getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
- mUnderlyingNetworkCapabilities),
- getRedactedLinkPropertiesOfUnderlyingNetwork(
- mUnderlyingLinkProperties));
- }
+ errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
} else if (exception.getCause() instanceof IOException) {
- // TODO(b/230548427): Remove SDK check once VPN related stuff are
- // decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
- sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_NETWORK_ERROR,
- VpnManager.ERROR_CLASS_RECOVERABLE,
- VpnManager.ERROR_CODE_NETWORK_IO,
- getPackage(), mSessionKey, makeVpnProfileStateLocked(),
- network,
- getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
- mUnderlyingNetworkCapabilities),
- getRedactedLinkPropertiesOfUnderlyingNetwork(
- mUnderlyingLinkProperties));
- }
+ errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
}
} else if (exception != null) {
Log.wtf(TAG, "onSessionLost: exception = " + exception);
}
- scheduleRetryNewIkeSession();
+ // TODO(b/230548427): Remove SDK check once VPN related stuff are
+ // decoupled from ConnectivityServiceTest.
+ if (SdkLevel.isAtLeastT() && category != null) {
+ sendEventToVpnManagerApp(category, errorClass, errorCode,
+ getPackage(), mSessionKey, makeVpnProfileStateLocked(),
+ mActiveNetwork,
+ getRedactedNetworkCapabilitiesOfUnderlyingNetwork(
+ mUnderlyingNetworkCapabilities),
+ getRedactedLinkPropertiesOfUnderlyingNetwork(
+ mUnderlyingLinkProperties));
+ }
+
+ if (errorClass == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
+ markFailedAndDisconnect(exception);
+ return;
+ } else {
+ scheduleRetryNewIkeSession();
+ }
}
mUnderlyingNetworkCapabilities = null;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 16bffd9a597b..1de1a7a5e1e5 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -1262,6 +1262,7 @@ abstract class HdmiCecLocalDevice {
boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
removeAction(AbsoluteVolumeAudioStatusAction.class);
removeAction(SetAudioVolumeLevelDiscoveryAction.class);
+ removeAction(ActiveSourceAction.class);
mPendingActionClearedCallback =
new PendingActionClearedCallback() {
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 3dcf72eb38d6..30a4f3134ffd 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -91,7 +91,8 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
boolean is20TargetOnBefore = mIsCec20 && getTargetDevicePowerStatus(mSource, mTargetAddress,
HdmiControlManager.POWER_STATUS_UNKNOWN) == HdmiControlManager.POWER_STATUS_ON;
- broadcastActiveSource();
+ // Make the device the active source.
+ setAndBroadcastActiveSource();
// If the device is not an audio system itself, request the connected audio system to
// turn on.
if (shouldTurnOnConnectedAudioSystem()) {
@@ -108,9 +109,11 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
queryDevicePowerStatus();
} else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
if (!is20TargetOnBefore) {
+ // If the device is still the active source, send the <Active Source> message
+ // again.
// Suppress 2nd <Active Source> message if the target device was already on when
// the 1st one was sent.
- broadcastActiveSource();
+ maySendActiveSource();
}
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
return true;
@@ -121,7 +124,9 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
return true;
}
- private void broadcastActiveSource() {
+ private void setAndBroadcastActiveSource() {
+ // If the device wasn´t the active source yet,
+ // this makes it the active source and wakes it up.
mSource.mService.setAndBroadcastActiveSourceFromOneDeviceType(
mTargetAddress, getSourcePath(), "OneTouchPlayAction#broadcastActiveSource()");
// When OneTouchPlay is called, client side should be responsible to send out the intent
@@ -135,6 +140,11 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
mSource.setLocalActivePort(Constants.CEC_SWITCH_HOME);
}
+ private void maySendActiveSource() {
+ // Only send <Active Source> if the device is already the active source at this time.
+ mSource.maySendActiveSource(mTargetAddress);
+ }
+
private void queryDevicePowerStatus() {
sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
mTargetAddress));
@@ -149,7 +159,9 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) {
int status = cmd.getParams()[0];
if (status == HdmiControlManager.POWER_STATUS_ON) {
- broadcastActiveSource();
+ // If the device is still the active source, send the <Active Source> message
+ // again.
+ maySendActiveSource();
finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
}
return true;
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 fa6a608d9d31..b45dc7ff0f6b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -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");
@@ -2108,9 +2108,24 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@Override
- public boolean isStylusHandwritingAvailable() {
+ public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ }
+
synchronized (ImfLock.class) {
- return mBindingController.supportsStylusHandwriting();
+ if (userId == mSettings.getCurrentUserId()) {
+ return mBindingController.supportsStylusHandwriting();
+ }
+
+ //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
+ //TODO(b/210039666): use cache.
+ final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
+ final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
+ mContext.getContentResolver(), methodMap, userId, true);
+ final InputMethodInfo imi = methodMap.get(settings.getSelectedInputMethod());
+ return imi != null && imi.supportsStylusHandwriting();
}
}
@@ -2476,7 +2491,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// We don't start input when session for a11y is created. We start input when
// input method start input, a11y manager service is always on.
if (startInputReason != StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY) {
- final Binder startInputToken = new Binder();
setEnabledSessionForAccessibilityLocked(mCurClient.mAccessibilitySessions);
AccessibilityManagerInternal.get().startInput(mCurRemoteAccessibilityInputConnection,
mCurEditorInfo, !initial /* restarting */);
@@ -4049,7 +4063,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);
@@ -4093,7 +4107,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);
}
}
@@ -4112,13 +4126,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;
}
@@ -4188,8 +4202,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;
}
@@ -4868,7 +4882,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) {
@@ -5011,7 +5025,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) {
@@ -5067,7 +5081,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) {
@@ -5193,8 +5207,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);
}
@@ -5229,7 +5243,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
@@ -5242,17 +5256,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;
@@ -6169,8 +6182,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 */);
@@ -6191,8 +6204,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/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 1235352b0590..549fd4918023 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -2523,10 +2523,16 @@ public class LocationProviderManager extends
filtered = locationResult;
}
- Location last = getLastLocationUnsafe(USER_CURRENT, PERMISSION_FINE, true, Long.MAX_VALUE);
- if (last != null && locationResult.get(0).getElapsedRealtimeNanos()
- < last.getElapsedRealtimeNanos()) {
- Log.e(TAG, "non-monotonic location received from " + mName + " provider");
+ // check for non-monotonic locations if we're not the passive manager. the passive manager
+ // is much more likely to see non-monotonic locations since it gets locations from all
+ // providers, so this error log is not very useful there.
+ if (mPassiveManager != null) {
+ Location last = getLastLocationUnsafe(USER_CURRENT, PERMISSION_FINE, true,
+ Long.MAX_VALUE);
+ if (last != null && locationResult.get(0).getElapsedRealtimeNanos()
+ < last.getElapsedRealtimeNanos()) {
+ Log.e(TAG, "non-monotonic location received from " + mName + " provider");
+ }
}
// update last location
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 24bd42e7d775..3edbfe038ce6 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -122,9 +122,9 @@ public class SyntheticPasswordManager {
// 256-bit synthetic password
private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
- private static final int PASSWORD_SCRYPT_N = 11;
- private static final int PASSWORD_SCRYPT_R = 3;
- private static final int PASSWORD_SCRYPT_P = 1;
+ private static final int PASSWORD_SCRYPT_LOG_N = 11;
+ private static final int PASSWORD_SCRYPT_LOG_R = 3;
+ private static final int PASSWORD_SCRYPT_LOG_P = 1;
private static final int PASSWORD_SALT_LENGTH = 16;
private static final int PASSWORD_TOKEN_LENGTH = 32;
private static final String TAG = "SyntheticPasswordManager";
@@ -192,7 +192,11 @@ public class SyntheticPasswordManager {
mVersion = version;
}
- private byte[] derivePassword(byte[] personalization) {
+ /**
+ * Derives a subkey from the synthetic password. For v3 and later synthetic passwords the
+ * subkeys are 256-bit; for v1 and v2 they are 512-bit.
+ */
+ private byte[] deriveSubkey(byte[] personalization) {
if (mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
return (new SP800Derive(mSyntheticPassword))
.withContext(personalization, PERSONALISATION_CONTEXT);
@@ -203,28 +207,28 @@ public class SyntheticPasswordManager {
}
public byte[] deriveKeyStorePassword() {
- return bytesToHex(derivePassword(PERSONALIZATION_KEY_STORE_PASSWORD));
+ return bytesToHex(deriveSubkey(PERSONALIZATION_KEY_STORE_PASSWORD));
}
public byte[] deriveGkPassword() {
- return derivePassword(PERSONALIZATION_SP_GK_AUTH);
+ return deriveSubkey(PERSONALIZATION_SP_GK_AUTH);
}
public byte[] deriveDiskEncryptionKey() {
- return derivePassword(PERSONALIZATION_FBE_KEY);
+ return deriveSubkey(PERSONALIZATION_FBE_KEY);
}
public byte[] deriveVendorAuthSecret() {
- return derivePassword(PERSONALIZATION_AUTHSECRET_KEY);
+ return deriveSubkey(PERSONALIZATION_AUTHSECRET_KEY);
}
public byte[] derivePasswordHashFactor() {
- return derivePassword(PERSONALIZATION_PASSWORD_HASH);
+ return deriveSubkey(PERSONALIZATION_PASSWORD_HASH);
}
/** Derives key used to encrypt password metrics */
public byte[] deriveMetricsKey() {
- return derivePassword(PERSONALIZATION_PASSWORD_METRICS);
+ return deriveSubkey(PERSONALIZATION_PASSWORD_METRICS);
}
/**
@@ -274,9 +278,8 @@ public class SyntheticPasswordManager {
* AuthenticationToken.mSyntheticPassword for details on what each block means.
*/
private void recreate(byte[] escrowSplit0, byte[] escrowSplit1) {
- mSyntheticPassword = String.valueOf(HexEncoding.encode(
- SyntheticPasswordCrypto.personalisedHash(
- PERSONALIZATION_SP_SPLIT, escrowSplit0, escrowSplit1))).getBytes();
+ mSyntheticPassword = bytesToHex(SyntheticPasswordCrypto.personalisedHash(
+ PERSONALIZATION_SP_SPLIT, escrowSplit0, escrowSplit1));
}
/**
@@ -310,9 +313,9 @@ public class SyntheticPasswordManager {
}
static class PasswordData {
- byte scryptN;
- byte scryptR;
- byte scryptP;
+ byte scryptLogN;
+ byte scryptLogR;
+ byte scryptLogP;
public int credentialType;
byte[] salt;
// For GateKeeper-based credential, this is the password handle returned by GK,
@@ -321,9 +324,9 @@ public class SyntheticPasswordManager {
public static PasswordData create(int passwordType) {
PasswordData result = new PasswordData();
- result.scryptN = PASSWORD_SCRYPT_N;
- result.scryptR = PASSWORD_SCRYPT_R;
- result.scryptP = PASSWORD_SCRYPT_P;
+ result.scryptLogN = PASSWORD_SCRYPT_LOG_N;
+ result.scryptLogR = PASSWORD_SCRYPT_LOG_R;
+ result.scryptLogP = PASSWORD_SCRYPT_LOG_P;
result.credentialType = passwordType;
result.salt = secureRandom(PASSWORD_SALT_LENGTH);
return result;
@@ -335,9 +338,9 @@ public class SyntheticPasswordManager {
buffer.put(data, 0, data.length);
buffer.flip();
result.credentialType = buffer.getInt();
- result.scryptN = buffer.get();
- result.scryptR = buffer.get();
- result.scryptP = buffer.get();
+ result.scryptLogN = buffer.get();
+ result.scryptLogR = buffer.get();
+ result.scryptLogP = buffer.get();
int saltLen = buffer.getInt();
result.salt = new byte[saltLen];
buffer.get(result.salt);
@@ -357,9 +360,9 @@ public class SyntheticPasswordManager {
+ Integer.BYTES + salt.length + Integer.BYTES +
(passwordHandle != null ? passwordHandle.length : 0));
buffer.putInt(credentialType);
- buffer.put(scryptN);
- buffer.put(scryptR);
- buffer.put(scryptP);
+ buffer.put(scryptLogN);
+ buffer.put(scryptLogR);
+ buffer.put(scryptLogP);
buffer.putInt(salt.length);
buffer.put(salt);
if (passwordHandle != null && passwordHandle.length > 0) {
@@ -930,26 +933,6 @@ public class SyntheticPasswordManager {
private ArrayMap<Integer, ArrayMap<Long, TokenData>> tokenMap = new ArrayMap<>();
/**
- * Create a token based Synthetic password for the given user.
- * @return the handle of the token
- */
- public long createStrongTokenBasedSyntheticPassword(byte[] token, int userId,
- @Nullable EscrowTokenStateChangeCallback changeCallback) {
- return createTokenBasedSyntheticPassword(token, TOKEN_TYPE_STRONG, userId,
- changeCallback);
- }
-
- /**
- * Create a weak token based Synthetic password for the given user.
- * @return the handle of the weak token
- */
- public long createWeakTokenBasedSyntheticPassword(byte[] token, int userId,
- @Nullable EscrowTokenStateChangeCallback changeCallback) {
- return createTokenBasedSyntheticPassword(token, TOKEN_TYPE_WEAK, userId,
- changeCallback);
- }
-
- /**
* Create a token based Synthetic password of the given type for the given user.
* @return the handle of the token
*/
@@ -1499,8 +1482,8 @@ public class SyntheticPasswordManager {
private byte[] computePasswordToken(LockscreenCredential credential, PasswordData data) {
final byte[] password = credential.isNone() ? DEFAULT_PASSWORD : credential.getCredential();
- return scrypt(password, data.salt, 1 << data.scryptN, 1 << data.scryptR, 1 << data.scryptP,
- PASSWORD_TOKEN_LENGTH);
+ return scrypt(password, data.salt, 1 << data.scryptLogN, 1 << data.scryptLogR,
+ 1 << data.scryptLogP, PASSWORD_TOKEN_LENGTH);
}
private byte[] passwordTokenToGkInput(byte[] token) {
@@ -1541,18 +1524,9 @@ public class SyntheticPasswordManager {
return result;
}
- protected static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes();
- private static byte[] bytesToHex(byte[] bytes) {
- if (bytes == null) {
- return "null".getBytes();
- }
- byte[] hexBytes = new byte[bytes.length * 2];
- for ( int j = 0; j < bytes.length; j++ ) {
- int v = bytes[j] & 0xFF;
- hexBytes[j * 2] = HEX_ARRAY[v >>> 4];
- hexBytes[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
- }
- return hexBytes;
+ @VisibleForTesting
+ static byte[] bytesToHex(byte[] bytes) {
+ return HexEncoding.encodeToString(bytes).getBytes();
}
/**
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 098e8f74749c..7d12ede754ef 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -46,7 +46,6 @@ import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.ContentRecordingSession;
-import android.window.WindowContainerToken;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -433,7 +432,7 @@ public final class MediaProjectionManagerService extends SystemService
private IBinder mToken;
private IBinder.DeathRecipient mDeathEater;
private boolean mRestoreSystemAlertWindow;
- private WindowContainerToken mTaskRecordingWindowContainerToken = null;
+ private IBinder mLaunchCookie = null;
MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
boolean isPrivileged) {
@@ -609,14 +608,13 @@ public final class MediaProjectionManagerService extends SystemService
}
@Override // Binder call
- public void setTaskRecordingWindowContainerToken(WindowContainerToken token) {
- // TODO(b/221417940) set the task id to record from sysui, for the package chosen.
- mTaskRecordingWindowContainerToken = token;
+ public void setLaunchCookie(IBinder launchCookie) {
+ mLaunchCookie = launchCookie;
}
@Override // Binder call
- public WindowContainerToken getTaskRecordingWindowContainerToken() {
- return mTaskRecordingWindowContainerToken;
+ public IBinder getLaunchCookie() {
+ return mLaunchCookie;
}
public MediaProjectionInfo getProjectionInfo() {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index de9102a69a2e..6135fe8acbed 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -340,7 +340,8 @@ public class ZenModeHelper {
int newRuleInstanceCount = getCurrentInstanceCount(automaticZenRule.getOwner())
+ getCurrentInstanceCount(automaticZenRule.getConfigurationActivity())
+ 1;
- if (newRuleInstanceCount > RULE_LIMIT_PER_PACKAGE
+ int newPackageRuleCount = getPackageRuleCount(pkg) + 1;
+ if (newPackageRuleCount > RULE_LIMIT_PER_PACKAGE
|| (ruleInstanceLimit > 0 && ruleInstanceLimit < newRuleInstanceCount)) {
throw new IllegalArgumentException("Rule instance limit exceeded");
}
@@ -521,6 +522,23 @@ public class ZenModeHelper {
return count;
}
+ // Equivalent method to getCurrentInstanceCount, but for all rules associated with a specific
+ // package rather than a condition provider service or activity.
+ private int getPackageRuleCount(String pkg) {
+ if (pkg == null) {
+ return 0;
+ }
+ int count = 0;
+ synchronized (mConfig) {
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (pkg.equals(rule.getPkg())) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
public boolean canManageAutomaticZenRule(ZenRule rule) {
final int callingUid = Binder.getCallingUid();
if (callingUid == 0 || callingUid == Process.SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index bdde4f6ad86b..b05e44f9e625 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -62,7 +62,8 @@ final class OverlayManagerShellCommand extends ShellCommand {
private final Context mContext;
private final IOverlayManager mInterface;
private static final Map<String, Integer> TYPE_MAP = Map.of(
- "color", TypedValue.TYPE_FIRST_COLOR_INT);
+ "color", TypedValue.TYPE_FIRST_COLOR_INT,
+ "string", TypedValue.TYPE_STRING);
OverlayManagerShellCommand(@NonNull final Context ctx, @NonNull final IOverlayManager iom) {
mContext = ctx;
@@ -390,13 +391,17 @@ final class OverlayManagerShellCommand extends ShellCommand {
type = Integer.parseUnsignedInt(typeString);
}
}
- final int intData;
- if (valueString.startsWith("0x")) {
- intData = Integer.parseUnsignedInt(valueString.substring(2), 16);
+ if (type == TypedValue.TYPE_STRING) {
+ overlayBuilder.setResourceValue(resourceName, type, valueString);
} else {
- intData = Integer.parseUnsignedInt(valueString);
+ final int intData;
+ if (valueString.startsWith("0x")) {
+ intData = Integer.parseUnsignedInt(valueString.substring(2), 16);
+ } else {
+ intData = Integer.parseUnsignedInt(valueString);
+ }
+ overlayBuilder.setResourceValue(resourceName, type, intData);
}
- overlayBuilder.setResourceValue(resourceName, type, intData);
}
private int runEnableExclusive() throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 109e7071469c..a909977583b4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6760,6 +6760,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
long deleteOatArtifactsOfPackage(@NonNull Computer snapshot, String packageName) {
+ PackageManagerServiceUtils.enforceSystemOrRootOrShell(
+ "Only the system or shell can delete oat artifacts");
+
PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
if (packageState == null || packageState.getPkg() == null) {
return -1; // error code of deleteOptimizedFiles
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 2db1c2029d9c..24ed6216f7fe 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -294,6 +294,10 @@ final class ResolveIntentHelper {
// non-launchable IntentSender which contains the failed intent is created. The
// SendIntentException is thrown if the IntentSender#sendIntent is invoked.
if (ris != null && !ris.isEmpty()) {
+ // am#isIntentSenderTargetedToPackage returns false if both package name and component
+ // name are set in the intent. Clear the package name to have the api return true and
+ // prevent the package existence info from side channel leaks by the api.
+ intent.setPackage(null);
intent.setClassName(ris.get(0).activityInfo.packageName,
ris.get(0).activityInfo.name);
}
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 6e4acde501c3..201ca9c4e9cd 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -1530,6 +1530,7 @@ public class ParsingPackageUtils {
try {
int minVers = ParsingUtils.DEFAULT_MIN_SDK_VERSION;
String minCode = null;
+ boolean minAssigned = false;
int targetVers = ParsingUtils.DEFAULT_TARGET_SDK_VERSION;
String targetCode = null;
int maxVers = Integer.MAX_VALUE;
@@ -1538,9 +1539,11 @@ public class ParsingPackageUtils {
if (val != null) {
if (val.type == TypedValue.TYPE_STRING && val.string != null) {
minCode = val.string.toString();
+ minAssigned = !TextUtils.isEmpty(minCode);
} else {
// If it's not a string, it's an integer.
minVers = val.data;
+ minAssigned = true;
}
}
@@ -1548,7 +1551,7 @@ public class ParsingPackageUtils {
if (val != null) {
if (val.type == TypedValue.TYPE_STRING && val.string != null) {
targetCode = val.string.toString();
- if (minCode == null) {
+ if (!minAssigned) {
minCode = targetCode;
}
} else {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 50d1bd6c3e33..70be8b82ba78 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -42,12 +42,13 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.SynchronousUserSwitchObserver;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -63,6 +64,7 @@ import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.BatterySaverPolicyConfig;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
@@ -94,6 +96,7 @@ import android.service.dreams.DreamManagerInternal;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
import android.sysprop.InitProperties;
+import android.sysprop.PowerProperties;
import android.util.ArrayMap;
import android.util.KeyValueListParser;
import android.util.LongArray;
@@ -124,6 +127,7 @@ import com.android.server.UiThread;
import com.android.server.UserspaceRebootLogger;
import com.android.server.Watchdog;
import com.android.server.am.BatteryStatsService;
+import com.android.server.compat.PlatformCompat;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
import com.android.server.policy.WindowManagerPolicy;
@@ -279,6 +283,17 @@ public final class PowerManagerService extends SystemService
*/
private static final long ENHANCED_DISCHARGE_PREDICTION_BROADCAST_MIN_DELAY_MS = 60 * 1000L;
+ /**
+ * Apps targeting Android U and above need to define
+ * {@link android.Manifest.permission#TURN_SCREEN_ON} in their manifest for
+ * {@link android.os.PowerManager#ACQUIRE_CAUSES_WAKEUP} to have any effect.
+ * Note that most applications should use {@link android.R.attr#turnScreenOn} or
+ * {@link android.app.Activity#setTurnScreenOn(boolean)} instead, as this prevents the
+ * previous foreground app from being resumed first when the screen turns on.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long REQUIRE_TURN_SCREEN_ON_PERMISSION = 216114297L;
/** Reason ID for holding display suspend blocker. */
private static final String HOLDING_DISPLAY_SUSPEND_BLOCKER = "holding display";
@@ -302,6 +317,7 @@ public final class PowerManagerService extends SystemService
private final SystemPropertiesWrapper mSystemProperties;
private final Clock mClock;
private final Injector mInjector;
+ private final PlatformCompat mPlatformCompat;
private AppOpsManager mAppOpsManager;
private LightsManager mLightsManager;
@@ -980,6 +996,11 @@ public final class PowerManagerService extends SystemService
public void set(String key, String val) {
SystemProperties.set(key, val);
}
+
+ @Override
+ public boolean getBoolean(String key, boolean def) {
+ return SystemProperties.getBoolean(key, def);
+ }
};
}
@@ -1005,6 +1026,10 @@ public final class PowerManagerService extends SystemService
AppOpsManager createAppOpsManager(Context context) {
return context.getSystemService(AppOpsManager.class);
}
+
+ PlatformCompat createPlatformCompat(Context context) {
+ return context.getSystemService(PlatformCompat.class);
+ }
}
final Constants mConstants;
@@ -1061,6 +1086,8 @@ public final class PowerManagerService extends SystemService
mAppOpsManager = injector.createAppOpsManager(mContext);
+ mPlatformCompat = injector.createPlatformCompat(mContext);
+
mPowerGroupWakefulnessChangeListener = new PowerGroupWakefulnessChangeListener();
// Save brightness values:
@@ -1470,18 +1497,10 @@ public final class PowerManagerService extends SystemService
updatePowerStateLocked();
}
+ @RequiresPermission(value = android.Manifest.permission.TURN_SCREEN_ON, conditional = true)
private void acquireWakeLockInternal(IBinder lock, int displayId, int flags, String tag,
String packageName, WorkSource ws, String historyTag, int uid, int pid,
@Nullable IWakeLockCallback callback) {
-
- boolean isCallerPrivileged = false;
- try {
- ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(packageName,
- PackageManager.ApplicationInfoFlags.of(0));
- isCallerPrivileged = appInfo.uid == uid && appInfo.isPrivilegedApp();
- } catch (PackageManager.NameNotFoundException e) {
- // assume app is not privileged
- }
synchronized (mLock) {
if (displayId != Display.INVALID_DISPLAY) {
final DisplayInfo displayInfo =
@@ -1528,7 +1547,7 @@ public final class PowerManagerService extends SystemService
notifyAcquire = true;
}
- applyWakeLockFlagsOnAcquireLocked(wakeLock, isCallerPrivileged);
+ applyWakeLockFlagsOnAcquireLocked(wakeLock);
mDirty |= DIRTY_WAKE_LOCKS;
updatePowerStateLocked();
if (notifyAcquire) {
@@ -1567,33 +1586,44 @@ public final class PowerManagerService extends SystemService
return null;
}
- private boolean isAcquireCausesWakeupFlagAllowed(String opPackageName, int opUid,
- boolean isCallerPrivileged) {
+ @RequiresPermission(value = android.Manifest.permission.TURN_SCREEN_ON, conditional = true)
+ private boolean isAcquireCausesWakeupFlagAllowed(String opPackageName, int opUid) {
if (opPackageName == null) {
return false;
}
- if (isCallerPrivileged) {
- if (DEBUG_SPEW) {
- Slog.d(TAG, "Allowing device wake-up for privileged app, call attributed to "
- + opPackageName);
- }
- return true;
- }
if (mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON, opUid, opPackageName)
== AppOpsManager.MODE_ALLOWED) {
- if (DEBUG_SPEW) {
- Slog.d(TAG, "Allowing device wake-up for app with special access " + opPackageName);
+ if (mPlatformCompat.isChangeEnabledByPackageName(REQUIRE_TURN_SCREEN_ON_PERMISSION,
+ opPackageName, UserHandle.getUserId(opUid))) {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.TURN_SCREEN_ON)
+ == PackageManager.PERMISSION_GRANTED) {
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "Allowing device wake-up from app " + opPackageName);
+ }
+ return true;
+ }
+ } else {
+ // android.permission.TURN_SCREEN_ON has only been introduced in Android U, only
+ // check for appOp for apps targeting lower SDK versions
+ if (DEBUG_SPEW) {
+ Slog.d(TAG, "Allowing device wake-up from app with "
+ + "REQUIRE_TURN_SCREEN_ON_PERMISSION disabled " + opPackageName);
+ }
+ return true;
}
- return true;
}
- if (DEBUG_SPEW) {
- Slog.d(TAG, "Not allowing device wake-up for " + opPackageName);
+ if (PowerProperties.permissionless_turn_screen_on().orElse(true)) {
+ Slog.d(TAG, "Device wake-up will be denied without android.permission.TURN_SCREEN_ON");
+ return true;
}
+ Slog.w(TAG, "Not allowing device wake-up for " + opPackageName);
return false;
}
@GuardedBy("mLock")
- private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock, boolean isCallerPrivileged) {
+ @RequiresPermission(value = android.Manifest.permission.TURN_SCREEN_ON, conditional = true)
+ private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock) {
if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0
&& isScreenLock(wakeLock)) {
String opPackageName;
@@ -1615,8 +1645,7 @@ public final class PowerManagerService extends SystemService
}
Integer powerGroupId = wakeLock.getPowerGroupId();
// powerGroupId is null if the wakelock associated display is no longer available
- if (powerGroupId != null && isAcquireCausesWakeupFlagAllowed(opPackageName, opUid,
- isCallerPrivileged)) {
+ if (powerGroupId != null && isAcquireCausesWakeupFlagAllowed(opPackageName, opUid)) {
if (powerGroupId == Display.INVALID_DISPLAY_GROUP) {
// wake up all display groups
if (DEBUG_SPEW) {
@@ -5438,6 +5467,7 @@ public final class PowerManagerService extends SystemService
}
@Override // Binder call
+ @RequiresPermission(value = android.Manifest.permission.TURN_SCREEN_ON, conditional = true)
public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
WorkSource ws, String historyTag, int displayId,
@Nullable IWakeLockCallback callback) {
diff --git a/services/core/java/com/android/server/power/SystemPropertiesWrapper.java b/services/core/java/com/android/server/power/SystemPropertiesWrapper.java
index 1acf798eb099..c68f9c63b13b 100644
--- a/services/core/java/com/android/server/power/SystemPropertiesWrapper.java
+++ b/services/core/java/com/android/server/power/SystemPropertiesWrapper.java
@@ -48,4 +48,19 @@ interface SystemPropertiesWrapper {
* SELinux. libc will log the underlying reason.
*/
void set(@NonNull String key, @Nullable String val);
+
+ /**
+ * Get the value for the given {@code key}, returned as a boolean.
+ * Values 'n', 'no', '0', 'false' or 'off' are considered false.
+ * Values 'y', 'yes', '1', 'true' or 'on' are considered true.
+ * (case sensitive).
+ * If the key does not exist, or has any other value, then the default
+ * result is returned.
+ *
+ * @param key the key to lookup
+ * @param def a default value to return
+ * @return the key parsed as a boolean, or def if the key isn't found or is
+ * not able to be parsed as a boolean.
+ */
+ boolean getBoolean(@NonNull String key, boolean def);
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 622de57a1078..270891fcf421 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -56,6 +56,10 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
import static com.android.internal.util.FrameworkStatsLog.CAMERA_COMPAT_CONTROL_EVENT_REPORTED__EVENT__APPEARED_APPLY_TREATMENT;
@@ -1376,7 +1380,7 @@ class ActivityMetricsLogger {
return;
}
- logAppCompatStateInternal(activity, state, packageUid, compatStateInfo);
+ logAppCompatStateInternal(activity, state, compatStateInfo);
}
/**
@@ -1416,18 +1420,61 @@ class ActivityMetricsLogger {
}
}
if (activityToLog != null && stateToLog != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) {
- logAppCompatStateInternal(activityToLog, stateToLog, packageUid, compatStateInfo);
+ logAppCompatStateInternal(activityToLog, stateToLog, compatStateInfo);
}
}
+ private static boolean isAppCompateStateChangedToLetterboxed(int state) {
+ return state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO
+ || state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION
+ || state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
+ }
+
private void logAppCompatStateInternal(@NonNull ActivityRecord activity, int state,
- int packageUid, PackageCompatStateInfo compatStateInfo) {
+ PackageCompatStateInfo compatStateInfo) {
compatStateInfo.mLastLoggedState = state;
compatStateInfo.mLastLoggedActivity = activity;
- FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED, packageUid, state);
+ int packageUid = activity.info.applicationInfo.uid;
+
+ int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
+ if (isAppCompateStateChangedToLetterboxed(state)) {
+ positionToLog = activity.mLetterboxUiController.getLetterboxPositionForLogging();
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED,
+ packageUid, state, positionToLog);
+
+ if (DEBUG_METRICS) {
+ Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s, %s)",
+ packageUid, state, positionToLog));
+ }
+ }
+
+ /**
+ * Logs the changing of the letterbox position along with its package UID
+ */
+ void logLetterboxPositionChange(@NonNull ActivityRecord activity, int position) {
+ int packageUid = activity.info.applicationInfo.uid;
+ FrameworkStatsLog.write(FrameworkStatsLog.LETTERBOX_POSITION_CHANGED, packageUid, position);
+
+ if (!mPackageUidToCompatStateInfo.contains(packageUid)) {
+ // There is no last logged activity for this packageUid so we should not log the
+ // position change as we can only log the position change for the current activity
+ return;
+ }
+ final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid);
+ final ActivityRecord lastLoggedActivity = compatStateInfo.mLastLoggedActivity;
+ if (activity != lastLoggedActivity) {
+ // Only log the position change for the current activity to be consistent with
+ // findAppCompatStateToLog and ensure that metrics for the state changes are computed
+ // correctly
+ return;
+ }
+ int state = activity.getAppCompatState();
+ logAppCompatStateInternal(activity, state, compatStateInfo);
if (DEBUG_METRICS) {
- Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s)", packageUid, state));
+ Slog.i(TAG, String.format("LETTERBOX_POSITION_CHANGED(%s, %s)",
+ packageUid, position));
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index b70f7a0fd213..0a44cda8d7e2 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3215,12 +3215,29 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return false;
}
- if (mRootWindowContainer.getTopResumedActivity() == this
- && getDisplayContent().mFocusedApp == this) {
- ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: already on top, "
- + "activity=%s", this);
- return !isState(RESUMED);
+ // If this activity already positions on the top focused task, moving the task to front
+ // is not needed. But we still need to ensure this activity is focused because the
+ // current focused activity could be another activity in the same Task if activities are
+ // displayed on adjacent TaskFragments.
+ final ActivityRecord currentFocusedApp = mDisplayContent.mFocusedApp;
+ if (currentFocusedApp != null && currentFocusedApp.task == task) {
+ final Task topFocusableTask = mDisplayContent.getTask(
+ (t) -> t.isLeafTask() && t.isFocusable(), true /* traverseTopToBottom */);
+ if (task == topFocusableTask) {
+ if (currentFocusedApp == this) {
+ ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: already on top "
+ + "and focused, activity=%s", this);
+ } else {
+ ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: set focused, "
+ + "activity=%s", this);
+ mDisplayContent.setFocusedApp(this);
+ mAtmService.mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+ true /* updateInputWindows */);
+ }
+ return !isState(RESUMED);
+ }
}
+
ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: activity=%s", this);
rootTask.moveToFront(reason, task);
@@ -7796,11 +7813,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
newParentConfiguration.windowConfiguration.getWindowingMode();
final boolean isFixedOrientationLetterboxAllowed =
parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
- || parentWindowingMode == WINDOWING_MODE_FULLSCREEN;
+ || parentWindowingMode == WINDOWING_MODE_FULLSCREEN
+ // Switching from PiP to fullscreen.
+ || (parentWindowingMode == WINDOWING_MODE_PINNED
+ && resolvedConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN);
// TODO(b/181207944): Consider removing the if condition and always run
// resolveFixedOrientationConfiguration() since this should be applied for all cases.
if (isFixedOrientationLetterboxAllowed) {
- resolveFixedOrientationConfiguration(newParentConfiguration, parentWindowingMode);
+ resolveFixedOrientationConfiguration(newParentConfiguration);
}
if (mCompatDisplayInsets != null) {
@@ -8092,8 +8113,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied
* in this method.
*/
- private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig,
- int windowingMode) {
+ private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
mIsEligibleForFixedOrientationLetterbox = false;
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
@@ -8113,11 +8133,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (organizedTf != null && !organizedTf.fillsParent()) {
return;
}
- if (windowingMode == WINDOWING_MODE_PINNED) {
- // PiP bounds have higher priority than the requested orientation. Otherwise the
- // activity may be squeezed into a small piece.
- return;
- }
final Rect resolvedBounds =
getResolvedOverrideConfiguration().windowConfiguration.getBounds();
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index c14e54ef586a..9637aca4c690 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2576,6 +2576,7 @@ class ActivityStarter {
mInTask = null;
}
mInTaskFragment = inTaskFragment;
+ sendNewTaskFragmentResultRequestIfNeeded();
mStartFlags = startFlags;
// If the onlyIfNeeded flag is set, then we can do this if the activity being launched
@@ -2618,6 +2619,18 @@ class ActivityStarter {
}
}
+ private void sendNewTaskFragmentResultRequestIfNeeded() {
+ if (mStartActivity.resultTo != null && mInTaskFragment != null
+ && mInTaskFragment != mStartActivity.resultTo.getTaskFragment()) {
+ Slog.w(TAG,
+ "Activity is launching as a new TaskFragment, so cancelling activity result.");
+ mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho,
+ mStartActivity.requestCode, RESULT_CANCELED,
+ null /* data */, null /* dataGrants */);
+ mStartActivity.resultTo = null;
+ }
+ }
+
private void computeLaunchingTaskFlags() {
// If the caller is not coming from another activity, but has given us an explicit task into
// which they would like us to launch the new activity, then let's see about doing that.
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index fca4942d4b79..fff7637acc7e 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -63,6 +63,7 @@ final class ContentRecordingController {
*/
void setContentRecordingSessionLocked(@Nullable ContentRecordingSession incomingSession,
@NonNull WindowManagerService wmService) {
+ // TODO(b/219761722) handle a null session arriving due to task setup failing
if (incomingSession != null && (!ContentRecordingSession.isValid(incomingSession)
|| ContentRecordingSession.isSameDisplay(mSession, incomingSession))) {
// Ignore an invalid session, or a session for the same display as currently recording.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index afcb21a45106..6619f985df58 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1611,7 +1611,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (mTransitionController.useShellTransitionsRotation()) {
return ROTATION_UNDEFINED;
}
- if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) {
+ if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM
+ || getIgnoreOrientationRequest()) {
return ROTATION_UNDEFINED;
}
if (r.mOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
@@ -2942,9 +2943,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 +6196,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/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index d2c71f57e701..b9d83198139d 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -620,7 +620,8 @@ public class DisplayRotation {
// We only enable seamless rotation if the top window has requested it and is in the
// fullscreen opaque state. Seamless rotation requires freezing various Surface states and
// won't work well with animations, so we disable it in the animation case for now.
- if (w.getAttrs().rotationAnimation != ROTATION_ANIMATION_SEAMLESS || w.isAnimatingLw()) {
+ if (w.getAttrs().rotationAnimation != ROTATION_ANIMATION_SEAMLESS || w.inMultiWindowMode()
+ || w.isAnimatingLw()) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 08715b160b9a..91b2fb63a543 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -687,6 +687,24 @@ final class LetterboxConfiguration {
}
}
+ /*
+ * Gets the horizontal position of the letterboxed app window when horizontal reachability is
+ * enabled.
+ */
+ @LetterboxHorizontalReachabilityPosition
+ int getLetterboxPositionForHorizontalReachability() {
+ return mLetterboxPositionForHorizontalReachability;
+ }
+
+ /*
+ * Gets the vertical position of the letterboxed app window when vertical reachability is
+ * enabled.
+ */
+ @LetterboxVerticalReachabilityPosition
+ int getLetterboxPositionForVerticalReachability() {
+ return mLetterboxPositionForVerticalReachability;
+ }
+
/** Returns a string representing the given {@link LetterboxHorizontalReachabilityPosition}. */
static String letterboxHorizontalReachabilityPositionToString(
@LetterboxHorizontalReachabilityPosition int position) {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f849d2886ba1..d65276793700 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -21,6 +21,20 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
import static com.android.server.wm.ActivityRecord.computeAspectRatio;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -28,6 +42,12 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString;
@@ -259,12 +279,26 @@ final class LetterboxUiController {
return;
}
+ int letterboxPositionForHorizontalReachability = mLetterboxConfiguration
+ .getLetterboxPositionForHorizontalReachability();
if (mLetterbox.getInnerFrame().left > x) {
// Moving to the next stop on the left side of the app window: right > center > left.
mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextLeftStop();
+ int changeToLog =
+ letterboxPositionForHorizontalReachability
+ == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
} else if (mLetterbox.getInnerFrame().right < x) {
// Moving to the next stop on the right side of the app window: left > center > right.
mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
+ int changeToLog =
+ letterboxPositionForHorizontalReachability
+ == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
}
// TODO(197549949): Add animation for transition.
@@ -280,13 +314,26 @@ final class LetterboxUiController {
// Only react to clicks at the top and bottom of the letterboxed app window.
return;
}
-
+ int letterboxPositionForVerticalReachability = mLetterboxConfiguration
+ .getLetterboxPositionForVerticalReachability();
if (mLetterbox.getInnerFrame().top > y) {
// Moving to the next stop on the top side of the app window: bottom > center > top.
mLetterboxConfiguration.movePositionForVerticalReachabilityToNextTopStop();
+ int changeToLog =
+ letterboxPositionForVerticalReachability
+ == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
} else if (mLetterbox.getInnerFrame().bottom < y) {
// Moving to the next stop on the bottom side of the app window: top > center > bottom.
mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
+ int changeToLog =
+ letterboxPositionForVerticalReachability
+ == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
}
// TODO(197549949): Add animation for transition.
@@ -577,4 +624,63 @@ final class LetterboxUiController {
return "UNKNOWN_REASON";
}
+ private int letterboxHorizontalReachabilityPositionToLetterboxPosition(
+ @LetterboxConfiguration.LetterboxHorizontalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox horizontal reachability position type: "
+ + position);
+ }
+ }
+
+ private int letterboxVerticalReachabilityPositionToLetterboxPosition(
+ @LetterboxConfiguration.LetterboxVerticalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox vertical reachability position type: "
+ + position);
+ }
+ }
+
+ int getLetterboxPositionForLogging() {
+ int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+ if (isHorizontalReachabilityEnabled()) {
+ int letterboxPositionForHorizontalReachability = getLetterboxConfiguration()
+ .getLetterboxPositionForHorizontalReachability();
+ positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition(
+ letterboxPositionForHorizontalReachability);
+ } else if (isVerticalReachabilityEnabled()) {
+ int letterboxPositionForVerticalReachability = getLetterboxConfiguration()
+ .getLetterboxPositionForVerticalReachability();
+ positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition(
+ letterboxPositionForVerticalReachability);
+ }
+ return positionToLog;
+ }
+
+ private LetterboxConfiguration getLetterboxConfiguration() {
+ return mLetterboxConfiguration;
+ }
+
+ /**
+ * Logs letterbox position changes via {@link ActivityMetricsLogger#logLetterboxPositionChange}.
+ */
+ private void logLetterboxPositionChange(int letterboxPositionChange) {
+ mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
+ .logLetterboxPositionChange(mActivityRecord, letterboxPositionChange);
+ }
}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index ad158c7b45b9..ac1a2b17603a 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -331,8 +331,10 @@ class RemoteAnimationController implements DeathRecipient {
private void invokeAnimationCancelled(String reason) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "cancelAnimation(): reason=%s", reason);
+ final boolean isKeyguardOccluded = mDisplayContent.isKeyguardOccluded();
+
try {
- mRemoteAnimationAdapter.getRunner().onAnimationCancelled();
+ mRemoteAnimationAdapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to notify cancel", e);
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 4bb023c1c218..52bf220a647a 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1114,7 +1114,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
// If a task is launching from a created-by-organizer task, it should be launched into the
// same created-by-organizer task as well. Unless, the candidate task is already positioned
// in the another adjacent task.
- if (sourceTask != null) {
+ if (sourceTask != null && (candidateTask == null
+ // A pinned task relaunching should be handled by its task organizer. Skip fallback
+ // launch target of a pinned task from source task.
+ || candidateTask.getWindowingMode() != WINDOWING_MODE_PINNED)) {
Task launchTarget = sourceTask.getCreatedByOrganizerTask();
if (launchTarget != null && launchTarget.getAdjacentTaskFragment() != null) {
if (candidateTask != null) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3c0cac0079e8..c27a3d34a02f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -149,9 +149,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
final @TransitionType int mType;
private int mSyncId = -1;
- // Used for tracking a Transition throughout a lifecycle (i.e. from STATE_COLLECTING to
- // STATE_FINISHED or STATE_ABORT), and should only be used for testing and debugging.
- private int mDebugId = -1;
private @TransitionFlags int mFlags;
private final TransitionController mController;
private final BLASTSyncEngine mSyncEngine;
@@ -295,11 +292,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return mSyncId;
}
- @VisibleForTesting
- int getDebugId() {
- return mDebugId;
- }
-
@TransitionFlags
int getFlags() {
return mFlags;
@@ -315,6 +307,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return mFinishTransaction;
}
+ private boolean isCollecting() {
+ return mState == STATE_COLLECTING || mState == STATE_STARTED;
+ }
+
/** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
void startCollecting(long timeoutMs) {
if (mState != STATE_PENDING) {
@@ -322,7 +318,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
mState = STATE_COLLECTING;
mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
- mDebugId = mSyncId;
mController.mTransitionTracer.logState(this);
}
@@ -353,7 +348,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
if (mState < STATE_COLLECTING) {
throw new IllegalStateException("Transition hasn't started collecting.");
}
- if (mSyncId < 0) return;
+ if (!isCollecting()) {
+ // Too late, transition already started playing, so don't collect.
+ return;
+ }
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
mSyncId, wc);
// "snapshot" all parents (as potential promotion targets). Do this before checking
@@ -403,7 +401,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
* or waiting until after the animation to close).
*/
void collectExistenceChange(@NonNull WindowContainer wc) {
- if (mSyncId < 0) return;
+ if (mState >= STATE_PLAYING) {
+ // Too late to collect. Don't check too-early here since `collect` will check that.
+ return;
+ }
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:"
+ " %s", mSyncId, wc);
collect(wc);
@@ -437,7 +438,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
*/
void setOverrideAnimation(TransitionInfo.AnimationOptions options,
@Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
- if (mSyncId < 0) return;
+ if (!isCollecting()) return;
mOverrideOptions = options;
sendRemoteCallback(mClientAnimationStartCallback);
mClientAnimationStartCallback = startCallback;
@@ -455,7 +456,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
* The transition will wait for all groups to be ready.
*/
void setReady(WindowContainer wc, boolean ready) {
- if (mSyncId < 0) return;
+ if (!isCollecting() || mSyncId < 0) return;
mReadyTracker.setReadyFrom(wc, ready);
applyReady();
}
@@ -473,7 +474,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
* @see ReadyTracker#setAllReady.
*/
void setAllReady() {
- if (mSyncId < 0) return;
+ if (!isCollecting() || mSyncId < 0) return;
mReadyTracker.setAllReady();
applyReady();
}
@@ -889,7 +890,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
// No player registered, so just finish/apply immediately
cleanUpOnFailure();
}
- mSyncId = -1;
mOverrideOptions = null;
reportStartReasonsToLogger();
@@ -1614,7 +1614,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
boolean getLegacyIsReady() {
- return (mState == STATE_STARTED || mState == STATE_COLLECTING) && mSyncId >= 0;
+ return isCollecting() && mSyncId >= 0;
}
static Transition fromBinder(IBinder binder) {
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index b1951e038177..c1927d864320 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -79,7 +79,7 @@ public class TransitionTracer {
final ProtoOutputStream outputStream = new ProtoOutputStream();
final long transitionEntryToken = outputStream.start(TRANSITION);
- outputStream.write(ID, transition.getDebugId());
+ outputStream.write(ID, transition.getSyncId());
outputStream.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
outputStream.write(TRANSITION_TYPE, transition.mType);
outputStream.write(STATE, transition.getState());
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9d5b9455da13..d7c85d4c7853 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -48,6 +48,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
import static android.provider.Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR;
import static android.provider.Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH;
+import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
@@ -122,7 +123,6 @@ 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;
@@ -289,6 +289,7 @@ import android.view.displayhash.VerifiedDisplayHash;
import android.window.ClientWindowFrames;
import android.window.ITaskFpsCallback;
import android.window.TaskSnapshot;
+import android.window.WindowContainerToken;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -2245,7 +2246,7 @@ public class WindowManagerService extends IWindowManager.Stub
return 0;
}
- if (win.cancelAndRedraw()) {
+ if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= win.mLastSeqIdSentToRelayout) {
result |= RELAYOUT_RES_CANCEL_AND_REDRAW;
}
@@ -2551,11 +2552,6 @@ 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);
}
@@ -8281,6 +8277,26 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) {
synchronized (mGlobalLock) {
+ // Allow the controller to handle teardown or a non-task session.
+ if (incomingSession == null
+ || incomingSession.getContentToRecord() != RECORD_CONTENT_TASK) {
+ mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
+ WindowManagerService.this);
+ return;
+ }
+ // For a task session, find the activity identified by the launch cookie.
+ final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie(
+ incomingSession.getTokenToRecord());
+ if (wct == null) {
+ Slog.w(TAG, "Handling a new recording session; unable to find the "
+ + "WindowContainerToken");
+ mContentRecordingController.setContentRecordingSessionLocked(null,
+ WindowManagerService.this);
+ return;
+ }
+ // Replace the launch cookie in the session details with the task's
+ // WindowContainerToken.
+ incomingSession.setTokenToRecord(wct.asBinder());
mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
WindowManagerService.this);
}
@@ -8539,6 +8555,38 @@ public class WindowManagerService extends IWindowManager.Stub
}
/**
+ * Retrieve the {@link WindowContainerToken} of the task that contains the activity started
+ * with the given launch cookie.
+ *
+ * @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an
+ * activity
+ * @return a token representing the task containing the activity started with the given launch
+ * cookie, or {@code null} if the token couldn't be found.
+ */
+ @VisibleForTesting
+ @Nullable
+ WindowContainerToken getTaskWindowContainerTokenForLaunchCookie(@NonNull IBinder launchCookie) {
+ // Find the activity identified by the launch cookie.
+ final ActivityRecord targetActivity = mRoot.getActivity(
+ activity -> activity.mLaunchCookie == launchCookie);
+ if (targetActivity == null) {
+ Slog.w(TAG, "Unable to find the activity for this launch cookie");
+ return null;
+ }
+ if (targetActivity.getTask() == null) {
+ Slog.w(TAG, "Unable to find the task for this launch cookie");
+ return null;
+ }
+ WindowContainerToken taskWindowContainerToken =
+ targetActivity.getTask().mRemoteToken.toWindowContainerToken();
+ if (taskWindowContainerToken == null) {
+ Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName());
+ return null;
+ }
+ return taskWindowContainerToken;
+ }
+
+ /**
* You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY.
*/
private int sanitizeFlagSlippery(int flags, String windowName, int callingUid, int callingPid) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6728e63d055f..46091d842c2a 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -115,6 +115,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RESIZE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
@@ -391,7 +392,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
int mSyncSeqId = 0;
int mLastSeqIdSentToRelayout = 0;
- boolean mAlreadyRequestedSync;
+
+ /** The last syncId associated with a prepareSync or 0 when no sync is active. */
+ int mPrepareSyncSeqId = 0;
/**
* {@code true} when the client was still drawing for sync when the sync-set was finished or
@@ -4421,7 +4424,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
- pw.println(prefix + "mAlreadyRequestedSync=" + mAlreadyRequestedSync);
+ pw.println(prefix + "mPrepareSyncSeqId=" + mPrepareSyncSeqId);
}
@Override
@@ -5913,6 +5916,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mWinAnimator.getSurfaceControl();
}
+ /** Drops a buffer for this window's view-root from a transaction */
+ private void dropBufferFrom(Transaction t) {
+ SurfaceControl viewSurface = getClientViewRootSurface();
+ if (viewSurface == null) return;
+ t.setBuffer(viewSurface, (android.hardware.HardwareBuffer) null);
+ }
+
@Override
boolean prepareSync() {
if (!mDrawHandlers.isEmpty()) {
@@ -5928,7 +5938,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// to draw even if the children draw first or don't need to sync, so we start
// in WAITING state rather than READY.
mSyncState = SYNC_STATE_WAITING_FOR_DRAW;
+
+ if (mPrepareSyncSeqId > 0) {
+ // another prepareSync during existing sync (eg. reparented), so pre-emptively
+ // drop buffer (if exists). If the buffer hasn't been received yet, it will be
+ // dropped in finishDrawing.
+ ProtoLog.d(WM_DEBUG_SYNC_ENGINE, "Preparing to sync a window that was already in the"
+ + " sync, so try dropping buffer. win=%s", this);
+ dropBufferFrom(mSyncTransaction);
+ }
+
mSyncSeqId++;
+ mPrepareSyncSeqId = mSyncSeqId;
requestRedrawForSync();
return true;
}
@@ -5949,7 +5970,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) {
mClientWasDrawingForSync = true;
}
- mAlreadyRequestedSync = false;
+ mPrepareSyncSeqId = 0;
+ if (cancel) {
+ // This is leaving sync so any buffers left in the sync have a chance of
+ // being applied out-of-order and can also block the buffer queue for this
+ // window. To prevent this, drop the buffer.
+ dropBufferFrom(mSyncTransaction);
+ }
super.finishSync(outMergedTransaction, cancel);
}
@@ -5971,6 +5998,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
.notifyStartingWindowDrawn(mActivityRecord);
}
+ final boolean syncActive = mPrepareSyncSeqId > 0;
+ final boolean syncStillPending = syncActive && mPrepareSyncSeqId > syncSeqId;
+ if (syncStillPending && postDrawTransaction != null) {
+ ProtoLog.d(WM_DEBUG_SYNC_ENGINE, "Got a buffer for request id=%d but latest request is"
+ + " id=%d. Since the buffer is out-of-date, drop it. win=%s", syncSeqId,
+ mPrepareSyncSeqId, this);
+ // sync is waiting for a newer seqId, so this buffer is obsolete and can be dropped
+ // to free up the buffer queue.
+ dropBufferFrom(postDrawTransaction);
+ }
+
final boolean hasSyncHandlers = executeDrawHandlers(postDrawTransaction, syncSeqId);
boolean skipLayout = false;
@@ -5983,10 +6021,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Layout is not needed because the window will be hidden by the fade leash.
postDrawTransaction = null;
skipLayout = true;
- } else if (onSyncFinishedDrawing() && postDrawTransaction != null) {
- mSyncTransaction.merge(postDrawTransaction);
- // Consume the transaction because the sync group will merge it.
- postDrawTransaction = null;
+ } else if (syncActive) {
+ if (!syncStillPending) {
+ onSyncFinishedDrawing();
+ }
+ if (postDrawTransaction != null) {
+ mSyncTransaction.merge(postDrawTransaction);
+ // Consume the transaction because the sync group will merge it.
+ postDrawTransaction = null;
+ }
}
final boolean layoutNeeded =
@@ -6218,6 +6261,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
public boolean cancelAndRedraw() {
- return mSyncState != SYNC_STATE_NONE && mAlreadyRequestedSync;
+ // Cancel any draw requests during a sync.
+ return mPrepareSyncSeqId > 0;
}
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index ec38f777593f..40789964f4e1 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -119,7 +119,7 @@ cc_defaults {
"libutils",
"libui",
"libvibratorservice",
- "PlatformProperties",
+ "libPlatformProperties",
"libinput",
"libinputflinger",
"libinputflinger_base",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 06c0e366ad1a..cbd574348de0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1981,6 +1981,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
synchronized (getLockObject()) {
mOwners.load();
setDeviceOwnershipSystemPropertyLocked();
+ if (mOwners.hasDeviceOwner()) {
+ setGlobalSettingDeviceOwnerType(
+ mOwners.getDeviceOwnerType(mOwners.getDeviceOwnerPackageName()));
+ }
}
}
@@ -8804,6 +8808,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
deleteTransferOwnershipBundleLocked(userId);
toggleBackupServiceActive(UserHandle.USER_SYSTEM, true);
pushUserControlDisabledPackagesLocked(userId);
+ setGlobalSettingDeviceOwnerType(DEVICE_OWNER_TYPE_DEFAULT);
}
private void clearApplicationRestrictions(int userId) {
@@ -18363,6 +18368,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
"Test only admins can only set the device owner type more than once");
mOwners.setDeviceOwnerType(packageName, deviceOwnerType, isAdminTestOnly);
+ setGlobalSettingDeviceOwnerType(deviceOwnerType);
+ }
+
+ // TODO(b/237065504): Allow mainline modules to get the device owner type. This is a workaround
+ // to get the device owner type in PermissionController. See HibernationPolicy.kt.
+ private void setGlobalSettingDeviceOwnerType(int deviceOwnerType) {
+ mInjector.binderWithCleanCallingIdentity(
+ () -> mInjector.settingsGlobalPutInt("device_owner_type", deviceOwnerType));
}
@Override
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 886a397c87e6..804ccc514df5 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -28,30 +28,6 @@ java_library_static {
],
}
-// Version of services.net for usage by the wifi mainline module.
-// Note: This is compiled against module_current.
-// TODO(b/172457099): This should be moved to networkstack-client,
-// with dependencies moved to frameworks/libs/net right.
-java_library {
- name: "services.net-module-wifi",
- sdk_version: "module_current",
- min_sdk_version: "30",
- static_libs: [
- // All the classes in netd_aidl_interface must be jarjar so they do not conflict with the
- // classes generated by netd_aidl_interfaces-platform-java above.
- "netd_aidl_interface-V3-java",
- "networkstack-client",
- "net-utils-services-common",
- ],
- apex_available: [
- "com.android.wifi",
- ],
- visibility: [
- "//packages/modules/Wifi/service",
- "//packages/modules/Wifi/service/tests/wifitests",
- ],
-}
-
filegroup {
name: "services-tethering-shared-srcs",
srcs: [
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java
index 08cea06fd314..31fe18410182 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java
+++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java
@@ -21,14 +21,19 @@ import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import static com.google.common.truth.Truth.assertThat;
import android.app.AppGlobals;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.IntentSender;
import android.content.pm.IPackageManager;
import android.content.pm.ProviderInfo;
import android.os.Process;
import android.os.UserHandle;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -54,6 +59,7 @@ public class AppEnumerationInternalTests {
private static final String TARGET_HAS_APPOP_PERMISSION =
"com.android.appenumeration.hasappoppermission";
private static final String TARGET_SHARED_USER = "com.android.appenumeration.shareduid";
+ private static final String TARGET_NON_EXISTENT = "com.android.appenumeration.nonexistent.pkg";
private static final String SYNC_PROVIDER_AUTHORITY = TARGET_SYNC_PROVIDER;
private static final String PERMISSION_REQUEST_INSTALL_PACKAGES =
@@ -134,6 +140,26 @@ public class AppEnumerationInternalTests {
assertThat(uid).isEqualTo(Process.INVALID_UID);
}
+ @Test
+ public void getLaunchIntentSenderForPackage_intentSender_cannotDetectPackage()
+ throws Exception {
+ installPackage(SHARED_USER_APK_PATH, false /* forceQueryable */);
+
+ final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ final IntentSender sender = context.getPackageManager()
+ .getLaunchIntentSenderForPackage(TARGET_SHARED_USER);
+ assertThat(new PendingIntent(sender.getTarget()).isTargetedToPackage()).isTrue();
+ sender.sendIntent(context, 0 /* code */, null /* intent */,
+ null /* onFinished */, null /* handler */);
+
+ final IntentSender failedSender = InstrumentationRegistry.getInstrumentation().getContext()
+ .getPackageManager().getLaunchIntentSenderForPackage(TARGET_NON_EXISTENT);
+ assertThat(new PendingIntent(failedSender.getTarget()).isTargetedToPackage()).isTrue();
+ Assert.assertThrows(IntentSender.SendIntentException.class,
+ () -> failedSender.sendIntent(context, 0 /* code */, null /* intent */,
+ null /* onFinished */, null /* handler */));
+ }
+
private static void installPackage(String apkPath, boolean forceQueryable) {
final StringBuilder cmd = new StringBuilder("pm install ");
if (forceQueryable) {
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/AndroidManifest-sharedUser.xml b/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/AndroidManifest-sharedUser.xml
index b424298e0ae0..65d0f218461a 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/AndroidManifest-sharedUser.xml
+++ b/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/AndroidManifest-sharedUser.xml
@@ -19,6 +19,13 @@
package="com.android.appenumeration.shareduid"
android:sharedUserId="com.android.appenumeration.shareduid">
<application>
+ <activity android:name="com.android.appenumeration.testapp.DummyActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.INFO"/>
+ </intent-filter>
+ </activity>
<uses-library android:name="android.test.runner" />
</application>
</manifest> \ No newline at end of file
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/src/com/android/appenumeration/testapp/DummyActivity.java b/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/src/com/android/appenumeration/testapp/DummyActivity.java
new file mode 100644
index 000000000000..bc5d3b5d022d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/appenumeration/test-apps/target/src/com/android/appenumeration/testapp/DummyActivity.java
@@ -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.appenumeration.testapp;
+
+import android.app.Activity;
+
+public class DummyActivity extends Activity {
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 25cf8a86baad..e95924ad7109 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -20,7 +20,9 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricPrompt.DISMISSED_REASON_NEGATIVE;
-import static com.android.server.biometrics.BiometricServiceStateProto.*;
+import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED;
+import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED;
+import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_STARTED_UI_SHOWING;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -32,6 +34,8 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -280,6 +284,43 @@ public class AuthSessionTest {
}
@Test
+ public void testOnDialogAnimatedInDoesNothingDuringInvalidState() throws Exception {
+ setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
+ final long operationId = 123;
+ final int userId = 10;
+
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ operationId,
+ userId);
+ final IBiometricAuthenticator impl = session.mPreAuthInfo.eligibleSensors.get(0).impl;
+
+ session.goToInitialState();
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
+ session.onCookieReceived(
+ session.mPreAuthInfo.eligibleSensors.get(sensor.id).getCookie());
+ }
+ assertTrue(session.allCookiesReceived());
+ assertEquals(STATE_AUTH_STARTED, session.getState());
+ verify(impl, never()).startPreparedClient(anyInt());
+
+ // First invocation should start the client monitor.
+ session.onDialogAnimatedIn();
+ assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
+ verify(impl).startPreparedClient(anyInt());
+
+ // Subsequent invocations should not start the client monitor again.
+ session.onDialogAnimatedIn();
+ session.onDialogAnimatedIn();
+ session.onDialogAnimatedIn();
+ assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState());
+ verify(impl, times(1)).startPreparedClient(anyInt());
+ }
+
+ @Test
public void testCancelAuthentication_whenStateAuthCalled_invokesCancel()
throws RemoteException {
testInvokesCancel(session -> session.onCancelAuthSession(false /* force */));
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index c17347320f52..9e9d70332f00 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -80,11 +80,14 @@ public class BiometricSchedulerOperationTest {
private Handler mHandler;
private BiometricSchedulerOperation mOperation;
+ private boolean mIsDebuggable;
@Before
public void setUp() {
mHandler = new Handler(TestableLooper.get(this).getLooper());
- mOperation = new BiometricSchedulerOperation(mClientMonitor, mClientCallback);
+ mIsDebuggable = false;
+ mOperation = new BiometricSchedulerOperation(mClientMonitor, mClientCallback,
+ () -> mIsDebuggable);
}
@Test
@@ -126,6 +129,34 @@ public class BiometricSchedulerOperationTest {
}
@Test
+ public void testSecondStartWithCookieCrashesWhenDebuggable() {
+ final int cookie = 5;
+ mIsDebuggable = true;
+ when(mClientMonitor.getCookie()).thenReturn(cookie);
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ final boolean started = mOperation.startWithCookie(mOnStartCallback, cookie);
+ assertThat(started).isTrue();
+
+ assertThrows(IllegalStateException.class,
+ () -> mOperation.startWithCookie(mOnStartCallback, cookie));
+ }
+
+ @Test
+ public void testSecondStartWithCookieFailsNicelyWhenNotDebuggable() {
+ final int cookie = 5;
+ mIsDebuggable = false;
+ when(mClientMonitor.getCookie()).thenReturn(cookie);
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ final boolean started = mOperation.startWithCookie(mOnStartCallback, cookie);
+ assertThat(started).isTrue();
+
+ final boolean startedAgain = mOperation.startWithCookie(mOnStartCallback, cookie);
+ assertThat(startedAgain).isFalse();
+ }
+
+ @Test
public void startsWhenReadyAndHalAvailable() {
when(mClientMonitor.getCookie()).thenReturn(0);
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
@@ -170,7 +201,34 @@ public class BiometricSchedulerOperationTest {
}
@Test
+ public void secondStartCrashesWhenDebuggable() {
+ mIsDebuggable = true;
+ when(mClientMonitor.getCookie()).thenReturn(0);
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ final boolean started = mOperation.start(mOnStartCallback);
+ assertThat(started).isTrue();
+
+ assertThrows(IllegalStateException.class, () -> mOperation.start(mOnStartCallback));
+ }
+
+ @Test
+ public void secondStartFailsNicelyWhenNotDebuggable() {
+ mIsDebuggable = false;
+ when(mClientMonitor.getCookie()).thenReturn(0);
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ final boolean started = mOperation.start(mOnStartCallback);
+ assertThat(started).isTrue();
+
+ final boolean startedAgain = mOperation.start(mOnStartCallback);
+ assertThat(startedAgain).isFalse();
+ }
+
+ @Test
public void doesNotStartWithCookie() {
+ // This class only throws exceptions when debuggable.
+ mIsDebuggable = true;
when(mClientMonitor.getCookie()).thenReturn(9);
assertThrows(IllegalStateException.class,
() -> mOperation.start(mock(ClientMonitorCallback.class)));
@@ -178,6 +236,8 @@ public class BiometricSchedulerOperationTest {
@Test
public void cannotRestart() {
+ // This class only throws exceptions when debuggable.
+ mIsDebuggable = true;
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
mOperation.start(mOnStartCallback);
@@ -188,6 +248,8 @@ public class BiometricSchedulerOperationTest {
@Test
public void abortsNotRunning() {
+ // This class only throws exceptions when debuggable.
+ mIsDebuggable = true;
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
mOperation.abort();
@@ -200,7 +262,8 @@ public class BiometricSchedulerOperationTest {
}
@Test
- public void cannotAbortRunning() {
+ public void abortCrashesWhenDebuggableIfOperationIsRunning() {
+ mIsDebuggable = true;
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
mOperation.start(mOnStartCallback);
@@ -209,6 +272,16 @@ public class BiometricSchedulerOperationTest {
}
@Test
+ public void abortFailsNicelyWhenNotDebuggableIfOperationIsRunning() {
+ mIsDebuggable = false;
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ mOperation.start(mOnStartCallback);
+
+ mOperation.abort();
+ }
+
+ @Test
public void cancel() {
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
@@ -254,6 +327,30 @@ public class BiometricSchedulerOperationTest {
}
@Test
+ public void cancelCrashesWhenDebuggableIfOperationIsFinished() {
+ mIsDebuggable = true;
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ mOperation.abort();
+ assertThat(mOperation.isFinished()).isTrue();
+
+ final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
+ assertThrows(IllegalStateException.class, () -> mOperation.cancel(mHandler, cancelCb));
+ }
+
+ @Test
+ public void cancelFailsNicelyWhenNotDebuggableIfOperationIsFinished() {
+ mIsDebuggable = false;
+ when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
+
+ mOperation.abort();
+ assertThat(mOperation.isFinished()).isTrue();
+
+ final ClientMonitorCallback cancelCb = mock(ClientMonitorCallback.class);
+ mOperation.cancel(mHandler, cancelCb);
+ }
+
+ @Test
public void markCanceling() {
when(mClientMonitor.getFreshDaemon()).thenReturn(mHal);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 388170bd24cb..a7ca08385b9f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -7848,7 +7848,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
setDeviceOwner();
dpm.setDeviceOwnerType(admin1, DEVICE_OWNER_TYPE_FINANCED);
when(getServices().ipackageManager.getBlockUninstallForUser(
- eq(packageName), eq(UserHandle.USER_SYSTEM)))
+ eq(packageName), eq(UserHandle.getCallingUserId())))
.thenReturn(true);
assertThat(dpm.isUninstallBlocked(admin1, packageName)).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 46a4e862c300..1fa3871347f8 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -628,6 +628,46 @@ public class OneTouchPlayActionTest {
assertThat(mNativeWrapper.getResultMessages()).contains(standbyMessage);
}
+ @Test
+ public void noWakeUpOnReportPowerStatus() throws Exception {
+ setUp(true);
+
+ HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
+ mHdmiControlService);
+ playbackDevice.init();
+ mLocalDevices.add(playbackDevice);
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
+ mTestLooper.dispatchAll();
+ mNativeWrapper.clearResultMessages();
+
+ TestActionTimer actionTimer = new TestActionTimer();
+ TestCallback callback = new TestCallback();
+ OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback,
+ false);
+ playbackDevice.addAndStartAction(action);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isTrue();
+ mPowerManager.setInteractive(false);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportPowerStatusOn =
+ HdmiCecMessage.build(
+ ADDR_TV,
+ playbackDevice.getDeviceInfo().getLogicalAddress(),
+ Constants.MESSAGE_REPORT_POWER_STATUS,
+ POWER_ON);
+ action.processCommand(reportPowerStatusOn);
+ mTestLooper.dispatchAll();
+
+ assertThat(mPowerManager.isInteractive()).isFalse();
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ }
+
private static class TestActionTimer implements ActionTimer {
private int mState;
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/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index eae9ee7fc55a..09aa345bef22 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -468,18 +468,18 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
@Test
public void testPasswordData_serializeDeserialize() {
PasswordData data = new PasswordData();
- data.scryptN = 11;
- data.scryptR = 22;
- data.scryptP = 33;
+ data.scryptLogN = 11;
+ data.scryptLogR = 22;
+ data.scryptLogP = 33;
data.credentialType = CREDENTIAL_TYPE_PASSWORD;
data.salt = PAYLOAD;
data.passwordHandle = PAYLOAD2;
PasswordData deserialized = PasswordData.fromBytes(data.toBytes());
- assertEquals(11, deserialized.scryptN);
- assertEquals(22, deserialized.scryptR);
- assertEquals(33, deserialized.scryptP);
+ assertEquals(11, deserialized.scryptLogN);
+ assertEquals(22, deserialized.scryptLogR);
+ assertEquals(33, deserialized.scryptLogP);
assertEquals(CREDENTIAL_TYPE_PASSWORD, deserialized.credentialType);
assertArrayEquals(PAYLOAD, deserialized.salt);
assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
@@ -491,9 +491,9 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
// wire format.
byte[] serialized = new byte[] {
0, 0, 0, 2, /* CREDENTIAL_TYPE_PASSWORD_OR_PIN */
- 11, /* scryptN */
- 22, /* scryptR */
- 33, /* scryptP */
+ 11, /* scryptLogN */
+ 22, /* scryptLogR */
+ 33, /* scryptLogP */
0, 0, 0, 5, /* salt.length */
1, 2, -1, -2, 55, /* salt */
0, 0, 0, 6, /* passwordHandle.length */
@@ -501,9 +501,9 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
};
PasswordData deserialized = PasswordData.fromBytes(serialized);
- assertEquals(11, deserialized.scryptN);
- assertEquals(22, deserialized.scryptR);
- assertEquals(33, deserialized.scryptP);
+ assertEquals(11, deserialized.scryptLogN);
+ assertEquals(22, deserialized.scryptLogR);
+ assertEquals(33, deserialized.scryptLogP);
assertEquals(CREDENTIAL_TYPE_PASSWORD_OR_PIN, deserialized.credentialType);
assertArrayEquals(PAYLOAD, deserialized.salt);
assertArrayEquals(PAYLOAD2, deserialized.passwordHandle);
@@ -574,6 +574,13 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests {
}
}
+ @Test
+ public void testHexEncodingIsUppercase() {
+ final byte[] raw = new byte[] { (byte)0xAB, (byte)0xCD, (byte)0xEF };
+ final byte[] expected = new byte[] { 'A', 'B', 'C', 'D', 'E', 'F' };
+ assertArrayEquals(expected, SyntheticPasswordManager.bytesToHex(raw));
+ }
+
// b/62213311
//TODO: add non-migration work profile case, and unify/un-unify transition.
//TODO: test token after user resets password
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 40ce956a0790..78f7f66ec7eb 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -54,6 +54,7 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.SensorManager;
import android.hardware.display.AmbientDisplayConfiguration;
@@ -75,6 +76,7 @@ import android.os.UserHandle;
import android.os.test.TestLooper;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
+import android.sysprop.PowerProperties;
import android.test.mock.MockContentResolver;
import android.view.Display;
import android.view.DisplayInfo;
@@ -85,6 +87,7 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.compat.PlatformCompat;
import com.android.server.lights.LightsManager;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.power.PowerManagerService.BatteryReceiver;
@@ -144,6 +147,7 @@ public class PowerManagerServiceTest {
@Mock private SystemPropertiesWrapper mSystemPropertiesMock;
@Mock private AppOpsManager mAppOpsManagerMock;
@Mock private LowPowerStandbyController mLowPowerStandbyControllerMock;
+ @Mock private PlatformCompat mPlatformCompat;
@Mock
private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
@@ -305,6 +309,11 @@ public class PowerManagerServiceTest {
AppOpsManager createAppOpsManager(Context context) {
return mAppOpsManagerMock;
}
+
+ @Override
+ PlatformCompat createPlatformCompat(Context context) {
+ return mPlatformCompat;
+ }
});
return mService;
}
@@ -478,6 +487,12 @@ public class PowerManagerServiceTest {
String packageName = "pkg.name";
when(mAppOpsManagerMock.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON,
Binder.getCallingUid(), packageName)).thenReturn(MODE_ALLOWED);
+ when(mPlatformCompat.isChangeEnabledByPackageName(
+ eq(PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION), anyString(),
+ anyInt())).thenReturn(true);
+ when(mContextSpy.checkCallingOrSelfPermission(
+ android.Manifest.permission.TURN_SCREEN_ON)).thenReturn(
+ PackageManager.PERMISSION_GRANTED);
// First, ensure that a normal full wake lock does not cause a wakeup
int flags = PowerManager.FULL_WAKE_LOCK;
@@ -499,6 +514,23 @@ public class PowerManagerServiceTest {
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+
+ // Verify that on older platforms only the appOp is necessary and the permission isn't
+ // checked
+ when(mPlatformCompat.isChangeEnabledByPackageName(
+ eq(PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION), anyString(),
+ anyInt())).thenReturn(false);
+ when(mContextSpy.checkCallingOrSelfPermission(
+ android.Manifest.permission.TURN_SCREEN_ON)).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+ forceSleep();
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+ flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
}
@Test
@@ -518,7 +550,32 @@ public class PowerManagerServiceTest {
int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
- assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+ if (PowerProperties.permissionless_turn_screen_on().orElse(true)) {
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ } else {
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+ }
+ mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+
+ when(mAppOpsManagerMock.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON,
+ Binder.getCallingUid(), packageName)).thenReturn(MODE_ALLOWED);
+ when(mPlatformCompat.isChangeEnabledByPackageName(
+ eq(PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION), anyString(),
+ anyInt())).thenReturn(true);
+ when(mContextSpy.checkCallingOrSelfPermission(
+ android.Manifest.permission.TURN_SCREEN_ON)).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+
+ // Verify that the flag has no effect when OP_TURN_SCREEN_ON is allowed but
+ // android.permission.TURN_SCREEN_ON is denied
+ flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+ if (PowerProperties.permissionless_turn_screen_on().orElse(true)) {
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ } else {
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+ }
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index fd1536c5c0f1..4550b56f6fd0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1622,7 +1622,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
+ // We need the package name to be something that's not "android" so there aren't any
+ // existing rules under that package.
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
assertNotNull(id);
}
try {
@@ -1632,12 +1634,41 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
}
+ }
+ @Test
+ public void testAddAutomaticZenRule_beyondSystemLimit_differentComponents() {
+ // Make sure the system limit is enforced per-package even with different component provider
+ // names.
+ for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
+ ScheduleInfo si = new ScheduleInfo();
+ si.startHour = i;
+ AutomaticZenRule zenRule = new AutomaticZenRule("name" + i,
+ null,
+ new ComponentName("android", "ScheduleConditionProvider" + i),
+ ZenModeConfig.toScheduleConditionId(si),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ assertNotNull(id);
+ }
+ try {
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName("android", "ScheduleConditionProviderFinal"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ fail("allowed too many rules to be created");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index cfeaf850da03..0c3b270518cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -29,6 +29,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
@@ -182,6 +184,10 @@ public class ActivityRecordTests extends WindowTestsBase {
private final String mPackageName = getInstrumentation().getTargetContext().getPackageName();
+ private static final int ORIENTATION_CONFIG_CHANGES =
+ CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT | CONFIG_SCREEN_SIZE
+ | CONFIG_SMALLEST_SCREEN_SIZE;
+
@Before
public void setUp() throws Exception {
setBooted(mAtm);
@@ -487,7 +493,7 @@ public class ActivityRecordTests extends WindowTestsBase {
public void testSetRequestedOrientationUpdatesConfiguration() throws Exception {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
- .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
+ .setConfigChanges(ORIENTATION_CONFIG_CHANGES)
.build();
activity.setState(RESUMED, "Testing");
@@ -710,7 +716,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.setLaunchTaskBehind(true)
- .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
+ .setConfigChanges(ORIENTATION_CONFIG_CHANGES)
.build();
final Task task = activity.getTask();
activity.setState(STOPPED, "Testing");
@@ -779,7 +785,7 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
}
}, 0, 0));
activity.updateOptionsLocked(opts);
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/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d737963f80e7..40e266c71328 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1699,6 +1699,13 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(displayContent.mPinnedTaskController.isFreezingTaskConfig(pinnedTask));
assertEquals(pinnedActivity.getConfiguration().orientation,
displayContent.getConfiguration().orientation);
+
+ // No need to apply rotation if the display ignores orientation request.
+ doCallRealMethod().when(displayContent).rotationForActivityInDifferentOrientation(any());
+ pinnedActivity.mOrientation = SCREEN_ORIENTATION_LANDSCAPE;
+ displayContent.setIgnoreOrientationRequest(true);
+ assertEquals(WindowConfiguration.ROTATION_UNDEFINED,
+ displayContent.rotationForActivityInDifferentOrientation(pinnedActivity));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/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/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 2a9fcb9d070b..7f09606d1c3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -762,12 +762,20 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
Task actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED,
ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */,
0 /* launchFlags */, candidateTask);
- assertSame(rootTask, actualRootTask.getRootTask());
+ assertSame(rootTask, actualRootTask);
// Verify the launch root task without candidate task
actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED,
ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */,
0 /* launchFlags */);
- assertSame(adjacentRootTask, actualRootTask.getRootTask());
+ assertSame(adjacentRootTask, actualRootTask);
+
+ final Task pinnedTask = createTask(
+ mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ // Verify not adjusting launch target for pinned candidate task
+ actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED,
+ ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */,
+ 0 /* launchFlags */, pinnedTask /* candidateTask */);
+ assertNull(actualRootTask);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 228cb65aab38..5f3096356bc5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -475,5 +475,13 @@ public class TaskFragmentTest extends WindowTestsBase {
assertFalse(activity0.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(activity1.isLetterboxedForFixedOrientationAndAspectRatio());
assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
+
+ tf0.setResumedActivity(activity0, "test");
+ tf1.setResumedActivity(activity1, "test");
+ mDisplayContent.mFocusedApp = activity1;
+
+ // Making the activity0 be the focused activity and ensure the focused app is updated.
+ activity0.moveFocusableActivityToTop("test");
+ assertEquals(activity0, mDisplayContent.mFocusedApp);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 5743922d0428..1715a295ded3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -979,7 +979,7 @@ public class WindowContainerTests extends WindowTestsBase {
}
@Override
- public void onAnimationCancelled() {
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
}
}, 0, 0, false);
adapter.setCallingPidUid(123, 456);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index e09a94f3bcf9..1a64f5e3a356 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -61,6 +61,7 @@ import android.view.InsetsState;
import android.view.InsetsVisibilities;
import android.view.View;
import android.view.WindowManager;
+import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
@@ -317,4 +318,76 @@ public class WindowManagerServiceTests extends WindowTestsBase {
verify(mWm.mInputManager).setInTouchMode(
!currentTouchMode, callingPid, callingUid, /* hasPermission= */ false);
}
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() {
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null);
+ assertThat(wct).isNull();
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() {
+ Binder cookie = new Binder("test cookie");
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
+ assertThat(wct).isNull();
+
+ final ActivityRecord testActivity = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
+ .build();
+
+ wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
+ assertThat(wct).isNull();
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() {
+ final Binder cookie = new Binder("ginger cookie");
+ final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
+ setupActivityWithLaunchCookie(cookie, launchRootTask);
+
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
+ assertThat(wct).isEqualTo(launchRootTask);
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() {
+ final Binder cookie1 = new Binder("ginger cookie");
+ final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class);
+ setupActivityWithLaunchCookie(cookie1, launchRootTask1);
+
+ setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
+ mock(WindowContainerToken.class));
+
+ setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
+ mock(WindowContainerToken.class));
+
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie1);
+ assertThat(wct).isEqualTo(launchRootTask1);
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() {
+ setupActivityWithLaunchCookie(new Binder("ginger cookie"),
+ mock(WindowContainerToken.class));
+
+ setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
+ mock(WindowContainerToken.class));
+
+ setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
+ mock(WindowContainerToken.class));
+
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(
+ new Binder("some other cookie"));
+ assertThat(wct).isNull();
+ }
+
+ private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
+ final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
+ when(remoteToken.toWindowContainerToken()).thenReturn(wct);
+ final ActivityRecord testActivity = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
+ .build();
+ testActivity.mLaunchCookie = launchCookie;
+ testActivity.getTask().mRemoteToken = remoteToken;
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index bb2e9639881a..1e0e8366c385 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -136,6 +136,7 @@ final class HotwordDetectionConnection {
// The error codes are used for onError callback
private static final int HOTWORD_DETECTION_SERVICE_DIED = -1;
private static final int CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION = -2;
+ private static final int CALLBACK_DETECT_TIMEOUT = -3;
// Hotword metrics
private static final int METRICS_INIT_UNKNOWN_TIMEOUT =
@@ -545,66 +546,7 @@ final class HotwordDetectionConnection {
if (DEBUG) {
Slog.d(TAG, "triggerHardwareRecognitionEventForTestLocked");
}
- detectFromDspSourceForTest(event, callback);
- }
-
- private void detectFromDspSourceForTest(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
- IHotwordRecognitionStatusCallback externalCallback) {
- Slog.v(TAG, "detectFromDspSourceForTest");
- IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
- @Override
- public void onDetected(HotwordDetectedResult result) throws RemoteException {
- Slog.v(TAG, "onDetected");
- synchronized (mLock) {
- if (!mValidatingDspTrigger) {
- Slog.i(TAG, "Ignored hotword detected since trigger has been handled");
- return;
- }
- mValidatingDspTrigger = false;
- try {
- enforcePermissionsForDataDelivery();
- enforceExtraKeyphraseIdNotLeaked(result, recognitionEvent);
- } catch (SecurityException e) {
- externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
- return;
- }
- externalCallback.onKeyphraseDetected(recognitionEvent, result);
- if (result != null) {
- Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
- + " bits from hotword trusted process");
- if (mDebugHotwordLogging) {
- Slog.i(TAG, "Egressed detected result: " + result);
- }
- }
- }
- }
-
- @Override
- public void onRejected(HotwordRejectedResult result) throws RemoteException {
- Slog.v(TAG, "onRejected");
- synchronized (mLock) {
- if (mValidatingDspTrigger) {
- mValidatingDspTrigger = false;
- externalCallback.onRejected(result);
- if (mDebugHotwordLogging && result != null) {
- Slog.i(TAG, "Egressed rejected result: " + result);
- }
- } else {
- Slog.i(TAG, "Ignored hotword rejected since trigger has been handled");
- }
- }
- }
- };
-
- synchronized (mLock) {
- mValidatingDspTrigger = true;
- mRemoteHotwordDetectionService.run(
- service -> service.detectFromDspSource(
- recognitionEvent,
- recognitionEvent.getCaptureFormat(),
- VALIDATION_TIMEOUT_MILLIS,
- internalCallback));
- }
+ detectFromDspSource(event, callback);
}
private void detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
@@ -613,6 +555,7 @@ final class HotwordDetectionConnection {
Slog.d(TAG, "detectFromDspSource");
}
+ AtomicBoolean timeoutDetected = new AtomicBoolean(false);
// TODO: consider making this a non-anonymous class.
IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
@Override
@@ -621,12 +564,12 @@ final class HotwordDetectionConnection {
Slog.d(TAG, "onDetected");
}
synchronized (mLock) {
- // TODO: If the dsp trigger comes in after the timeout, we will log both events.
- // Because we don't enforce the timeout yet. We should add some synchronizations
- // within the runnable to prevent the race condition to log both events.
if (mCancellationKeyPhraseDetectionFuture != null) {
mCancellationKeyPhraseDetectionFuture.cancel(true);
}
+ if (timeoutDetected.get()) {
+ return;
+ }
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
@@ -668,6 +611,9 @@ final class HotwordDetectionConnection {
if (mCancellationKeyPhraseDetectionFuture != null) {
mCancellationKeyPhraseDetectionFuture.cancel(true);
}
+ if (timeoutDetected.get()) {
+ return;
+ }
HotwordMetricsLogger.writeKeyphraseTriggerEvent(
mDetectorType,
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
@@ -689,21 +635,29 @@ final class HotwordDetectionConnection {
synchronized (mLock) {
mValidatingDspTrigger = true;
- mRemoteHotwordDetectionService.run(
- service -> {
- // TODO: avoid allocate every time
- mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule(
- () -> HotwordMetricsLogger
- .writeKeyphraseTriggerEvent(mDetectorType,
- HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT),
- VALIDATION_TIMEOUT_MILLIS,
- TimeUnit.MILLISECONDS);
- service.detectFromDspSource(
- recognitionEvent,
- recognitionEvent.getCaptureFormat(),
- VALIDATION_TIMEOUT_MILLIS,
- internalCallback);
- });
+ mRemoteHotwordDetectionService.run(service -> {
+ mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule(
+ () -> {
+ // TODO: avoid allocate every time
+ timeoutDetected.set(true);
+ Slog.w(TAG, "Timed out on #detectFromDspSource");
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT);
+ try {
+ externalCallback.onError(CALLBACK_DETECT_TIMEOUT);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to report onError status: ", e);
+ }
+ },
+ VALIDATION_TIMEOUT_MILLIS,
+ TimeUnit.MILLISECONDS);
+ service.detectFromDspSource(
+ recognitionEvent,
+ recognitionEvent.getCaptureFormat(),
+ VALIDATION_TIMEOUT_MILLIS,
+ internalCallback);
+ });
}
}
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/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp
index 62e16a5b83de..f026bea80470 100644
--- a/tests/ApkVerityTest/Android.bp
+++ b/tests/ApkVerityTest/Android.bp
@@ -37,8 +37,8 @@ java_test_host {
"general-tests",
"vts",
],
- target_required: [
- "block_device_writer_module",
+ data_device_bins_both: [
+ "block_device_writer",
],
data: [
":ApkVerityTestCertDer",
diff --git a/tests/ApkVerityTest/AndroidTest.xml b/tests/ApkVerityTest/AndroidTest.xml
index 3c8e1ed99604..2a0a2c76e50f 100644
--- a/tests/ApkVerityTest/AndroidTest.xml
+++ b/tests/ApkVerityTest/AndroidTest.xml
@@ -31,10 +31,18 @@
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
- <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
<option name="push" value="ApkVerityTestCert.der->/data/local/tmp/ApkVerityTestCert.der" />
</target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <!-- The build system produces both 32 and 64 bit variants with bitness suffix. Let
+ FilePusher find the filename with bitness and push to a remote name without bitness.
+ -->
+ <option name="append-bitness" value="true" />
+ <option name="cleanup" value="true" />
+ <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
+ </target_preparer>
+
<!-- Skip on HWASan. TODO(b/232288278): Re-enable -->
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.SkipHWASanModuleController" />
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp
index fdfa41fd4ca9..0002447d17f2 100644
--- a/tests/ApkVerityTest/block_device_writer/Android.bp
+++ b/tests/ApkVerityTest/block_device_writer/Android.bp
@@ -24,12 +24,7 @@ package {
}
cc_test {
- // Depending on how the test runs, the executable may be uploaded to different location.
- // Before the bug in the file pusher is fixed, workaround by making the name unique.
- // See b/124718249#comment12.
- name: "block_device_writer_module",
- stem: "block_device_writer",
-
+ name: "block_device_writer",
srcs: ["block_device_writer.cpp"],
cflags: [
"-D_FILE_OFFSET_BITS=64",
@@ -42,20 +37,13 @@ cc_test {
"libbase",
"libutils",
],
- // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when
- // the uploader does not pick up the executable from correct output location. The following
- // workaround allows the test to:
- // * upload the 32-bit exectuable for both 32 and 64 bits devices to use
- // * refer to the same executable name in Java
- // * no need to force the Java test to be archiecture specific.
- //
- // See b/145573317 for details.
+ compile_multilib: "both",
multilib: {
lib32: {
- suffix: "",
+ suffix: "32",
},
lib64: {
- suffix: "64", // not really used
+ suffix: "64",
},
},
diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
index 5c2c15b22bb0..9be02ec3be86 100644
--- a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
+++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java
@@ -32,11 +32,12 @@ import java.util.ArrayList;
* <p>To use this class, please push block_device_writer binary to /data/local/tmp.
* 1. In Android.bp, add:
* <pre>
- * target_required: ["block_device_writer_module"],
+ * data_device_bins_both: ["block_device_writer"],
* </pre>
* 2. In AndroidText.xml, add:
* <pre>
- * <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ * <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ * <option name="append-bitness" value="true" />
* <option name="push" value="block_device_writer->/data/local/tmp/block_device_writer" />
* </target_preparer>
* </pre>
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 315c40ffa9ba..896c7309cf4c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -111,8 +111,8 @@ fun FlickerTestParameter.statusBarLayerIsVisible() {
*/
fun FlickerTestParameter.navBarLayerPositionStart() {
assertLayersStart {
- val display = this.entry.displays.minByOrNull { it.id }
- ?: throw RuntimeException("There is no display!")
+ val display = this.entry.displays.firstOrNull { !it.isVirtual }
+ ?: error("There is no display!")
this.visibleRegion(FlickerComponentName.NAV_BAR)
.coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
}
@@ -180,7 +180,8 @@ fun FlickerTestParameter.statusBarLayerRotatesScales() {
* the visibleRegion of the given app component exactly
*/
fun FlickerTestParameter.snapshotStartingWindowLayerCoversExactlyOnApp(
- component: FlickerComponentName) {
+ component: FlickerComponentName
+) {
assertLayers {
invoke("snapshotStartingWindowLayerCoversExactlyOnApp") {
val snapshotLayers = it.subjects.filter { subject ->
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 6c5f6bace49d..d0f1dfddaed0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -72,13 +72,15 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio
get() = {
super.transition(this)
transitions {
- device.pressBack()
- wmHelper.waitForHomeActivityVisible()
+ tapl.pressBack()
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
}
}
/** {@inheritDoc} */
- @FlakyTest
+ @FlakyTest(bugId = 206753786)
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
@@ -118,4 +120,4 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio
.getConfigNonRotationTests(repetitions = 3)
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 7451a523f260..b178e8c7eced 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.close
-import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -69,38 +68,30 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio
get() = {
super.transition(this)
transitions {
+ // Can't use TAPL at the moment because of rotation test issues
+ // When pressing home, TAPL expects the orientation to remain constant
+ // However, when closing a landscape app back to a portrait-only launcher
+ // this causes an error in verifyActiveContainer();
device.pressHome()
- wmHelper.waitForHomeActivityVisible()
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
}
}
/** {@inheritDoc} */
- @FlakyTest
+ @FlakyTest(bugId = 206753786)
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
/** {@inheritDoc} */
- @FlakyTest(bugId = 227430489)
+ @FlakyTest(bugId = 206753786)
@Test
override fun statusBarLayerRotatesScales() {
super.statusBarLayerRotatesScales()
}
/** {@inheritDoc} */
- @Presubmit
- @Test
- override fun launcherLayerReplacesApp() {
- super.launcherLayerReplacesApp()
- }
-
- /** {@inheritDoc} */
- @Presubmit
- @Test
- override fun entireScreenCovered() {
- super.entireScreenCovered()
- }
-
- /** {@inheritDoc} */
@FlakyTest(bugId = 229762973)
@Test
override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
@@ -120,4 +111,4 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio
.getConfigNonRotationTests(repetitions = 3)
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index aaa2db768792..ae002c9458f5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -19,21 +19,22 @@ package com.android.server.wm.flicker.close
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.entireScreenCovered
+import com.android.server.wm.flicker.replacesLayer
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.flicker.replacesLayer
+import com.android.server.wm.traces.common.FlickerComponentName.Companion.LAUNCHER
import org.junit.Test
/**
@@ -42,12 +43,16 @@ import org.junit.Test
abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter) {
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
+ protected val tapl = LauncherInstrumentation()
/**
* Specification of the test transition to execute
*/
protected open val transition: FlickerBuilder.() -> Unit = {
setup {
+ test {
+ tapl.setExpectedRotation(testSpec.startRotation)
+ }
eachRun {
testApp.launchViaIntent(wmHelper)
this.setRotation(testSpec.startRotation)
@@ -154,7 +159,7 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter)
/**
* Checks that [testApp] is the top visible app window at the start of the transition and
- * that it is replaced by [LAUNCHER_COMPONENT] during the transition
+ * that it is replaced by [LAUNCHER] during the transition
*/
@Presubmit
@Test
@@ -162,30 +167,30 @@ abstract class CloseAppTransition(protected val testSpec: FlickerTestParameter)
testSpec.assertWm {
this.isAppWindowOnTop(testApp.component)
.then()
- .isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .isAppWindowOnTop(LAUNCHER)
}
}
/**
- * Checks that [LAUNCHER_COMPONENT] is invisible at the start of the transition and that
+ * Checks that [LAUNCHER] is invisible at the start of the transition and that
* it becomes visible during the transition
*/
@Presubmit
@Test
open fun launcherWindowBecomesVisible() {
testSpec.assertWm {
- this.isAppWindowNotOnTop(LAUNCHER_COMPONENT)
+ this.isAppWindowNotOnTop(LAUNCHER)
.then()
- .isAppWindowOnTop(LAUNCHER_COMPONENT)
+ .isAppWindowOnTop(LAUNCHER)
}
}
/**
- * Checks that [LAUNCHER_COMPONENT] layer becomes visible when [testApp] becomes invisible
+ * Checks that [LAUNCHER] layer becomes visible when [testApp] becomes invisible
*/
@Presubmit
@Test
open fun launcherLayerReplacesApp() {
- testSpec.replacesLayer(testApp.component, LAUNCHER_COMPONENT)
+ testSpec.replacesLayer(testApp.component, LAUNCHER)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index aacc17a49a24..cd184ac10c22 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -16,19 +16,16 @@
package com.android.server.wm.flicker.helpers
+import android.app.Instrumentation
import android.view.WindowInsets.Type.ime
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
-
-import android.app.Instrumentation
import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-
import java.util.regex.Pattern
class ImeAppAutoFocusHelper @JvmOverloads constructor(
@@ -39,12 +36,9 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor(
component: FlickerComponentName =
ActivityOptions.IME_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME.toFlickerComponent()
) : ImeAppHelper(instr, launcherName, component) {
- override fun openIME(
- device: UiDevice,
- wmHelper: WindowManagerStateHelper?
- ) {
+ override fun openIME(wmHelper: WindowManagerStateHelper) {
// do nothing (the app is focused automatically)
- waitIMEShown(device, wmHelper)
+ waitIMEShown(wmHelper)
}
override fun launchViaIntent(
@@ -54,7 +48,7 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor(
stringExtras: Map<String, String>
) {
super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras)
- waitIMEShown(uiDevice, wmHelper)
+ waitIMEShown(wmHelper)
}
override fun open() {
@@ -70,15 +64,15 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor(
val button = uiDevice.wait(Until.findObject(By.res(getPackage(),
"start_dialog_themed_activity_btn")), FIND_TIMEOUT)
- require(button != null) {
+ requireNotNull(button) {
"Button not found, this usually happens when the device " +
"was left in an unknown state (e.g. Screen turned off)"
}
button.click()
- wmHelper.waitForAppTransitionIdle()
- wmHelper.waitForFullScreenApp(
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(
ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent())
- mInstrumentation.waitForIdleSync()
+ .waitForAndVerify()
}
fun dismissDialog(wmHelper: WindowManagerStateHelper) {
val dialog = uiDevice.wait(
@@ -87,14 +81,16 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor(
// Pressing back key to dismiss the dialog
if (dialog != null) {
uiDevice.pressBack()
- wmHelper.waitForAppTransitionIdle()
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .waitForAndVerify()
}
}
fun getInsetsVisibleFromDialog(type: Int): Boolean {
- var insetsVisibilityTextView = uiDevice.wait(
+ val insetsVisibilityTextView = uiDevice.wait(
Until.findObject(By.res("android:id/text1")), FIND_TIMEOUT)
if (insetsVisibilityTextView != null) {
- var visibility = insetsVisibilityTextView.text.toString()
+ val visibility = insetsVisibilityTextView.text.toString()
val matcher = when (type) {
ime() -> {
Pattern.compile("IME\\: (VISIBLE|INVISIBLE)").matcher(visibility)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
index 5bd365c7eefd..8ac9a75aff94 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppHelper.kt
@@ -20,7 +20,6 @@ import android.app.Instrumentation
import android.support.test.launcherhelper.ILauncherStrategy
import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.FlickerComponentName
@@ -39,64 +38,49 @@ open class ImeAppHelper @JvmOverloads constructor(
/**
* Opens the IME and wait for it to be displayed
*
- * @param device UIDevice instance to interact with the device
* @param wmHelper Helper used to wait for WindowManager states
*/
- @JvmOverloads
- open fun openIME(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) {
- val editText = device.wait(
+ open fun openIME(wmHelper: WindowManagerStateHelper) {
+ val editText = uiDevice.wait(
Until.findObject(By.res(getPackage(), "plain_text_input")),
FIND_TIMEOUT)
- require(editText != null) {
+ requireNotNull(editText) {
"Text field not found, this usually happens when the device " +
"was left in an unknown state (e.g. in split screen)"
}
editText.click()
- waitIMEShown(device, wmHelper)
+ waitIMEShown(wmHelper)
}
- protected fun waitIMEShown(
- device: UiDevice,
- wmHelper: WindowManagerStateHelper? = null
- ) {
- if (wmHelper == null) {
- device.waitForIdle()
- } else {
- wmHelper.waitImeShown()
- }
+ protected fun waitIMEShown(wmHelper: WindowManagerStateHelper) {
+ wmHelper.StateSyncBuilder()
+ .withImeShown()
+ .waitForAndVerify()
}
/**
* Opens the IME and wait for it to be gone
*
- * @param device UIDevice instance to interact with the device
* @param wmHelper Helper used to wait for WindowManager states
*/
- @JvmOverloads
- open fun closeIME(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) {
- device.pressBack()
- // Using only the AccessibilityInfo it is not possible to identify if the IME is active
- if (wmHelper == null) {
- device.waitForIdle()
- } else {
- wmHelper.waitImeGone()
- }
+ open fun closeIME(wmHelper: WindowManagerStateHelper) {
+ uiDevice.pressBack()
+ wmHelper.StateSyncBuilder()
+ .withImeGone()
+ .waitForAndVerify()
}
- @JvmOverloads
- open fun finishActivity(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) {
- val finishButton = device.wait(
+ open fun finishActivity(wmHelper: WindowManagerStateHelper) {
+ val finishButton = uiDevice.wait(
Until.findObject(By.res(getPackage(), "finish_activity_btn")),
FIND_TIMEOUT)
- require(finishButton != null) {
- "Finish activity button not found, probably IME activity is not on the screen ?"
+ requireNotNull(finishButton) {
+ "Finish activity button not found, probably IME activity is not on the screen?"
}
finishButton.click()
- if (wmHelper == null) {
- device.waitForIdle()
- } else {
- wmHelper.waitForActivityRemoved(component)
- }
+ wmHelper.StateSyncBuilder()
+ .withActivityRemoved(component)
+ .waitForAndVerify()
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
index 172c4330c3c6..438aeeb2cc74 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeEditorPopupDialogAppHelper.kt
@@ -18,7 +18,6 @@ package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.FlickerComponentName
@@ -33,18 +32,15 @@ class ImeEditorPopupDialogAppHelper @JvmOverloads constructor(
component: FlickerComponentName =
ActivityOptions.EDITOR_POPUP_DIALOG_ACTIVITY_COMPONENT_NAME.toFlickerComponent()
) : ImeAppHelper(instr, launcherName, component) {
- override fun openIME(
- device: UiDevice,
- wmHelper: WindowManagerStateHelper?
- ) {
- val editText = device.wait(Until.findObject(By.text("focused editText")), FIND_TIMEOUT)
+ override fun openIME(wmHelper: WindowManagerStateHelper) {
+ val editText = uiDevice.wait(Until.findObject(By.text("focused editText")), FIND_TIMEOUT)
require(editText != null) {
"Text field not found, this usually happens when the device " +
"was left in an unknown state (e.g. in split screen)"
}
editText.click()
- waitIMEShown(device, wmHelper)
+ waitIMEShown(wmHelper)
}
fun dismissDialog(wmHelper: WindowManagerStateHelper) {
@@ -54,7 +50,9 @@ class ImeEditorPopupDialogAppHelper @JvmOverloads constructor(
// Pressing back key to dismiss the dialog
if (dismissButton != null) {
dismissButton.click()
- wmHelper.waitForAppTransitionIdle()
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .waitForAndVerify()
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
index be68704fc32d..1a2543cb8f97 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NewTasksAppHelper.kt
@@ -41,12 +41,13 @@ class NewTasksAppHelper @JvmOverloads constructor(
Until.findObject(By.res(getPackage(), "launch_new_task")),
FIND_TIMEOUT)
- require(button != null) {
+ requireNotNull(button) {
"Button not found, this usually happens when the device " +
"was left in an unknown state (e.g. in split screen)"
}
button.click()
- wmHelper.waitForAppTransitionIdle()
- wmHelper.waitForFullScreenApp(component)
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(component)
+ .waitForAndVerify()
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
index 4e360f98723e..00b208bc8c23 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/NotificationAppHelper.kt
@@ -20,7 +20,6 @@ import android.app.Instrumentation
import android.support.test.launcherhelper.ILauncherStrategy
import android.support.test.launcherhelper.LauncherStrategyFactory
import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.FlickerComponentName
@@ -36,8 +35,8 @@ class NotificationAppHelper @JvmOverloads constructor(
.getInstance(instr)
.launcherStrategy
) : StandardAppHelper(instr, launcherName, component, launcherStrategy) {
- fun postNotification(device: UiDevice, wmHelper: WindowManagerStateHelper) {
- val button = device.wait(
+ fun postNotification(wmHelper: WindowManagerStateHelper) {
+ val button = uiDevice.wait(
Until.findObject(By.res(getPackage(), "post_notification")),
FIND_TIMEOUT)
@@ -47,7 +46,7 @@ class NotificationAppHelper @JvmOverloads constructor(
}
button.click()
- device.wait(Until.findObject(By.text("Flicker Test Notification")), FIND_TIMEOUT)
+ uiDevice.wait(Until.findObject(By.text("Flicker Test Notification")), FIND_TIMEOUT)
?: error("Flicker Notification not found")
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
index a135e0af067b..8519da14aa2d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TwoActivitiesAppHelper.kt
@@ -19,13 +19,11 @@ package com.android.server.wm.flicker.helpers
import android.app.Instrumentation
import android.support.test.launcherhelper.ILauncherStrategy
import android.support.test.launcherhelper.LauncherStrategyFactory
-import android.view.Display
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
@@ -46,21 +44,19 @@ class TwoActivitiesAppHelper @JvmOverloads constructor(
val launchActivityButton = By.res(getPackage(), LAUNCH_SECOND_ACTIVITY)
val button = device.wait(Until.findObject(launchActivityButton), FIND_TIMEOUT)
- require(button != null) {
+ requireNotNull(button) {
"Button not found, this usually happens when the device " +
"was left in an unknown state (e.g. in split screen)"
}
button.click()
device.wait(Until.gone(launchActivityButton), FIND_TIMEOUT)
- wmHelper.waitFor(
- WindowManagerStateHelper.isAppFullScreen(secondActivityComponent),
- WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY),
- WindowManagerConditionsFactory.hasLayersAnimating().negate()
- )
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(secondActivityComponent)
+ .waitForAndVerify()
}
companion object {
private const val LAUNCH_SECOND_ACTIVITY = "launch_second_activity"
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 843ef232fdeb..67d2067d1b06 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -17,10 +17,10 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -71,7 +71,6 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter
setup {
eachRun {
testApp.launchViaIntent(wmHelper)
- testApp.openIME(device, wmHelper)
}
}
teardown {
@@ -80,7 +79,7 @@ class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter
}
}
transitions {
- testApp.closeIME(device, wmHelper)
+ testApp.closeIME(wmHelper)
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
index 13a49a1cb47c..dcb5dad3e931 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt
@@ -17,10 +17,10 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -29,11 +29,11 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -71,7 +71,6 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete
setup {
eachRun {
testApp.launchViaIntent(wmHelper)
- testApp.openIME(device, wmHelper)
}
}
teardown {
@@ -81,8 +80,10 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete
}
transitions {
device.pressHome()
- wmHelper.waitForHomeActivityVisible()
- wmHelper.waitImeGone()
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .withImeGone()
+ .waitForAndVerify()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
index 2e29b3e314ca..b979f3ec3442 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
@@ -23,9 +23,9 @@ import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
-import com.android.server.wm.flicker.FlickerTestParameterFactory
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.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeEditorPopupDialogAppHelper
@@ -54,18 +54,22 @@ class CloseImeEditorPopupDialogTest(private val testSpec: FlickerTestParameter)
setup {
eachRun {
imeTestApp.launchViaIntent(wmHelper)
- imeTestApp.openIME(device, wmHelper)
+ imeTestApp.openIME(wmHelper)
}
}
transitions {
imeTestApp.dismissDialog(wmHelper)
- wmHelper.waitImeGone()
+ wmHelper.StateSyncBuilder()
+ .withImeGone()
+ .waitForAndVerify()
}
teardown {
eachRun {
device.pressHome()
- wmHelper.waitForHomeActivityVisible()
- imeTestApp.exit()
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ imeTestApp.exit(wmHelper)
}
}
}
@@ -133,4 +137,4 @@ class CloseImeEditorPopupDialogTest(private val testSpec: FlickerTestParameter)
)
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index 1ce1e1fb05f6..928bb5346671 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -17,8 +17,8 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
-import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -27,11 +27,11 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
@@ -64,7 +64,7 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) {
testApp.launchViaIntent(wmHelper)
}
eachRun {
- testApp.openIME(device, wmHelper)
+ testApp.openIME(wmHelper)
}
}
teardown {
@@ -73,7 +73,7 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) {
}
}
transitions {
- testApp.closeIME(device, wmHelper)
+ testApp.closeIME(wmHelper)
}
}
}
@@ -163,4 +163,4 @@ class CloseImeWindowToAppTest(private val testSpec: FlickerTestParameter) {
.getConfigNonRotationTests(repetitions = 3)
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index c77ea12a5850..3ff809d38330 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -17,10 +17,10 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -29,11 +29,11 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
@@ -42,6 +42,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
+
/**
* Test IME window closing to home transitions.
* To run this test: `atest FlickerTests:CloseImeWindowToHomeTest`
@@ -61,21 +62,19 @@ class CloseImeWindowToHomeTest(private val testSpec: FlickerTestParameter) {
setup {
eachRun {
testApp.launchViaIntent(wmHelper)
- testApp.openIME(device, wmHelper)
+ testApp.openIME(wmHelper)
}
}
transitions {
device.pressHome()
- wmHelper.waitForHomeActivityVisible()
- wmHelper.waitImeGone()
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .withImeGone()
+ .waitForAndVerify()
}
teardown {
- eachRun {
- device.pressHome()
- wmHelper.waitForHomeActivityVisible()
- }
test {
- testApp.exit()
+ testApp.exit(wmHelper)
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
index b298df2f8ffe..d1761fca899b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt
@@ -16,15 +16,14 @@
package com.android.server.wm.flicker.ime
-import android.view.WindowInsets.Type.ime
-import android.view.WindowInsets.Type.navigationBars
-import android.view.WindowInsets.Type.statusBars
-
import android.app.Instrumentation
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
+import android.view.WindowInsets.Type.ime
+import android.view.WindowInsets.Type.navigationBars
+import android.view.WindowInsets.Type.statusBars
import android.view.WindowManagerPolicyConstants
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -34,13 +33,13 @@ import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.traces.common.FlickerComponentName
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
/**
* Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity.
@@ -60,7 +59,9 @@ class LaunchAppShowImeAndDialogThemeAppTest(private val testSpec: FlickerTestPar
setup {
eachRun {
testApp.launchViaIntent(wmHelper)
- wmHelper.waitImeShown()
+ wmHelper.StateSyncBuilder()
+ .withImeShown()
+ .waitForAndVerify()
testApp.startDialogThemedActivity(wmHelper)
// Verify IME insets isn't visible on dialog since it's non-IME focusable window
assertFalse(testApp.getInsetsVisibleFromDialog(ime()))
@@ -70,7 +71,7 @@ class LaunchAppShowImeAndDialogThemeAppTest(private val testSpec: FlickerTestPar
}
teardown {
eachRun {
- testApp.exit()
+ testApp.exit(wmHelper)
}
}
transitions {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
index b897ca2a9c15..d393ace3254d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt
@@ -79,19 +79,21 @@ class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) {
return FlickerBuilder(instrumentation).apply {
setup {
eachRun {
- initializeApp.launchViaIntent()
+ initializeApp.launchViaIntent(wmHelper)
this.setRotation(testSpec.startRotation)
}
}
teardown {
eachRun {
- initializeApp.exit()
- testApp.exit()
+ initializeApp.exit(wmHelper)
+ testApp.exit(wmHelper)
}
}
transitions {
testApp.launchViaIntent(wmHelper)
- wmHelper.waitImeShown()
+ wmHelper.StateSyncBuilder()
+ .withImeShown()
+ .waitForAndVerify()
}
}
}
@@ -153,4 +155,4 @@ class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) {
)
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index 972918e28fa7..1854d6e5506e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -22,11 +22,19 @@ import android.view.Surface
import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.*
+import com.android.server.wm.flicker.FlickerBuilderProvider
+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.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.server.wm.flicker.navBarLayerIsVisible
+import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.statusBarLayerIsVisible
+import com.android.server.wm.flicker.statusBarWindowIsVisible
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -56,15 +64,15 @@ class OpenImeWindowAndCloseTest(private val testSpec: FlickerTestParameter) {
eachRun {
simpleApp.launchViaIntent(wmHelper)
testApp.launchViaIntent(wmHelper)
- testApp.openIME(device, wmHelper)
+ testApp.openIME(wmHelper)
}
}
transitions {
- testApp.finishActivity(device, wmHelper)
+ testApp.finishActivity(wmHelper)
}
teardown {
test {
- simpleApp.exit()
+ simpleApp.exit(wmHelper)
}
}
}
@@ -128,4 +136,4 @@ class OpenImeWindowAndCloseTest(private val testSpec: FlickerTestParameter) {
)
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
index fdaeacb5e8a2..4f26fc284198 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
@@ -17,12 +17,12 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.Surface
import android.view.WindowManagerPolicyConstants
-import android.platform.test.annotations.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -70,7 +70,9 @@ class OpenImeWindowFromFixedOrientationAppTest(private val testSpec: FlickerTest
eachRun {
// Swiping out the IME activity to home.
taplInstrumentation.goHome()
- wmHelper.waitForHomeActivityVisible()
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
}
}
transitions {
@@ -135,4 +137,4 @@ class OpenImeWindowFromFixedOrientationAppTest(private val testSpec: FlickerTest
)
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index 94a6639e75ac..dbec3643c39f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -17,10 +17,10 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
@@ -28,12 +28,12 @@ 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.Group2
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.ImeAppHelper
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
@@ -65,14 +65,14 @@ class OpenImeWindowTest(private val testSpec: FlickerTestParameter) {
}
}
transitions {
- testApp.openIME(device, wmHelper)
+ testApp.openIME(wmHelper)
}
teardown {
eachRun {
- testApp.closeIME(device, wmHelper)
+ testApp.closeIME(wmHelper)
}
test {
- testApp.exit()
+ testApp.exit(wmHelper)
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
index e899a750a5df..eedbd5d3dba8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowToOverViewTest.kt
@@ -17,12 +17,12 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.Surface
import android.view.WindowManagerPolicyConstants
-import android.platform.test.annotations.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -30,8 +30,8 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarLayerIsVisible
@@ -39,8 +39,8 @@ import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
-import org.junit.Assume.assumeTrue
import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -72,12 +72,17 @@ class OpenImeWindowToOverViewTest(private val testSpec: FlickerTestParameter) {
}
transitions {
device.pressRecentApps()
- waitForRecentsActivityVisible(wmHelper)
- waitNavStatusBarVisibility(wmHelper)
+ val builder = wmHelper.StateSyncBuilder()
+ .withRecentsActivityVisible()
+ waitNavStatusBarVisibility(builder)
+ builder.waitForAndVerify()
}
teardown {
test {
device.pressHome()
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
imeTestApp.exit(wmHelper)
}
}
@@ -97,12 +102,12 @@ class OpenImeWindowToOverViewTest(private val testSpec: FlickerTestParameter) {
*
* b/227189877
*/
- private fun waitNavStatusBarVisibility(wmHelper: WindowManagerStateHelper) {
+ private fun waitNavStatusBarVisibility(stateSync: WindowManagerStateHelper.StateSyncBuilder) {
when {
- testSpec.isLandscapeOrSeascapeAtStart && !testSpec.isGesturalNavigation ->
- wmHelper.waitFor(statusBarInvisible)
testSpec.isLandscapeOrSeascapeAtStart ->
- wmHelper.waitFor(statusBarInvisible, navBarInvisible)
+ stateSync.add(statusBarInvisible)
+ else ->
+ stateSync.withNavBarStatusBarVisible()
}
}
@@ -195,25 +200,6 @@ class OpenImeWindowToOverViewTest(private val testSpec: FlickerTestParameter) {
}
}
- private fun waitForRecentsActivityVisible(
- wmHelper: WindowManagerStateHelper
- ) {
- val waitMsg = "state of Recents activity to be visible"
- require(
- wmHelper.waitFor(waitMsg) {
- it.wmState.homeActivity?.let { act ->
- it.wmState.isActivityVisible(act.name)
- } == true ||
- it.wmState.recentsActivity?.let { act ->
- it.wmState.isActivityVisible(act.name)
- } == true
- }
- ) { "Recents activity should be visible" }
- wmHelper.waitForAppTransitionIdle()
- // Ensure WindowManagerService wait until all animations have completed
- instrumentation.uiAutomation.syncInputTransactions()
- }
-
companion object {
/**
* Creates the test configurations.
@@ -235,4 +221,4 @@ class OpenImeWindowToOverViewTest(private val testSpec: FlickerTestParameter) {
)
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
index 2b23d53e8a85..7d4724c48bbb 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt
@@ -17,33 +17,30 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
-import android.view.Display
import android.view.Surface
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
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.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group2
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.ConditionList
import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import com.android.server.wm.traces.common.FlickerComponentName.Companion.LAUNCHER
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Before
@@ -66,12 +63,6 @@ open class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
- private val waitConditionSetup = ConditionList(listOf(
- WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY),
- WindowManagerConditionsFactory.hasLayersAnimating().negate(),
- WindowManagerConditionsFactory.isHomeActivityVisible()
- ))
-
@Before
open fun before() {
assumeFalse(isShellTransitionsEnabled)
@@ -83,21 +74,25 @@ open class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) {
setup {
test {
testApp.launchViaIntent(wmHelper)
- testApp.openIME(device, wmHelper)
+ testApp.openIME(wmHelper)
}
eachRun {
- device.pressRecentApps()
- wmHelper.waitFor(waitConditionSetup)
this.setRotation(testSpec.startRotation)
+ device.pressRecentApps()
+ wmHelper.StateSyncBuilder()
+ .withRecentsActivityVisible()
+ .waitForAndVerify()
}
}
transitions {
device.reopenAppFromOverview(wmHelper)
- wmHelper.waitImeShown()
+ wmHelper.StateSyncBuilder()
+ .withImeShown()
+ .waitForAndVerify()
}
teardown {
test {
- testApp.exit()
+ testApp.exit(wmHelper)
}
}
}
@@ -128,9 +123,9 @@ open class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) {
@Test
fun launcherWindowBecomesInvisible() {
testSpec.assertWm {
- this.isAppWindowVisible(LAUNCHER_COMPONENT)
+ this.isAppWindowVisible(LAUNCHER)
.then()
- .isAppWindowInvisible(LAUNCHER_COMPONENT)
+ .isAppWindowInvisible(LAUNCHER)
}
}
@@ -208,7 +203,7 @@ open class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) {
@Test
fun appLayerReplacesLauncher() {
testSpec.assertLayers {
- this.isVisible(LAUNCHER_COMPONENT)
+ this.isVisible(FlickerComponentName.LAUNCHER)
.then()
.isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
.then()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
index 4b268a871fa0..d1188467cc01 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/SwitchImeWindowsFromGestureNavTest.kt
@@ -18,17 +18,16 @@ package com.android.server.wm.flicker.ime
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
-import android.view.Display
import android.view.Surface
import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.FlickerBuilderProvider
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.Group4
+import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.WindowUtils
@@ -37,11 +36,8 @@ import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import org.junit.Assume
import org.junit.Before
-
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -75,32 +71,26 @@ open class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestP
eachRun {
this.setRotation(testSpec.startRotation)
testApp.launchViaIntent(wmHelper)
- val testAppVisible = wmHelper.waitFor(
- WindowManagerStateHelper.isAppFullScreen(testApp.component),
- WindowManagerConditionsFactory.isAppTransitionIdle(
- Display.DEFAULT_DISPLAY))
- require(testAppVisible) {
- "Expected ${testApp.component.toWindowName()} to be visible"
- }
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(testApp.component)
+ .waitForAndVerify()
imeTestApp.launchViaIntent(wmHelper)
- val imeAppVisible = wmHelper.waitFor(
- WindowManagerStateHelper.isAppFullScreen(imeTestApp.component),
- WindowManagerConditionsFactory.isAppTransitionIdle(
- Display.DEFAULT_DISPLAY))
- require(imeAppVisible) {
- "Expected ${imeTestApp.component.toWindowName()} to be visible"
- }
-
- imeTestApp.openIME(device, wmHelper)
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(imeTestApp.component)
+ .waitForAndVerify()
+
+ imeTestApp.openIME(wmHelper)
}
}
teardown {
eachRun {
device.pressHome()
- wmHelper.waitForHomeActivityVisible()
- testApp.exit()
- imeTestApp.exit()
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ testApp.exit(wmHelper)
+ imeTestApp.exit(wmHelper)
}
}
transitions {
@@ -110,8 +100,9 @@ open class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestP
device.swipe(0, displayBounds.bounds.height,
displayBounds.bounds.width, displayBounds.bounds.height, 50)
- wmHelper.waitForFullScreenApp(testApp.component)
- wmHelper.waitForAppTransitionIdle()
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(testApp.component)
+ .waitForAndVerify()
createTag(TAG_IME_INVISIBLE)
}
transitions {
@@ -119,7 +110,9 @@ open class SwitchImeWindowsFromGestureNavTest(private val testSpec: FlickerTestP
val displayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
device.swipe(displayBounds.bounds.width, displayBounds.bounds.height,
0, displayBounds.bounds.height, 50)
- wmHelper.waitForFullScreenApp(imeTestApp.component)
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(imeTestApp.component)
+ .waitForAndVerify()
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
index cc808a0ce871..a33f0ea5a074 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/ActivitiesTransitionTest.kt
@@ -18,22 +18,20 @@ package com.android.server.wm.flicker.launch
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
-import android.view.Display
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.entireScreenCovered
+import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.FlickerBuilderProvider
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.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.TwoActivitiesAppHelper
import com.android.server.wm.flicker.testapp.ActivityOptions
-import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -64,6 +62,7 @@ import org.junit.runners.Parameterized
class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp: TwoActivitiesAppHelper = TwoActivitiesAppHelper(instrumentation)
+ private val tapl = LauncherInstrumentation()
/**
* Entry point for the test runner. It will use this method to initialize and cache
@@ -74,8 +73,8 @@ class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) {
return FlickerBuilder(instrumentation).apply {
setup {
test {
+ tapl.setExpectedRotation(testSpec.startRotation)
testApp.launchViaIntent(wmHelper)
- wmHelper.waitForFullScreenApp(testApp.component)
}
}
teardown {
@@ -85,11 +84,10 @@ class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) {
}
transitions {
testApp.openSecondActivity(device, wmHelper)
- device.pressBack()
- val firstActivityVisible = wmHelper.waitFor(
- WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY),
- WindowManagerStateHelper.isAppFullScreen(testApp.component))
- require(firstActivityVisible) { "Expected ${testApp.component} to be visible" }
+ tapl.pressBack()
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(testApp.component)
+ .waitForAndVerify()
}
}
}
@@ -125,7 +123,7 @@ class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) {
fun entireScreenCovered() = testSpec.entireScreenCovered()
/**
- * Checks that the [LAUNCHER_COMPONENT] window is not on top. The launcher cannot be
+ * Checks that the [FlickerComponentName.LAUNCHER] window is not on top. The launcher cannot be
* asserted with `isAppWindowVisible` because it contains 2 windows with the exact same name,
* and both are never simultaneously visible
*/
@@ -133,17 +131,17 @@ class ActivitiesTransitionTest(val testSpec: FlickerTestParameter) {
@Test
fun launcherWindowNotOnTop() {
testSpec.assertWm {
- this.isAppWindowNotOnTop(LAUNCHER_COMPONENT)
+ this.isAppWindowNotOnTop(FlickerComponentName.LAUNCHER)
}
}
/**
- * Checks that the [LAUNCHER_COMPONENT] layer is never visible during the transition
+ * Checks that the [FlickerComponentName.LAUNCHER] layer is never visible during the transition
*/
@Presubmit
@Test
fun launcherLayerNotVisible() {
- testSpec.assertLayers { this.isInvisible(LAUNCHER_COMPONENT) }
+ testSpec.assertLayers { this.isInvisible(FlickerComponentName.LAUNCHER) }
}
companion object {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
new file mode 100644
index 000000000000..c92704464817
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -0,0 +1,179 @@
+/*
+ * 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.wm.flicker.launch
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.view.Surface
+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.helpers.setRotation
+import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenAppColdFromIcon`
+ *
+ * Actions:
+ * Make sure no apps are running on the device
+ * Launch an app [testApp] by clicking it's icon on all apps and wait animation to complete
+ *
+ * Notes:
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [OpenAppTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class OpenAppColdFromIcon(testSpec: FlickerTestParameter) :
+ OpenAppFromLauncherTransition(testSpec) {
+ /**
+ * Defines the transition used to run the test
+ */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ tapl.setExpectedRotation(Surface.ROTATION_0)
+ RemoveAllTasksButHomeRule.removeAllTasksButHome()
+ this.setRotation(testSpec.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ testApp.exit(wmHelper)
+ }
+ }
+ transitions {
+ tapl.goHome()
+ .switchToAllApps()
+ .getAppIcon(testApp.launcherName)
+ .launch(testApp.`package`)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ override fun appWindowReplacesLauncherAsTopWindow() =
+ super.appWindowReplacesLauncherAsTopWindow()
+
+ @Postsubmit
+ @Test
+ override fun appLayerBecomesVisible() =
+ super.appLayerBecomesVisible()
+
+ @Postsubmit
+ @Test
+ override fun appLayerReplacesLauncher() =
+ super.appLayerReplacesLauncher()
+
+ @Postsubmit
+ @Test
+ override fun appWindowBecomesTopWindow() =
+ super.appWindowBecomesTopWindow()
+
+ @Postsubmit
+ @Test
+ override fun appWindowBecomesVisible() =
+ super.appWindowBecomesVisible()
+
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ @Postsubmit
+ @Test
+ override fun focusChanges() =
+ super.focusChanges()
+
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisible() =
+ super.navBarLayerIsVisible()
+
+ @Postsubmit
+ @Test
+ override fun navBarLayerRotatesAndScales() =
+ super.navBarLayerRotatesAndScales()
+
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsVisible() =
+ super.navBarWindowIsVisible()
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerRotatesScales() =
+ super.statusBarLayerRotatesScales()
+
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisible() =
+ super.statusBarLayerIsVisible()
+
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsVisible() =
+ super.statusBarWindowIsVisible()
+
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ @Postsubmit
+ @Test
+ override fun appWindowIsTopWindowAtEnd() =
+ super.appWindowIsTopWindowAtEnd()
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
+ * repetitions, screen orientation and navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance()
+ .getConfigNonRotationTests()
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index a25ccfb21948..e52b6c326b73 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -54,8 +54,8 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
-open class OpenAppColdTest(testSpec: FlickerTestParameter)
- : OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppColdTest(testSpec: FlickerTestParameter) :
+ OpenAppFromLauncherTransition(testSpec) {
/**
* Defines the transition used to run the test
*/
@@ -75,7 +75,6 @@ open class OpenAppColdTest(testSpec: FlickerTestParameter)
}
transitions {
testApp.launchViaIntent(wmHelper)
- wmHelper.waitForFullScreenApp(testApp.component)
}
}
@@ -85,7 +84,7 @@ open class OpenAppColdTest(testSpec: FlickerTestParameter)
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
/** {@inheritDoc} */
- @FlakyTest
+ @FlakyTest(bugId = 206753786)
@Test
override fun navBarLayerRotatesAndScales() {
super.navBarLayerRotatesAndScales()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
index c6e92adce8c7..269d82d7fa5a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLauncherTransition.kt
@@ -18,7 +18,6 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.replacesLayer
import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.Test
@@ -26,8 +25,8 @@ import org.junit.Test
/**
* Base class for app launch tests
*/
-abstract class OpenAppFromLauncherTransition(testSpec: FlickerTestParameter)
- : OpenAppTransition(testSpec) {
+abstract class OpenAppFromLauncherTransition(testSpec: FlickerTestParameter) :
+ OpenAppTransition(testSpec) {
/**
* Checks that the focus changes from the launcher to [testApp]
@@ -41,25 +40,25 @@ abstract class OpenAppFromLauncherTransition(testSpec: FlickerTestParameter)
}
/**
- * Checks that [LAUNCHER_COMPONENT] layer is visible at the start of the transition, and
- * is replaced by [testApp], which remains visible until the end
+ * Checks that [FlickerComponentName.LAUNCHER] layer is visible at the start of the transition,
+ * and is replaced by [testApp], which remains visible until the end
*/
open fun appLayerReplacesLauncher() {
- testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component,
+ testSpec.replacesLayer(FlickerComponentName.LAUNCHER, testApp.component,
ignoreEntriesWithRotationLayer = true, ignoreSnapshot = true,
ignoreSplashscreen = true)
}
/**
- * Checks that [LAUNCHER_COMPONENT] window is visible at the start of the transition, and
- * is replaced by a snapshot or splash screen (optional), and finally, is replaced by
- * [testApp], which remains visible until the end
+ * Checks that [FlickerComponentName.LAUNCHER] window is visible at the start of the
+ * transition, and is replaced by a snapshot or splash screen (optional), and finally, is
+ * replaced by [testApp], which remains visible until the end
*/
@Presubmit
@Test
open fun appWindowReplacesLauncherAsTopWindow() {
testSpec.assertWm {
- this.isAppWindowOnTop(LAUNCHER_COMPONENT)
+ this.isAppWindowOnTop(FlickerComponentName.LAUNCHER)
.then()
.isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true)
.then()
@@ -68,4 +67,4 @@ abstract class OpenAppFromLauncherTransition(testSpec: FlickerTestParameter)
.isAppWindowOnTop(testApp.component)
}
}
-} \ No newline at end of file
+}
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..cea97ad0302a 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
@@ -62,9 +64,9 @@ open class OpenAppFromLockNotificationCold(testSpec: FlickerTestParameter)
setup {
eachRun {
device.sleep()
- wmHelper.waitFor("noAppWindowsOnTop") {
- it.wmState.topVisibleAppWindow.isEmpty()
- }
+ wmHelper.StateSyncBuilder()
+ .withoutTopVisibleAppWindows()
+ .waitForAndVerify()
}
}
}
@@ -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..3d5f31709a66 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
@@ -63,9 +65,9 @@ open class OpenAppFromLockNotificationWarm(testSpec: FlickerTestParameter)
setup {
eachRun {
device.sleep()
- wmHelper.waitFor("noAppWindowsOnTop") {
- it.wmState.topVisibleAppWindow.isEmpty()
- }
+ wmHelper.StateSyncBuilder()
+ .withoutTopVisibleAppWindows()
+ .waitForAndVerify()
}
}
}
@@ -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..446faa743450 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)
@@ -64,12 +66,14 @@ class OpenAppFromLockNotificationWithLockOverlayApp(testSpec: FlickerTestParamet
// Launch an activity that is shown when the device is locked
showWhenLockedApp.launchViaIntent(wmHelper)
- wmHelper.waitForFullScreenApp(showWhenLockedApp.component)
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(showWhenLockedApp.component)
+ .waitForAndVerify()
device.sleep()
- wmHelper.waitFor("noAppWindowsOnTop") {
- it.wmState.topVisibleAppWindow.isEmpty()
- }
+ wmHelper.StateSyncBuilder()
+ .withoutTopVisibleAppWindows()
+ .waitForAndVerify()
}
}
@@ -109,6 +113,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 +194,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/OpenAppFromLockTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
index 9840520d5dbb..85024e744dea 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockTransition.kt
@@ -16,19 +16,21 @@
package com.android.server.wm.flicker.launch
-import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
import com.android.server.wm.flicker.FlickerTestParameter
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.Test
/**
* Base class for app launch tests from lock screen
*/
-abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter)
- : OpenAppTransition(testSpec) {
+abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter) :
+ OpenAppTransition(testSpec) {
/**
* Defines the transition used to run the test
@@ -39,9 +41,9 @@ abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter)
setup {
eachRun {
device.sleep()
- wmHelper.waitFor("noAppWindowsOnTop") {
- it.wmState.topVisibleAppWindow.isEmpty()
- }
+ wmHelper.StateSyncBuilder()
+ .withoutTopVisibleAppWindows()
+ .waitForAndVerify()
}
}
teardown {
@@ -51,7 +53,6 @@ abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter)
}
transitions {
testApp.launchViaIntent(wmHelper)
- wmHelper.waitForFullScreenApp(testApp.component)
}
}
@@ -112,6 +113,16 @@ abstract class OpenAppFromLockTransition(testSpec: FlickerTestParameter)
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()
+
+ /**
* Checks that the status bar layer is visible at the end of the trace
*
* It is not possible to check at the start because the screen is off
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
index 5022dd8f9bff..68e8e467b950 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationCold.kt
@@ -42,8 +42,8 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
@Postsubmit
-open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter)
- : OpenAppFromNotificationWarm(testSpec) {
+open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter) :
+ OpenAppFromNotificationWarm(testSpec) {
override val transition: FlickerBuilder.() -> Unit
get() = {
super.transition(this)
@@ -52,9 +52,9 @@ open class OpenAppFromNotificationCold(testSpec: FlickerTestParameter)
eachRun {
// Close the app that posted the notification to trigger a cold start next time
// it is open - can't just kill it because that would remove the notification.
- taplInstrumentation.goHome()
- taplInstrumentation.workspace.switchToOverview()
- taplInstrumentation.overview.dismissAllTasks()
+ tapl.goHome()
+ tapl.workspace.switchToOverview()
+ tapl.overview.dismissAllTasks()
}
}
}
@@ -77,8 +77,8 @@ open class OpenAppFromNotificationCold(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/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index 2d00f3f92dcc..6d5911152dc0 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,14 +16,13 @@
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
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
@@ -53,10 +52,8 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
@Postsubmit
-open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter)
- : OpenAppTransition(testSpec) {
- protected val taplInstrumentation = LauncherInstrumentation()
-
+open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter) :
+ OpenAppTransition(testSpec) {
override val testApp: NotificationAppHelper = NotificationAppHelper(instrumentation)
open val openingNotificationsFromLockScreen = false
@@ -70,10 +67,14 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter)
}
eachRun {
testApp.launchViaIntent(wmHelper)
- wmHelper.waitForFullScreenApp(testApp.component)
- testApp.postNotification(device, wmHelper)
- device.pressHome()
- wmHelper.waitForAppTransitionIdle()
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(testApp.component)
+ .waitForAndVerify()
+ testApp.postNotification(wmHelper)
+ tapl.goHome()
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
}
}
@@ -106,7 +107,9 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter)
instrumentation.uiAutomation.syncInputTransactions()
// Wait for the app to launch
- wmHelper.waitForFullScreenApp(testApp.component)
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(testApp.component)
+ .waitForAndVerify()
}
teardown {
@@ -180,6 +183,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 +223,4 @@ open class OpenAppFromNotificationWarm(testSpec: FlickerTestParameter)
.getConfigNonRotationTests(repetitions = 3)
}
}
-} \ No newline at end of file
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index f7532b201b75..c7883d65856d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -19,17 +19,14 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
-import android.view.Display
+import android.view.Surface
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.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.traces.common.WindowManagerConditionsFactory
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
@@ -61,8 +58,8 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
-open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter)
- : OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) :
+ OpenAppFromLauncherTransition(testSpec) {
/**
* Defines the transition used to run the test
@@ -75,27 +72,25 @@ open class OpenAppFromOverviewTest(testSpec: FlickerTestParameter)
testApp.launchViaIntent(wmHelper)
}
eachRun {
+ // Can't use tapl.goHome() because of b/235841947
device.pressHome()
- wmHelper.waitForAppTransitionIdle()
- device.pressRecentApps()
- wmHelper.waitFor(
- WindowManagerConditionsFactory
- .isAppTransitionIdle(Display.DEFAULT_DISPLAY),
- WindowManagerConditionsFactory.isActivityVisible(LAUNCHER_COMPONENT),
- WindowManagerConditionsFactory.hasLayersAnimating().negate()
- )
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ // Launcher is always ROTATION_0
+ tapl.setExpectedRotation(Surface.ROTATION_0)
+ tapl.workspace.switchToOverview()
+ wmHelper.StateSyncBuilder()
+ .withRecentsActivityVisible()
+ .waitForAndVerify()
this.setRotation(testSpec.startRotation)
}
}
transitions {
- device.reopenAppFromOverview(wmHelper)
- wmHelper.waitFor(
- WindowManagerConditionsFactory.hasLayersAnimating().negate(),
- WindowManagerConditionsFactory.isWMStateComplete(),
- WindowManagerConditionsFactory.isLayerVisible(LAUNCHER_COMPONENT).negate(),
- WindowManagerConditionsFactory.isActivityVisible(LAUNCHER_COMPONENT).negate()
- )
- wmHelper.waitForFullScreenApp(testApp.component)
+ tapl.overview.currentTask.open()
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(testApp.component)
+ .waitForAndVerify()
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
index fd74ea5dd973..04523398046f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt
@@ -16,8 +16,8 @@
package com.android.server.wm.flicker.launch
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.Surface
@@ -60,10 +60,9 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
-open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter)
- : OpenAppFromLockTransition(testSpec) {
+open class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) :
+ OpenAppFromLockTransition(testSpec) {
override val testApp = NonResizeableAppHelper(instrumentation)
- private val colorFadComponent = FlickerComponentName("", "ColorFade BLAST#")
/**
* Checks that the nav bar layer starts invisible, becomes visible during unlocking animation
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
index 20e6d0222854..9c97970b4b91 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt
@@ -19,14 +19,15 @@ package com.android.server.wm.flicker.launch
import android.app.Instrumentation
import android.platform.test.annotations.Presubmit
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.SimpleAppHelper
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
@@ -42,6 +43,7 @@ import org.junit.Test
abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) {
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
protected open val testApp: StandardAppHelper = SimpleAppHelper(instrumentation)
+ protected val tapl = LauncherInstrumentation()
/**
* Defines the transition used to run the test
@@ -49,6 +51,7 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) {
protected open val transition: FlickerBuilder.() -> Unit = {
setup {
test {
+ tapl.setExpectedRotation(testSpec.startRotation)
device.wakeUpAndGoToHomeScreen()
this.setRotation(testSpec.startRotation)
}
@@ -225,11 +228,23 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) {
testSpec.assertWm {
this.isAppWindowNotOnTop(testApp.component)
.then()
- .isAppWindowOnTop(FlickerComponentName.SNAPSHOT, isOptional = true)
- .then()
- .isAppWindowOnTop(FlickerComponentName.SPLASH_SCREEN, isOptional = true)
- .then()
- .isAppWindowOnTop(testApp.component)
+ .isAppWindowOnTop(
+ testApp.component
+ .or(FlickerComponentName.SNAPSHOT)
+ .or(FlickerComponentName.SPLASH_SCREEN)
+ )
+ }
+ }
+
+ /**
+ * Checks that [testApp] window is not on top at the start of the transition, and then becomes
+ * the top visible window until the end of the transition.
+ */
+ @Presubmit
+ @Test
+ open fun appWindowIsTopWindowAtEnd() {
+ testSpec.assertWmEnd {
+ this.isAppWindowOnTop(testApp.component)
}
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 45f339979b15..88e2b2d8e7b7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -16,9 +16,9 @@
package com.android.server.wm.flicker.launch
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
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
@@ -54,8 +54,8 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
-open class OpenAppWarmTest(testSpec: FlickerTestParameter)
- : OpenAppFromLauncherTransition(testSpec) {
+open class OpenAppWarmTest(testSpec: FlickerTestParameter) :
+ OpenAppFromLauncherTransition(testSpec) {
/**
* Defines the transition used to run the test
*/
@@ -68,18 +68,19 @@ open class OpenAppWarmTest(testSpec: FlickerTestParameter)
}
eachRun {
device.pressHome()
- wmHelper.waitForHomeActivityVisible()
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
this.setRotation(testSpec.startRotation)
}
}
teardown {
- eachRun {
+ test {
testApp.exit(wmHelper)
}
}
transitions {
testApp.launchViaIntent(wmHelper)
- wmHelper.waitForFullScreenApp(testApp.component)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index 2f546b56f145..99df1f4b5798 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -21,11 +21,11 @@ import android.app.WallpaperManager
import android.platform.test.annotations.Postsubmit
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.FlickerBuilderProvider
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.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
@@ -64,10 +64,10 @@ import org.junit.runners.Parameterized
@Group4
class TaskTransitionTest(val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
private val mTestApp: NewTasksAppHelper = NewTasksAppHelper(instrumentation)
private val mWallpaper by lazy {
- getWallpaperPackage(InstrumentationRegistry.getInstrumentation())
- ?: error("Unable to obtain wallpaper")
+ getWallpaperPackage(instrumentation) ?: error("Unable to obtain wallpaper")
}
@FlickerBuilderProvider
@@ -76,19 +76,19 @@ class TaskTransitionTest(val testSpec: FlickerTestParameter) {
setup {
eachRun {
mTestApp.launchViaIntent(wmHelper)
- wmHelper.waitForFullScreenApp(mTestApp.component)
}
}
teardown {
test {
- mTestApp.exit()
+ mTestApp.exit(wmHelper)
}
}
transitions {
mTestApp.openNewTask(device, wmHelper)
- device.pressBack()
- wmHelper.waitForAppTransitionIdle()
- wmHelper.waitForFullScreenApp(mTestApp.component)
+ tapl.pressBack()
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(mTestApp.component)
+ .waitForAndVerify()
}
}
}
@@ -126,7 +126,7 @@ class TaskTransitionTest(val testSpec: FlickerTestParameter) {
@Test
fun launcherWindowIsNeverVisible() {
testSpec.assertWm {
- this.isAppWindowInvisible(LAUNCHER_COMPONENT)
+ this.isAppWindowInvisible(FlickerComponentName.LAUNCHER)
}
}
@@ -138,7 +138,7 @@ class TaskTransitionTest(val testSpec: FlickerTestParameter) {
@Test
fun launcherLayerIsNeverVisible() {
testSpec.assertLayers {
- this.isInvisible(LAUNCHER_COMPONENT)
+ this.isInvisible(FlickerComponentName.LAUNCHER)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index c89e6a44ab6c..1a712842fec0 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.quickswitch
import android.app.Instrumentation
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.Surface
@@ -27,12 +28,10 @@ import com.android.server.wm.flicker.FlickerBuilderProvider
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.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
@@ -40,6 +39,7 @@ import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.Rect
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -66,13 +66,11 @@ import org.junit.runners.Parameterized
@Group1
open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- private val taplInstrumentation = LauncherInstrumentation()
+ private val tapl = LauncherInstrumentation()
private val testApp1 = SimpleAppHelper(instrumentation)
private val testApp2 = NonResizeableAppHelper(instrumentation)
- private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
-
@Before
open fun before() {
Assume.assumeFalse(isShellTransitionsEnabled)
@@ -83,28 +81,26 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa
return FlickerBuilder(instrumentation).apply {
setup {
test {
- taplInstrumentation.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(testSpec.startRotation)
}
-
eachRun {
testApp1.launchViaIntent(wmHelper)
- wmHelper.waitForFullScreenApp(testApp1.component)
-
testApp2.launchViaIntent(wmHelper)
- wmHelper.waitForFullScreenApp(testApp2.component)
+ startDisplayBounds = wmHelper.currentState.layerState
+ .physicalDisplayBounds ?: error("Display not found")
}
}
transitions {
- taplInstrumentation.launchedAppState.quickSwitchToPreviousApp()
- wmHelper.waitForFullScreenApp(testApp1.component)
- wmHelper.waitForAppTransitionIdle()
- wmHelper.waitForNavBarStatusBarVisible()
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ wmHelper.StateSyncBuilder()
+ .withNavBarStatusBarVisible()
+ .waitForAndVerify()
}
teardown {
test {
- testApp1.exit()
- testApp2.exit()
+ testApp1.exit(wmHelper)
+ testApp2.exit(wmHelper)
}
}
}
@@ -251,7 +247,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa
this.isAppWindowVisible(testApp2.component)
.then()
// TODO: Do we actually want to test this? Seems too implementation specific...
- .isAppWindowVisible(LAUNCHER_COMPONENT, isOptional = true)
+ .isAppWindowVisible(FlickerComponentName.LAUNCHER, isOptional = true)
.then()
.isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
.then()
@@ -270,7 +266,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa
testSpec.assertLayers {
this.isVisible(testApp2.component)
.then()
- .isVisible(LAUNCHER_COMPONENT, isOptional = true)
+ .isVisible(FlickerComponentName.LAUNCHER, isOptional = true)
.then()
.isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
.then()
@@ -297,7 +293,7 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa
*
* NOTE: This doesn't check that the navbar is visible or not.
*/
- @Presubmit
+ @FlakyTest
@Test
fun navbarIsAlwaysInRightPosition() = testSpec.navBarLayerRotatesAndScales()
@@ -316,6 +312,8 @@ open class QuickSwitchBetweenTwoAppsBackTest(private val testSpec: FlickerTestPa
fun statusBarLayerIsAlwaysVisible() = testSpec.statusBarLayerIsVisible()
companion object {
+ private var startDisplayBounds = Rect.EMPTY
+
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index 725d2c3d818c..9e43a97a6242 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.quickswitch
import android.app.Instrumentation
+import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.Surface
@@ -27,7 +28,6 @@ import com.android.server.wm.flicker.FlickerBuilderProvider
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.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
@@ -66,7 +66,7 @@ import org.junit.runners.Parameterized
@Group1
open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- private val taplInstrumentation = LauncherInstrumentation()
+ private val tapl = LauncherInstrumentation()
private val testApp1 = SimpleAppHelper(instrumentation)
private val testApp2 = NonResizeableAppHelper(instrumentation)
@@ -81,33 +81,24 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes
return FlickerBuilder(instrumentation).apply {
setup {
test {
- taplInstrumentation.setExpectedRotation(testSpec.startRotation)
+ tapl.setExpectedRotation(testSpec.startRotation)
}
-
eachRun {
testApp1.launchViaIntent(wmHelper)
- wmHelper.waitForFullScreenApp(testApp1.component)
-
testApp2.launchViaIntent(wmHelper)
- wmHelper.waitForFullScreenApp(testApp2.component)
-
+ tapl.launchedAppState.quickSwitchToPreviousApp()
+ wmHelper.StateSyncBuilder()
+ .withNavBarStatusBarVisible()
+ .waitForAndVerify()
startDisplayBounds = wmHelper.currentState.layerState
- .displays.firstOrNull { !it.isVirtual }
- ?.layerStackSpace
- ?: error("Display not found")
-
- taplInstrumentation.launchedAppState.quickSwitchToPreviousApp()
-
- wmHelper.waitForFullScreenApp(testApp1.component)
- wmHelper.waitForAppTransitionIdle()
+ .physicalDisplayBounds ?: error("Display not found")
}
}
transitions {
- taplInstrumentation.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
-
- wmHelper.waitForFullScreenApp(testApp2.component)
- wmHelper.waitForAppTransitionIdle()
- wmHelper.waitForNavBarStatusBarVisible()
+ tapl.launchedAppState.quickSwitchToPreviousAppSwipeLeft()
+ wmHelper.StateSyncBuilder()
+ .withNavBarStatusBarVisible()
+ .waitForAndVerify()
}
teardown {
@@ -261,7 +252,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes
testSpec.assertWm {
this.isAppWindowVisible(testApp1.component)
.then()
- .isAppWindowVisible(LAUNCHER_COMPONENT, isOptional = true)
+ .isAppWindowVisible(FlickerComponentName.LAUNCHER, isOptional = true)
.then()
.isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
.then()
@@ -280,7 +271,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes
testSpec.assertLayers {
this.isVisible(testApp1.component)
.then()
- .isVisible(LAUNCHER_COMPONENT, isOptional = true)
+ .isVisible(FlickerComponentName.LAUNCHER, isOptional = true)
.then()
.isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
.then()
@@ -311,7 +302,7 @@ open class QuickSwitchBetweenTwoAppsForwardTest(private val testSpec: FlickerTes
*
* NOTE: This doesn't check that the navbar is visible or not.
*/
- @Presubmit
+ @FlakyTest
@Test
open fun navbarIsAlwaysInRightPosition() {
testSpec.navBarLayerRotatesAndScales()
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index cc4a4b2d38aa..4ea1f1c18b52 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -27,18 +27,17 @@ import com.android.server.wm.flicker.FlickerBuilderProvider
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.LAUNCHER_COMPONENT
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.SimpleAppHelper
-import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
import com.android.server.wm.flicker.statusBarLayerIsVisible
import com.android.server.wm.flicker.statusBarWindowIsVisible
import com.android.server.wm.traces.common.FlickerComponentName
+import com.android.server.wm.traces.common.Rect
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -67,8 +66,6 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
private val testApp = SimpleAppHelper(instrumentation)
- private val startDisplayBounds = WindowUtils.getDisplayBounds(testSpec.startRotation)
-
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
@@ -80,20 +77,26 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
eachRun {
testApp.launchViaIntent(wmHelper)
device.pressHome()
- wmHelper.waitForHomeActivityVisible()
- wmHelper.waitForWindowSurfaceDisappeared(testApp.component)
+ wmHelper.StateSyncBuilder()
+ .withHomeActivityVisible()
+ .withWindowSurfaceDisappeared(testApp.component)
+ .waitForAndVerify()
+
+ startDisplayBounds = wmHelper.currentState.layerState
+ .physicalDisplayBounds ?: error("Display not found")
}
}
transitions {
taplInstrumentation.workspace.quickSwitchToPreviousApp()
- wmHelper.waitForFullScreenApp(testApp.component)
- wmHelper.waitForAppTransitionIdle()
- wmHelper.waitForNavBarStatusBarVisible()
+ wmHelper.StateSyncBuilder()
+ .withFullScreenApp(testApp.component)
+ .withNavBarStatusBarVisible()
+ .waitForAndVerify()
}
teardown {
eachRun {
- testApp.exit()
+ testApp.exit(wmHelper)
}
}
}
@@ -154,7 +157,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
@Test
fun startsWithLauncherWindowsCoverFullScreen() {
testSpec.assertWmStart {
- this.frameRegion(LAUNCHER_COMPONENT).coversExactly(startDisplayBounds)
+ this.frameRegion(FlickerComponentName.LAUNCHER).coversExactly(startDisplayBounds)
}
}
@@ -166,7 +169,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
@Test
fun startsWithLauncherLayersCoverFullScreen() {
testSpec.assertLayersStart {
- this.visibleRegion(LAUNCHER_COMPONENT).coversExactly(startDisplayBounds)
+ this.visibleRegion(FlickerComponentName.LAUNCHER).coversExactly(startDisplayBounds)
}
}
@@ -177,7 +180,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
@Test
fun startsWithLauncherBeingOnTop() {
testSpec.assertWmStart {
- this.isAppWindowOnTop(LAUNCHER_COMPONENT)
+ this.isAppWindowOnTop(FlickerComponentName.LAUNCHER)
}
}
@@ -229,9 +232,9 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
@Test
fun launcherWindowBecomesAndStaysInvisible() {
testSpec.assertWm {
- this.isAppWindowOnTop(LAUNCHER_COMPONENT)
+ this.isAppWindowOnTop(FlickerComponentName.LAUNCHER)
.then()
- .isAppWindowNotOnTop(LAUNCHER_COMPONENT)
+ .isAppWindowNotOnTop(FlickerComponentName.LAUNCHER)
}
}
@@ -243,9 +246,9 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
@Test
fun launcherLayerBecomesAndStaysInvisible() {
testSpec.assertLayers {
- this.isVisible(LAUNCHER_COMPONENT)
+ this.isVisible(FlickerComponentName.LAUNCHER)
.then()
- .isInvisible(LAUNCHER_COMPONENT)
+ .isInvisible(FlickerComponentName.LAUNCHER)
}
}
@@ -257,7 +260,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
@Test
fun appWindowIsVisibleOnceLauncherWindowIsInvisible() {
testSpec.assertWm {
- this.isAppWindowOnTop(LAUNCHER_COMPONENT)
+ this.isAppWindowOnTop(FlickerComponentName.LAUNCHER)
.then()
.isAppWindowVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
.then()
@@ -273,7 +276,7 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
@Test
fun appLayerIsVisibleOnceLauncherLayerIsInvisible() {
testSpec.assertLayers {
- this.isVisible(LAUNCHER_COMPONENT)
+ this.isVisible(FlickerComponentName.LAUNCHER)
.then()
.isVisible(FlickerComponentName.SNAPSHOT, isOptional = true)
.then()
@@ -326,6 +329,8 @@ class QuickSwitchFromLauncherTest(private val testSpec: FlickerTestParameter) {
fun screenIsAlwaysFilled() = testSpec.entireScreenCovered()
companion object {
+ private var startDisplayBounds = Rect.EMPTY
+
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams(): Collection<FlickerTestParameter> {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 0becadf630e1..586be06d1a29 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -22,12 +22,12 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerIsVisible
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.traces.common.FlickerComponentName
import org.junit.Test
@@ -47,7 +47,7 @@ abstract class RotationTransition(protected val testSpec: FlickerTestParameter)
}
teardown {
test {
- testApp.exit()
+ testApp.exit(wmHelper)
}
}
transitions {
diff --git a/tests/RollbackTest/SampleRollbackApp/Android.bp b/tests/RollbackTest/SampleRollbackApp/Android.bp
new file mode 100644
index 000000000000..a18488d8f57f
--- /dev/null
+++ b/tests/RollbackTest/SampleRollbackApp/Android.bp
@@ -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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+ name: "SampleRollbackApp",
+ srcs: [
+ "src/**/*.java",
+ ],
+ resource_dirs: ["res"],
+ certificate: "platform",
+ sdk_version: "system_current",
+}
diff --git a/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml b/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml
new file mode 100644
index 000000000000..5a135c978343
--- /dev/null
+++ b/tests/RollbackTest/SampleRollbackApp/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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.sample.rollbackapp" >
+ <uses-permission android:name="android.permission.TEST_MANAGE_ROLLBACKS" />
+ <application
+ android:label="@string/title_activity_main">
+ <activity
+ android:name="com.android.sample.rollbackapp.MainActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest> \ No newline at end of file
diff --git a/tests/RollbackTest/SampleRollbackApp/res/layout/activity_main.xml b/tests/RollbackTest/SampleRollbackApp/res/layout/activity_main.xml
new file mode 100644
index 000000000000..3fb987bb539c
--- /dev/null
+++ b/tests/RollbackTest/SampleRollbackApp/res/layout/activity_main.xml
@@ -0,0 +1,37 @@
+<?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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:id="@+id/trigger_rollback_button"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ style="?android:attr/buttonBarButtonStyle"
+ android:text="Rollback Selected" />
+
+ <ListView
+ android:id="@+id/listView"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:divider="?android:attr/dividerHorizontal"
+ android:dividerHeight="1dp" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/tests/RollbackTest/SampleRollbackApp/res/layout/listitem_rollbackinfo.xml b/tests/RollbackTest/SampleRollbackApp/res/layout/listitem_rollbackinfo.xml
new file mode 100644
index 000000000000..f650dd5d2230
--- /dev/null
+++ b/tests/RollbackTest/SampleRollbackApp/res/layout/listitem_rollbackinfo.xml
@@ -0,0 +1,41 @@
+<?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.
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="10dp"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp">
+ <TextView android:id="@+id/rollback_id"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="20dp"/>
+ <TextView android:id="@+id/rollback_packages"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="16dp"/>
+ <CheckBox android:id="@+id/checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Roll Back"/>
+ </LinearLayout>
+</RelativeLayout> \ No newline at end of file
diff --git a/tests/RollbackTest/SampleRollbackApp/res/values/strings.xml b/tests/RollbackTest/SampleRollbackApp/res/values/strings.xml
new file mode 100644
index 000000000000..a85b6800a146
--- /dev/null
+++ b/tests/RollbackTest/SampleRollbackApp/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?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>
+ <string name="title_activity_main" description="Launcher title">Rollback Sample App</string>
+</resources> \ No newline at end of file
diff --git a/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java b/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java
new file mode 100644
index 000000000000..916551a8ce6d
--- /dev/null
+++ b/tests/RollbackTest/SampleRollbackApp/src/com/android/sample/rollbackapp/MainActivity.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sample.rollbackapp;
+
+import static android.app.PendingIntent.FLAG_MUTABLE;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class MainActivity extends Activity {
+
+ List<Integer> mIdsToRollback = new ArrayList<>();
+ Button mTriggerRollbackButton;
+ RollbackManager mRollbackManager;
+ static final String ROLLBACK_ID_EXTRA = "rollbackId";
+ static final String ACTION_NAME = MainActivity.class.getName();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ ListView rollbackListView = findViewById(R.id.listView);
+ mRollbackManager = getApplicationContext().getSystemService(RollbackManager.class);
+ initTriggerRollbackButton();
+
+ // Populate list of available rollbacks.
+ List<RollbackInfo> availableRollbacks = mRollbackManager.getAvailableRollbacks();
+ CustomAdapter adapter = new CustomAdapter(availableRollbacks);
+ rollbackListView.setAdapter(adapter);
+
+ // Register receiver for rollback status events.
+ getApplicationContext().registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context,
+ Intent intent) {
+ int rollbackId = intent.getIntExtra(ROLLBACK_ID_EXTRA, -1);
+ int rollbackStatusCode = intent.getIntExtra(RollbackManager.EXTRA_STATUS,
+ RollbackManager.STATUS_FAILURE);
+ String rollbackStatus = "FAILED";
+ if (rollbackStatusCode == RollbackManager.STATUS_SUCCESS) {
+ rollbackStatus = "SUCCESS";
+ }
+ makeToast("Status for rollback ID " + rollbackId + " is " + rollbackStatus);
+ }}, new IntentFilter(ACTION_NAME), Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ private void initTriggerRollbackButton() {
+ mTriggerRollbackButton = findViewById(R.id.trigger_rollback_button);
+ mTriggerRollbackButton.setClickable(false);
+ mTriggerRollbackButton.setOnClickListener(v -> {
+ // Commits all selected rollbacks. Rollback status events will be sent to our receiver.
+ for (int i = 0; i < mIdsToRollback.size(); i++) {
+ Intent intent = new Intent(ACTION_NAME);
+ intent.putExtra(ROLLBACK_ID_EXTRA, mIdsToRollback.get(i));
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ getApplicationContext(), 0, intent, FLAG_MUTABLE);
+ mRollbackManager.commitRollback(mIdsToRollback.get(i),
+ Collections.emptyList(),
+ pendingIntent.getIntentSender());
+ }
+ });
+ }
+
+
+
+ private void makeToast(String message) {
+ runOnUiThread(() -> Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show());
+ }
+
+ public class CustomAdapter extends BaseAdapter {
+ List<RollbackInfo> mRollbackInfos;
+ LayoutInflater mInflater = LayoutInflater.from(getApplicationContext());
+
+ CustomAdapter(List<RollbackInfo> rollbackInfos) {
+ mRollbackInfos = rollbackInfos;
+ }
+
+ @Override
+ public int getCount() {
+ return mRollbackInfos.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mRollbackInfos.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mRollbackInfos.get(position).getRollbackId();
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ if (view == null) {
+ view = mInflater.inflate(R.layout.listitem_rollbackinfo, null);
+ }
+ RollbackInfo rollbackInfo = mRollbackInfos.get(position);
+ TextView rollbackIdView = view.findViewById(R.id.rollback_id);
+ rollbackIdView.setText("Rollback ID " + rollbackInfo.getRollbackId());
+ TextView rollbackPackagesTextView = view.findViewById(R.id.rollback_packages);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < rollbackInfo.getPackages().size(); i++) {
+ PackageRollbackInfo pkgInfo = rollbackInfo.getPackages().get(i);
+ sb.append(pkgInfo.getPackageName() + ": "
+ + pkgInfo.getVersionRolledBackFrom().getLongVersionCode() + " -> "
+ + pkgInfo.getVersionRolledBackTo().getLongVersionCode() + ",");
+ }
+ sb.deleteCharAt(sb.length() - 1);
+ rollbackPackagesTextView.setText(sb.toString());
+ CheckBox checkbox = view.findViewById(R.id.checkbox);
+ checkbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ if (isChecked) {
+ mIdsToRollback.add(rollbackInfo.getRollbackId());
+ } else {
+ mIdsToRollback.remove(Integer.valueOf(rollbackInfo.getRollbackId()));
+ }
+ mTriggerRollbackButton.setClickable(mIdsToRollback.size() > 0);
+ });
+ return view;
+ }
+ }
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
index cc2ab19c97f1..06c098df385d 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
@@ -19,6 +19,7 @@ package com.google.android.lint.parcel
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
import com.android.tools.lint.detector.api.Location
+import com.intellij.psi.PsiArrayType
import com.intellij.psi.PsiCallExpression
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiIntersectionType
@@ -26,8 +27,8 @@ import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypeParameter
import com.intellij.psi.PsiWildcardType
-import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UVariable
@@ -42,11 +43,11 @@ abstract class CallMigrator(
) {
open fun report(context: JavaContext, call: UCallExpression, method: PsiMethod) {
val location = context.getLocation(call)
- val itemType = getBoundingClass(context, call, method)
+ val itemType = filter(getBoundingClass(context, call, method))
val fix = (itemType as? PsiClassType)?.let { type ->
getParcelFix(location, this.method.name, getArgumentSuffix(type))
}
- val message = "Unsafe `Parcel.${this.method.name}()` API usage"
+ val message = "Unsafe `${this.method.className}.${this.method.name}()` API usage"
context.report(SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, call, location, message, fix)
}
@@ -73,14 +74,14 @@ abstract class CallMigrator(
}
/**
- * Tries to obtain the type expected by the "receiving" end given a certain {@link UExpression}.
+ * Tries to obtain the type expected by the "receiving" end given a certain {@link UElement}.
*
* This could be an assignment, an argument passed to a method call, to a constructor call, a
* type cast, etc. If no receiving end is found, the type of the UExpression itself is returned.
*/
- protected fun getReceivingType(expression: UExpression): PsiType? {
+ protected fun getReceivingType(expression: UElement): PsiType? {
val parent = expression.uastParent
- val type = when (parent) {
+ var type = when (parent) {
is UCallExpression -> {
val i = parent.valueArguments.indexOf(expression)
val psiCall = parent.sourcePsi as? PsiCallExpression ?: return null
@@ -92,10 +93,13 @@ abstract class CallMigrator(
is UExpression -> parent.getExpressionType()
else -> null
}
- return filter(type ?: expression.getExpressionType())
+ if (type == null && expression is UExpression) {
+ type = expression.getExpressionType()
+ }
+ return type
}
- private fun filter(type: PsiType?): PsiType? {
+ protected fun filter(type: PsiType?): PsiType? {
// It's important that PsiIntersectionType case is above the one that check the type in
// rejects, because for intersect types, the canonicalText is one of the terms.
if (type is PsiIntersectionType) {
@@ -169,7 +173,7 @@ class ContainerReturnMigrator(
override fun getBoundingClass(
context: JavaContext, call: UCallExpression, method: PsiMethod
): PsiType? {
- val type = getReceivingType(call.uastParent as UExpression) ?: return null
+ val type = getReceivingType(call.uastParent!!) ?: return null
return getItemType(type, container)
}
}
@@ -184,7 +188,7 @@ class ReturnMigrator(
override fun getBoundingClass(
context: JavaContext, call: UCallExpression, method: PsiMethod
): PsiType? {
- return getReceivingType(call.uastParent as UExpression)
+ return getReceivingType(call.uastParent!!)
}
}
@@ -199,11 +203,27 @@ class ReturnMigratorWithClassLoader(
override fun getBoundingClass(
context: JavaContext, call: UCallExpression, method: PsiMethod
): PsiType? {
- return getReceivingType(call.uastParent as UExpression)
+ return getReceivingType(call.uastParent!!)
}
override fun getArgumentSuffix(type: PsiClassType): String =
"${type.rawType().canonicalText}.class.getClassLoader(), " +
"${type.rawType().canonicalText}.class"
-} \ No newline at end of file
+}
+
+/**
+ * This class derives the type to be appended by inferring the expected array type
+ * for the method result.
+ */
+class ArrayReturnMigrator(
+ method: Method,
+ rejects: Set<String> = emptySet(),
+) : CallMigrator(method, rejects) {
+ override fun getBoundingClass(
+ context: JavaContext, call: UCallExpression, method: PsiMethod
+ ): PsiType? {
+ val type = getReceivingType(call.uastParent!!)
+ return (type as? PsiArrayType)?.componentType
+ }
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
index c032fa29f254..0826e8e74431 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
@@ -35,4 +35,8 @@ data class Method(
val prefix = if (params.isEmpty()) "" else "${params.joinToString(", ", "<", ">")} "
return "$prefix$clazz.$name(${parameters.joinToString()})"
}
-} \ No newline at end of file
+
+ val className: String by lazy {
+ clazz.split(".").last()
+ }
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
index 89dbcaeac3a7..bf99e8e669fc 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
@@ -24,6 +24,7 @@ import com.intellij.psi.PsiTypeParameter
import org.jetbrains.uast.UCallExpression
import java.util.*
+@Suppress("UnstableApiUsage")
class SaferParcelChecker : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames(): List<String> =
MIGRATORS
@@ -65,9 +66,9 @@ class SaferParcelChecker : Detector(), SourceCodeScanner {
@JvmField
val ISSUE_UNSAFE_API_USAGE: Issue = Issue.create(
id = "UnsafeParcelApi",
- briefDescription = "Use of unsafe Parcel API",
+ briefDescription = "Use of unsafe deserialization API",
explanation = """
- You are using a deprecated Parcel API that doesn't accept the expected class as\
+ You are using a deprecated deserialization API that doesn't accept the expected class as\
a parameter. This means that unexpected classes could be instantiated and\
unexpected code executed.
@@ -83,25 +84,52 @@ class SaferParcelChecker : Detector(), SourceCodeScanner {
)
)
- private val METHOD_READ_SERIALIZABLE = Method("android.os.Parcel", "readSerializable", listOf())
- private val METHOD_READ_ARRAY_LIST = Method("android.os.Parcel", "readArrayList", listOf("java.lang.ClassLoader"))
- private val METHOD_READ_LIST = Method("android.os.Parcel", "readList", listOf("java.util.List", "java.lang.ClassLoader"))
- private val METHOD_READ_PARCELABLE = Method(listOf("T"), "android.os.Parcel", "readParcelable", listOf("java.lang.ClassLoader"))
- private val METHOD_READ_PARCELABLE_LIST = Method(listOf("T"), "android.os.Parcel", "readParcelableList", listOf("java.util.List<T>", "java.lang.ClassLoader"))
- private val METHOD_READ_SPARSE_ARRAY = Method(listOf("T"), "android.os.Parcel", "readSparseArray", listOf("java.lang.ClassLoader"))
+ // Parcel
+ private val PARCEL_METHOD_READ_SERIALIZABLE = Method("android.os.Parcel", "readSerializable", listOf())
+ private val PARCEL_METHOD_READ_ARRAY_LIST = Method("android.os.Parcel", "readArrayList", listOf("java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_LIST = Method("android.os.Parcel", "readList", listOf("java.util.List", "java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_PARCELABLE = Method(listOf("T"), "android.os.Parcel", "readParcelable", listOf("java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_PARCELABLE_LIST = Method(listOf("T"), "android.os.Parcel", "readParcelableList", listOf("java.util.List<T>", "java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_SPARSE_ARRAY = Method(listOf("T"), "android.os.Parcel", "readSparseArray", listOf("java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_ARRAY = Method("android.os.Parcel", "readArray", listOf("java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_PARCELABLE_ARRAY = Method("android.os.Parcel", "readParcelableArray", listOf("java.lang.ClassLoader"))
+
+ // Bundle
+ private val BUNDLE_METHOD_GET_SERIALIZABLE = Method("android.os.Bundle", "getSerializable", listOf("java.lang.String"))
+ private val BUNDLE_METHOD_GET_PARCELABLE = Method(listOf("T"), "android.os.Bundle", "getParcelable", listOf("java.lang.String"))
+ private val BUNDLE_METHOD_GET_PARCELABLE_ARRAY_LIST = Method(listOf("T"), "android.os.Bundle", "getParcelableArrayList", listOf("java.lang.String"))
+ private val BUNDLE_METHOD_GET_PARCELABLE_ARRAY = Method("android.os.Bundle", "getParcelableArray", listOf("java.lang.String"))
+ private val BUNDLE_METHOD_GET_SPARSE_PARCELABLE_ARRAY = Method(listOf("T"), "android.os.Bundle", "getSparseParcelableArray", listOf("java.lang.String"))
+
+ // Intent
+ private val INTENT_METHOD_GET_SERIALIZABLE_EXTRA = Method("android.content.Intent", "getSerializableExtra", listOf("java.lang.String"))
+ private val INTENT_METHOD_GET_PARCELABLE_EXTRA = Method(listOf("T"), "android.content.Intent", "getParcelableExtra", listOf("java.lang.String"))
+ private val INTENT_METHOD_GET_PARCELABLE_ARRAY_EXTRA = Method("android.content.Intent", "getParcelableArrayExtra", listOf("java.lang.String"))
+ private val INTENT_METHOD_GET_PARCELABLE_ARRAY_LIST_EXTRA = Method(listOf("T"), "android.content.Intent", "getParcelableArrayListExtra", listOf("java.lang.String"))
// TODO: Write migrators for methods below
- private val METHOD_READ_ARRAY = Method("android.os.Parcel", "readArray", listOf("java.lang.ClassLoader"))
- private val METHOD_READ_PARCELABLE_ARRAY = Method("android.os.Parcel", "readParcelableArray", listOf("java.lang.ClassLoader"))
- private val METHOD_READ_PARCELABLE_CREATOR = Method("android.os.Parcel", "readParcelableCreator", listOf("java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_PARCELABLE_CREATOR = Method("android.os.Parcel", "readParcelableCreator", listOf("java.lang.ClassLoader"))
private val MIGRATORS = listOf(
- ReturnMigrator(METHOD_READ_PARCELABLE, setOf("android.os.Parcelable")),
- ContainerArgumentMigrator(METHOD_READ_LIST, 0, "java.util.List"),
- ContainerReturnMigrator(METHOD_READ_ARRAY_LIST, "java.util.Collection"),
- ContainerReturnMigrator(METHOD_READ_SPARSE_ARRAY, "android.util.SparseArray"),
- ContainerArgumentMigrator(METHOD_READ_PARCELABLE_LIST, 0, "java.util.List"),
- ReturnMigratorWithClassLoader(METHOD_READ_SERIALIZABLE),
+ ReturnMigrator(PARCEL_METHOD_READ_PARCELABLE, setOf("android.os.Parcelable")),
+ ContainerArgumentMigrator(PARCEL_METHOD_READ_LIST, 0, "java.util.List"),
+ ContainerReturnMigrator(PARCEL_METHOD_READ_ARRAY_LIST, "java.util.Collection"),
+ ContainerReturnMigrator(PARCEL_METHOD_READ_SPARSE_ARRAY, "android.util.SparseArray"),
+ ContainerArgumentMigrator(PARCEL_METHOD_READ_PARCELABLE_LIST, 0, "java.util.List"),
+ ReturnMigratorWithClassLoader(PARCEL_METHOD_READ_SERIALIZABLE),
+ ArrayReturnMigrator(PARCEL_METHOD_READ_ARRAY, setOf("java.lang.Object")),
+ ArrayReturnMigrator(PARCEL_METHOD_READ_PARCELABLE_ARRAY, setOf("android.os.Parcelable")),
+
+ ReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE, setOf("android.os.Parcelable")),
+ ContainerReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE_ARRAY_LIST, "java.util.Collection", setOf("android.os.Parcelable")),
+ ArrayReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE_ARRAY, setOf("android.os.Parcelable")),
+ ContainerReturnMigrator(BUNDLE_METHOD_GET_SPARSE_PARCELABLE_ARRAY, "android.util.SparseArray", setOf("android.os.Parcelable")),
+ ReturnMigrator(BUNDLE_METHOD_GET_SERIALIZABLE, setOf("java.io.Serializable")),
+
+ ReturnMigrator(INTENT_METHOD_GET_PARCELABLE_EXTRA, setOf("android.os.Parcelable")),
+ ContainerReturnMigrator(INTENT_METHOD_GET_PARCELABLE_ARRAY_LIST_EXTRA, "java.util.Collection", setOf("android.os.Parcelable")),
+ ArrayReturnMigrator(INTENT_METHOD_GET_PARCELABLE_ARRAY_EXTRA, setOf("android.os.Parcelable")),
+ ReturnMigrator(INTENT_METHOD_GET_SERIALIZABLE_EXTRA, setOf("java.io.Serializable")),
)
}
-} \ No newline at end of file
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
index 05c7850c44c2..1286e519aae6 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
@@ -27,20 +27,22 @@ class SaferParcelCheckerTest : LintDetectorTest() {
override fun getDetector(): Detector = SaferParcelChecker()
override fun getIssues(): List<Issue> = listOf(
- SaferParcelChecker.ISSUE_UNSAFE_API_USAGE
+ SaferParcelChecker.ISSUE_UNSAFE_API_USAGE
)
override fun lint(): TestLintTask =
- super.lint()
- .allowMissingSdk(true)
- // We don't do partial analysis in the platform
- .skipTestModes(TestMode.PARTIAL)
+ super.lint()
+ .allowMissingSdk(true)
+ // We don't do partial analysis in the platform
+ .skipTestModes(TestMode.PARTIAL)
- fun testDetectUnsafeReadSerializable() {
+ /** Parcel Tests */
+
+ fun testParcelDetectUnsafeReadSerializable() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.os.Parcel;
import java.io.Serializable;
@@ -51,27 +53,27 @@ class SaferParcelCheckerTest : LintDetectorTest() {
}
}
"""
- ).indented(),
- *includes
- )
- .expectIdenticalTestModeOutput(false)
- .run()
- .expect(
- """
+ ).indented(),
+ *includes
+ )
+ .expectIdenticalTestModeOutput(false)
+ .run()
+ .expect(
+ """
src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readSerializable() \
API usage [UnsafeParcelApi]
Serializable ans = p.readSerializable();
~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
- )
+ )
}
- fun testDoesNotDetectSafeReadSerializable() {
+ fun testParcelDoesNotDetectSafeReadSerializable() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.os.Parcel;
import java.io.Serializable;
@@ -82,18 +84,18 @@ class SaferParcelCheckerTest : LintDetectorTest() {
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect("No warnings.")
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
}
- fun testDetectUnsafeReadArrayList() {
+ fun testParcelDetectUnsafeReadArrayList() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.os.Parcel;
@@ -103,26 +105,26 @@ class SaferParcelCheckerTest : LintDetectorTest() {
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect(
- """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
src/test/pkg/TestClass.java:6: Warning: Unsafe Parcel.readArrayList() API \
usage [UnsafeParcelApi]
ArrayList ans = p.readArrayList(null);
~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
- )
+ )
}
- fun testDoesNotDetectSafeReadArrayList() {
+ fun testParcelDoesNotDetectSafeReadArrayList() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -133,18 +135,18 @@ class SaferParcelCheckerTest : LintDetectorTest() {
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect("No warnings.")
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
}
- fun testDetectUnsafeReadList() {
+ fun testParcelDetectUnsafeReadList() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -157,26 +159,26 @@ class SaferParcelCheckerTest : LintDetectorTest() {
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect(
- """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
src/test/pkg/TestClass.java:9: Warning: Unsafe Parcel.readList() API usage \
[UnsafeParcelApi]
p.readList(list, null);
~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
- )
+ )
}
- fun testDoesNotDetectSafeReadList() {
+ fun testDParceloesNotDetectSafeReadList() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -189,18 +191,18 @@ class SaferParcelCheckerTest : LintDetectorTest() {
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect("No warnings.")
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
}
- fun testDetectUnsafeReadParcelable() {
+ fun testParcelDetectUnsafeReadParcelable() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -211,26 +213,26 @@ class SaferParcelCheckerTest : LintDetectorTest() {
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect(
- """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readParcelable() API \
usage [UnsafeParcelApi]
Intent ans = p.readParcelable(null);
~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
- )
+ )
}
- fun testDoesNotDetectSafeReadParcelable() {
+ fun testParcelDoesNotDetectSafeReadParcelable() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -241,18 +243,18 @@ class SaferParcelCheckerTest : LintDetectorTest() {
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect("No warnings.")
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
}
- fun testDetectUnsafeReadParcelableList() {
+ fun testParcelDetectUnsafeReadParcelableList() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -265,26 +267,26 @@ class SaferParcelCheckerTest : LintDetectorTest() {
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect(
- """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
src/test/pkg/TestClass.java:9: Warning: Unsafe Parcel.readParcelableList() \
API usage [UnsafeParcelApi]
List<Intent> ans = p.readParcelableList(list, null);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
- )
+ )
}
- fun testDoesNotDetectSafeReadParcelableList() {
+ fun testParcelDoesNotDetectSafeReadParcelableList() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -298,18 +300,18 @@ class SaferParcelCheckerTest : LintDetectorTest() {
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect("No warnings.")
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
}
- fun testDetectUnsafeReadSparseArray() {
+ fun testParcelDetectUnsafeReadSparseArray() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -321,26 +323,26 @@ class SaferParcelCheckerTest : LintDetectorTest() {
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect(
- """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
src/test/pkg/TestClass.java:8: Warning: Unsafe Parcel.readSparseArray() API\
usage [UnsafeParcelApi]
SparseArray<Intent> ans = p.readSparseArray(null);
~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
- )
+ )
}
- fun testDoesNotDetectSafeReadSparseArray() {
+ fun testParcelDoesNotDetectSafeReadSparseArray() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -353,21 +355,383 @@ class SaferParcelCheckerTest : LintDetectorTest() {
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect("No warnings.")
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ fun testParcelDetectUnsafeReadSArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Parcel;
+
+ public class TestClass {
+ private TestClass(Parcel p) {
+ Intent[] ans = p.readArray(null);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readArray() API\
+ usage [UnsafeParcelApi]
+ Intent[] ans = p.readArray(null);
+ ~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testParcelDoesNotDetectSafeReadArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Parcel;
+
+ public class TestClass {
+ private TestClass(Parcel p) {
+ Intent[] ans = p.readArray(null, Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ fun testParcelDetectUnsafeReadParcelableSArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Parcel;
+
+ public class TestClass {
+ private TestClass(Parcel p) {
+ Intent[] ans = p.readParcelableArray(null);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readParcelableArray() API\
+ usage [UnsafeParcelApi]
+ Intent[] ans = p.readParcelableArray(null);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testParcelDoesNotDetectSafeReadParcelableArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Parcel;
+
+ public class TestClass {
+ private TestClass(Parcel p) {
+ Intent[] ans = p.readParcelableArray(null, Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ /** Bundle Tests */
+
+ fun testBundleDetectUnsafeGetParcelable() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ Intent ans = b.getParcelable("key");
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelable() API usage [UnsafeParcelApi]
+ Intent ans = b.getParcelable("key");
+ ~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testBundleDoesNotDetectSafeGetParcelable() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ Intent ans = b.getParcelable("key", Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ fun testBundleDetectUnsafeGetParcelableArrayList() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ ArrayList<Intent> ans = b.getParcelableArrayList("key");
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelableArrayList() API usage [UnsafeParcelApi]
+ ArrayList<Intent> ans = b.getParcelableArrayList("key");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
}
+ fun testBundleDoesNotDetectSafeGetParcelableArrayList() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ ArrayList<Intent> ans = b.getParcelableArrayList("key", Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ fun testBundleDetectUnsafeGetParcelableArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ Intent[] ans = b.getParcelableArray("key");
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelableArray() API usage [UnsafeParcelApi]
+ Intent[] ans = b.getParcelableArray("key");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testBundleDoesNotDetectSafeGetParcelableArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ Intent[] ans = b.getParcelableArray("key", Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ fun testBundleDetectUnsafeGetSparseParcelableArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ SparseArray<Intent> ans = b.getSparseParcelableArray("key");
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getSparseParcelableArray() API usage [UnsafeParcelApi]
+ SparseArray<Intent> ans = b.getSparseParcelableArray("key");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testBundleDoesNotDetectSafeGetSparseParcelableArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ SparseArray<Intent> ans = b.getSparseParcelableArray("key", Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ /** Intent Tests */
+
+ fun testIntentDetectUnsafeGetParcelableExtra() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+
+ public class TestClass {
+ private TestClass(Intent i) {
+ Intent ans = i.getParcelableExtra("name");
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:6: Warning: Unsafe Intent.getParcelableExtra() API usage [UnsafeParcelApi]
+ Intent ans = i.getParcelableExtra("name");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testIntentDoesNotDetectSafeGetParcelableExtra() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+
+ public class TestClass {
+ private TestClass(Intent i) {
+ Intent ans = i.getParcelableExtra("name", Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+
/** Stubs for classes used for testing */
private val includes =
- arrayOf(
- manifest().minSdk("Tiramisu"),
- java(
- """
+ arrayOf(
+ manifest().minSdk("Tiramisu"),
+ java(
+ """
package android.os;
import java.util.ArrayList;
import java.util.List;
@@ -375,7 +739,7 @@ class SaferParcelCheckerTest : LintDetectorTest() {
import java.util.HashMap;
public final class Parcel {
- // Deprecateds
+ // Deprecated
public Object[] readArray(ClassLoader loader) { return null; }
public ArrayList readArrayList(ClassLoader loader) { return null; }
public HashMap readHashMap(ClassLoader loader) { return null; }
@@ -402,26 +766,57 @@ class SaferParcelCheckerTest : LintDetectorTest() {
public <T> SparseArray<T> readSparseArray(ClassLoader loader, Class<? extends T> clazz) { return null; }
}
"""
- ).indented(),
- java(
+ ).indented(),
+ java(
+ """
+ package android.os;
+ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.HashMap;
+
+ public final class Bundle {
+ // Deprecated
+ public <T extends Parcelable> T getParcelable(String key) { return null; }
+ public <T extends Parcelable> ArrayList<T> getParcelableArrayList(String key) { return null; }
+ public Parcelable[] getParcelableArray(String key) { return null; }
+ public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(String key) { return null; }
+
+ // Replacements
+ public <T> T getParcelable(String key, Class<T> clazz) { return null; }
+ public <T> ArrayList<T> getParcelableArrayList(String key, Class<? extends T> clazz) { return null; }
+ public <T> T[] getParcelableArray(String key, Class<T> clazz) { return null; }
+ public <T> SparseArray<T> getSparseParcelableArray(String key, Class<? extends T> clazz) { return null; }
+
+ }
"""
+ ).indented(),
+ java(
+ """
package android.os;
public interface Parcelable {}
"""
- ).indented(),
- java(
- """
+ ).indented(),
+ java(
+ """
package android.content;
- public class Intent implements Parcelable, Cloneable {}
- """
- ).indented(),
- java(
+ public class Intent implements Parcelable, Cloneable {
+ // Deprecated
+ public <T extends Parcelable> T getParcelableExtra(String name) { return null; }
+
+ // Replacements
+ public <T> T getParcelableExtra(String name, Class<T> clazz) { return null; }
+
+ }
"""
+ ).indented(),
+ java(
+ """
package android.util;
public class SparseArray<E> implements Cloneable {}
"""
- ).indented(),
- )
+ ).indented(),
+ )
// Substitutes "backslash + new line" with an empty string to imitate line continuation
private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")